Skip to main content

lichess_api/model/oauth/
pkce.rs

1use base64::Engine;
2use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3use rand::RngExt;
4use sha2::{Digest, Sha256};
5
6/// Number of random bytes used for generated secrets.
7///
8/// 32 bytes base64url-encodes to 43 characters, the minimum length RFC 7636
9/// permits for a `code_verifier`.
10const SECRET_BYTES: usize = 32;
11
12/// Generate a cryptographically random, base64url-encoded secret.
13fn generate_secret() -> String {
14    let bytes: [u8; SECRET_BYTES] = rand::rng().random();
15    URL_SAFE_NO_PAD.encode(bytes)
16}
17
18/// A PKCE secret pair, as described by RFC 7636.
19///
20/// The `verifier` is kept private until the token exchange; the `challenge` is
21/// what gets sent in the authorization request. Only the challenge travels over
22/// the initial redirect, so an eavesdropper who intercepts the authorization
23/// code cannot exchange it without the verifier.
24///
25/// Keep the verifier out of URLs and off insecure connections. For fully
26/// client-side apps the user themselves can always extract it, which is fine.
27#[derive(Clone, Debug)]
28pub struct Pkce {
29    verifier: String,
30    challenge: String,
31}
32
33impl Pkce {
34    /// Generate a new random verifier and its derived challenge.
35    pub fn generate() -> Self {
36        let verifier = generate_secret();
37        let challenge = Self::derive_challenge(&verifier);
38
39        Self {
40            verifier,
41            challenge,
42        }
43    }
44
45    /// `BASE64URL(SHA256(code_verifier))`, the `S256` challenge method.
46    pub fn derive_challenge(verifier: &str) -> String {
47        URL_SAFE_NO_PAD.encode(Sha256::digest(verifier.as_bytes()))
48    }
49
50    /// The secret sent only in the token exchange.
51    pub fn verifier(&self) -> &str {
52        &self.verifier
53    }
54
55    /// The derived value sent in the authorization request.
56    pub fn challenge(&self) -> &str {
57        &self.challenge
58    }
59}
60
61/// Generate a random `state` value, used to tie an authorization result back to
62/// the request that started it and defend against cross site request forgery.
63pub fn generate_state() -> String {
64    generate_secret()
65}