firebase_rs_sdk/auth/oauth/
pkce.rs

1use base64::Engine;
2use rand::Rng;
3use sha2::{Digest, Sha256};
4
5const PKCE_LENGTH: usize = 64;
6const PKCE_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
7
8/// Represents a PKCE verifier/challenge pair ready to attach to OAuth flows.
9#[derive(Debug, Clone)]
10pub struct PkcePair {
11    code_verifier: String,
12    code_challenge: String,
13}
14
15impl PkcePair {
16    /// Generates a new PKCE pair using a cryptographically secure RNG.
17    pub fn generate() -> Self {
18        let mut rng = rand::thread_rng();
19        let verifier: String = (0..PKCE_LENGTH)
20            .map(|_| {
21                let idx = rng.gen_range(0..PKCE_CHARSET.len());
22                PKCE_CHARSET[idx] as char
23            })
24            .collect();
25
26        let digest = Sha256::digest(verifier.as_bytes());
27        let challenge = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest);
28
29        Self {
30            code_verifier: verifier,
31            code_challenge: challenge,
32        }
33    }
34
35    /// Returns the plain-text code verifier value that must be sent during token exchange.
36    pub fn code_verifier(&self) -> &str {
37        &self.code_verifier
38    }
39
40    /// Returns the base64url-encoded code challenge.
41    pub fn code_challenge(&self) -> &str {
42        &self.code_challenge
43    }
44
45    /// Returns the PKCE method (currently always `S256`).
46    pub fn method(&self) -> &'static str {
47        "S256"
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn pkce_pair_has_expected_lengths() {
57        let pkce = PkcePair::generate();
58        assert!(pkce.code_verifier().len() >= 43);
59        assert!(pkce.code_verifier().len() <= 128);
60        assert!(!pkce.code_challenge().is_empty());
61        assert_eq!(pkce.method(), "S256");
62    }
63}