Skip to main content

rustauth_plugins/jwt/
verify.rs

1use base64::engine::general_purpose::URL_SAFE_NO_PAD;
2use base64::Engine;
3use josekit::jws::alg::ecdsa::EcdsaJwsAlgorithm::{Es256, Es512};
4use josekit::jws::alg::eddsa::EddsaJwsAlgorithm::Eddsa;
5use josekit::jws::alg::rsassa::RsassaJwsAlgorithm::Rs256;
6use josekit::jws::alg::rsassa_pss::RsassaPssJwsAlgorithm::Ps256;
7use josekit::jwt;
8use rustauth_core::context::AuthContext;
9use rustauth_core::error::RustAuthError;
10use serde_json::Value;
11
12use super::claims::JwtClaims;
13use super::{adapter, JwkAlgorithm, JwtOptions};
14
15pub async fn verify_jwt(
16    context: &AuthContext,
17    token: &str,
18    issuer_override: Option<&str>,
19) -> Result<Option<JwtClaims>, RustAuthError> {
20    verify_jwt_with_options(context, token, &JwtOptions::default(), issuer_override).await
21}
22
23pub async fn verify_jwt_with_options(
24    context: &AuthContext,
25    token: &str,
26    options: &JwtOptions,
27    issuer_override: Option<&str>,
28) -> Result<Option<JwtClaims>, RustAuthError> {
29    let Some(kid) = token_kid(token) else {
30        return Ok(None);
31    };
32    let Some(key) = adapter::get_all_keys(context, options)
33        .await?
34        .into_iter()
35        .find(|key| key.id == kid)
36    else {
37        return Ok(None);
38    };
39    let algorithm = key.alg.unwrap_or_else(|| options.algorithm());
40    let Ok(public) = josekit::jwk::Jwk::from_bytes(&key.public_key) else {
41        return Ok(None);
42    };
43    let decoded = match algorithm {
44        JwkAlgorithm::EdDsa => {
45            let Ok(verifier) = Eddsa.verifier_from_jwk(&public) else {
46                return Ok(None);
47            };
48            jwt::decode_with_verifier(token, &verifier)
49        }
50        JwkAlgorithm::Es256 => {
51            let Ok(verifier) = Es256.verifier_from_jwk(&public) else {
52                return Ok(None);
53            };
54            jwt::decode_with_verifier(token, &verifier)
55        }
56        JwkAlgorithm::Es512 => {
57            let Ok(verifier) = Es512.verifier_from_jwk(&public) else {
58                return Ok(None);
59            };
60            jwt::decode_with_verifier(token, &verifier)
61        }
62        JwkAlgorithm::Rs256 => {
63            let Ok(verifier) = Rs256.verifier_from_jwk(&public) else {
64                return Ok(None);
65            };
66            jwt::decode_with_verifier(token, &verifier)
67        }
68        JwkAlgorithm::Ps256 => {
69            let Ok(verifier) = Ps256.verifier_from_jwk(&public) else {
70                return Ok(None);
71            };
72            jwt::decode_with_verifier(token, &verifier)
73        }
74    };
75    let Ok((payload, _)) = decoded else {
76        return Ok(None);
77    };
78    let claims = payload.claims_set().clone();
79    if !valid_temporal_claims(&claims) || !valid_issuer(&claims, context, options, issuer_override)
80    {
81        return Ok(None);
82    }
83    if claims.get("sub").and_then(Value::as_str).is_none()
84        || !valid_audience(&claims, context, options)
85    {
86        return Ok(None);
87    }
88    Ok(Some(claims))
89}
90
91fn token_kid(token: &str) -> Option<String> {
92    let mut parts = token.split('.');
93    let header = parts.next()?;
94    parts.next()?;
95    parts.next()?;
96    if parts.next().is_some() {
97        return None;
98    }
99    let header = URL_SAFE_NO_PAD.decode(header).ok()?;
100    let header: Value = serde_json::from_slice(&header).ok()?;
101    header.get("kid").and_then(Value::as_str).map(str::to_owned)
102}
103
104fn valid_temporal_claims(claims: &JwtClaims) -> bool {
105    let now = time::OffsetDateTime::now_utc().unix_timestamp();
106    if claims
107        .get("exp")
108        .and_then(Value::as_i64)
109        .is_some_and(|exp| exp <= now)
110    {
111        return false;
112    }
113    if claims
114        .get("nbf")
115        .and_then(Value::as_i64)
116        .is_some_and(|nbf| nbf > now)
117    {
118        return false;
119    }
120    true
121}
122
123fn valid_issuer(
124    claims: &JwtClaims,
125    context: &AuthContext,
126    options: &JwtOptions,
127    issuer_override: Option<&str>,
128) -> bool {
129    let expected = issuer_override
130        .map(str::to_owned)
131        .or_else(|| options.jwt.issuer.clone())
132        .unwrap_or_else(|| context.base_url.clone());
133    claims.get("iss").and_then(Value::as_str) == Some(expected.as_str())
134}
135
136fn valid_audience(claims: &JwtClaims, context: &AuthContext, options: &JwtOptions) -> bool {
137    let expected = options
138        .jwt
139        .audience
140        .clone()
141        .unwrap_or_else(|| vec![context.base_url.clone()]);
142    match claims.get("aud") {
143        Some(Value::String(audience)) => expected.iter().any(|item| item == audience),
144        Some(Value::Array(audiences)) => audiences
145            .iter()
146            .filter_map(Value::as_str)
147            .any(|audience| expected.iter().any(|item| item == audience)),
148        _ => false,
149    }
150}