Skip to main content

origin_auth/
pkce.rs

1use crate::random_token;
2use base64::Engine as _;
3use origin_domain::Result;
4use sha2::{Digest, Sha256};
5
6/// Proof Key for Code Exchange (RFC 7636), S256 only.
7///
8/// `plain` is deliberately not supported: it offers no protection, and every provider
9/// worth integrating supports S256.
10#[derive(Clone)]
11pub struct Pkce {
12    verifier: String,
13    challenge: String,
14}
15
16impl Pkce {
17    /// Generate a fresh verifier and its challenge.
18    pub fn generate() -> Result<Self> {
19        // 32 bytes → 43 base64url characters, the length RFC 7636 recommends.
20        let verifier = random_token(32)?;
21        let challenge = Self::challenge_for(&verifier);
22
23        Ok(Self {
24            verifier,
25            challenge,
26        })
27    }
28
29    fn challenge_for(verifier: &str) -> String {
30        let digest = Sha256::digest(verifier.as_bytes());
31        base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest)
32    }
33
34    /// Sent to the authorization endpoint.
35    pub fn challenge(&self) -> &str {
36        &self.challenge
37    }
38
39    /// Sent to the token endpoint. Never appears in a URL the user can see.
40    pub fn verifier(&self) -> &str {
41        &self.verifier
42    }
43}
44
45/// Redacted: the verifier is the secret half of the exchange.
46impl std::fmt::Debug for Pkce {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("Pkce")
49            .field("challenge", &self.challenge)
50            .field("verifier", &"***")
51            .finish()
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn the_challenge_is_the_sha256_of_the_verifier() {
61        // The example pair from RFC 7636, appendix B.
62        let verifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
63        assert_eq!(
64            Pkce::challenge_for(verifier),
65            "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
66        );
67    }
68
69    #[test]
70    fn every_flow_gets_a_fresh_verifier() {
71        let first = Pkce::generate().unwrap();
72        let second = Pkce::generate().unwrap();
73        assert_ne!(first.verifier(), second.verifier());
74        assert_eq!(first.verifier().len(), 43);
75    }
76
77    #[test]
78    fn debug_output_never_contains_the_verifier() {
79        let pkce = Pkce::generate().unwrap();
80        assert!(!format!("{pkce:?}").contains(pkce.verifier()));
81    }
82}