Skip to main content

securitydept_oauth_resource_server/verifier/
mod.rs

1mod introspection;
2#[cfg(feature = "jwe")]
3mod jwe;
4#[cfg(feature = "jwe")]
5mod watcher;
6
7use std::sync::Arc;
8
9use securitydept_creds::{JwtClaimsTrait, JwtValidation, TokenData, TokenFormat, TokenJwtClaims};
10use tracing::debug;
11
12use self::introspection::OAuthResourceServerVerifierIntrospection;
13#[cfg(feature = "jwe")]
14use self::jwe::OAuthResourceServerVerifierJwe;
15use crate::{
16    OAuthResourceServerConfig, OAuthResourceServerError, OAuthResourceServerMetadata,
17    OAuthResourceServerResult, VerificationPolicy, VerifiedAccessToken, VerifiedOpaqueToken,
18    VerifiedToken, models::scope_contains_all,
19};
20
21pub struct OAuthResourceServerVerifier {
22    provider: Arc<securitydept_oauth_provider::OAuthProviderRuntime>,
23    policy: VerificationPolicy,
24    introspection: Option<OAuthResourceServerVerifierIntrospection>,
25    #[cfg(feature = "jwe")]
26    jwe: Option<OAuthResourceServerVerifierJwe>,
27}
28
29impl OAuthResourceServerVerifier {
30    pub async fn from_config(config: OAuthResourceServerConfig) -> OAuthResourceServerResult<Self> {
31        config.validate()?;
32        let provider = Arc::new(
33            securitydept_oauth_provider::OAuthProviderRuntime::from_config(
34                config.provider_config(),
35            )
36            .await?,
37        );
38        Self::from_provider(provider, config).await
39    }
40
41    pub async fn from_provider(
42        provider: Arc<securitydept_oauth_provider::OAuthProviderRuntime>,
43        config: OAuthResourceServerConfig,
44    ) -> OAuthResourceServerResult<Self> {
45        config.validate()?;
46
47        Ok(Self {
48            provider,
49            policy: VerificationPolicy::new(
50                config.audiences.clone(),
51                config.required_scopes.clone(),
52                config.clock_skew,
53            ),
54            introspection: config
55                .introspection
56                .as_ref()
57                .map(OAuthResourceServerVerifierIntrospection::from_config),
58            #[cfg(feature = "jwe")]
59            jwe: match config.jwe.as_ref() {
60                Some(jwe_config) => {
61                    Some(OAuthResourceServerVerifierJwe::from_config(jwe_config).await?)
62                }
63                None => None,
64            },
65        })
66    }
67
68    pub async fn metadata(&self) -> OAuthResourceServerResult<OAuthResourceServerMetadata> {
69        let metadata = self.provider.metadata().await?;
70        Ok(OAuthResourceServerMetadata {
71            issuer: metadata.issuer,
72            jwks_uri: metadata.jwks_uri,
73            introspection_url: metadata.introspection_endpoint,
74        })
75    }
76
77    pub fn policy(&self) -> &VerificationPolicy {
78        &self.policy
79    }
80
81    pub fn provider(&self) -> &Arc<securitydept_oauth_provider::OAuthProviderRuntime> {
82        &self.provider
83    }
84
85    pub async fn verify_token<CLAIMS>(
86        &self,
87        token: &str,
88    ) -> OAuthResourceServerResult<VerifiedToken<CLAIMS>>
89    where
90        CLAIMS: JwtClaimsTrait,
91    {
92        match TokenFormat::from_token(token) {
93            TokenFormat::Opaque => Ok(VerifiedToken::from(
94                self.verify_opaque_access_token(token).await?,
95            )),
96            _ => Ok(VerifiedToken::from(self.verify_access_token(token).await?)),
97        }
98    }
99
100    pub async fn verify_opaque_access_token(
101        &self,
102        token: &str,
103    ) -> OAuthResourceServerResult<VerifiedOpaqueToken> {
104        self.introspection
105            .as_ref()
106            .ok_or(OAuthResourceServerError::UnsupportedTokenFormat {
107                token_format: TokenFormat::Opaque,
108            })?
109            .introspect(token, &self.provider, &self.policy)
110            .await
111    }
112
113    pub async fn verify_rfc9068_access_token(
114        &self,
115        token: &str,
116    ) -> OAuthResourceServerResult<VerifiedAccessToken<TokenJwtClaims>> {
117        let token_data = self
118            .verify_structured_token_data::<TokenJwtClaims>(token)
119            .await?;
120        validate_rfc9068_scope_policy(&token_data, &self.policy)?;
121
122        Ok(VerifiedAccessToken {
123            token_data,
124            metadata: self.metadata().await?,
125        })
126    }
127
128    pub async fn verify_access_token<CLAIMS>(
129        &self,
130        token: &str,
131    ) -> OAuthResourceServerResult<VerifiedAccessToken<CLAIMS>>
132    where
133        CLAIMS: JwtClaimsTrait,
134    {
135        let token_data = self.verify_structured_token_data::<CLAIMS>(token).await?;
136
137        Ok(VerifiedAccessToken {
138            token_data,
139            metadata: self.metadata().await?,
140        })
141    }
142
143    async fn verify_structured_token_data<CLAIMS>(
144        &self,
145        token: &str,
146    ) -> OAuthResourceServerResult<TokenData<CLAIMS>>
147    where
148        CLAIMS: JwtClaimsTrait,
149    {
150        match TokenFormat::from_token(token) {
151            TokenFormat::JWT => self.verify_jwt_token_data(token).await,
152            TokenFormat::Opaque => Err(OAuthResourceServerError::UnsupportedTokenFormat {
153                token_format: TokenFormat::Opaque,
154            }),
155            TokenFormat::JWE => self.verify_jwe_token_data(token).await,
156        }
157    }
158
159    async fn verify_jwt_token_data<CLAIMS>(
160        &self,
161        token: &str,
162    ) -> OAuthResourceServerResult<TokenData<CLAIMS>>
163    where
164        CLAIMS: JwtClaimsTrait,
165    {
166        match self.verify_jwt_with_current_jwks(token).await {
167            Ok(token_data) => Ok(token_data),
168            Err(error) if should_retry_with_refreshed_jwks(&error) => {
169                debug!("Retrying access token verification after JWKS refresh");
170                let metadata = self.provider.refresh_jwks().await?;
171                verify_jwt_with_policy::<CLAIMS>(
172                    token,
173                    &metadata.jwks,
174                    &OAuthResourceServerMetadata {
175                        issuer: metadata.issuer,
176                        jwks_uri: metadata.jwks_uri,
177                        introspection_url: metadata.introspection_endpoint,
178                    },
179                    &self.policy,
180                )
181            }
182            Err(error) => Err(error),
183        }
184    }
185
186    async fn verify_jwt_with_current_jwks<CLAIMS>(
187        &self,
188        token: &str,
189    ) -> OAuthResourceServerResult<TokenData<CLAIMS>>
190    where
191        CLAIMS: JwtClaimsTrait,
192    {
193        let metadata = self.provider.metadata().await?;
194        let resource_metadata = OAuthResourceServerMetadata {
195            issuer: metadata.issuer.clone(),
196            jwks_uri: metadata.jwks_uri.clone(),
197            introspection_url: metadata.introspection_endpoint.clone(),
198        };
199        verify_jwt_with_policy::<CLAIMS>(token, &metadata.jwks, &resource_metadata, &self.policy)
200    }
201
202    async fn verify_jwe_token_data<CLAIMS>(
203        &self,
204        token: &str,
205    ) -> OAuthResourceServerResult<TokenData<CLAIMS>>
206    where
207        CLAIMS: JwtClaimsTrait,
208    {
209        #[cfg(feature = "jwe")]
210        {
211            return match self.verify_jwe_with_current_jwks(token).await {
212                Ok(token_data) => Ok(token_data),
213                Err(error) if should_retry_with_refreshed_jwks(&error) => {
214                    debug!("Retrying JWE access token verification after JWKS refresh");
215                    let metadata = self.provider.refresh_jwks().await?;
216                    self.jwe
217                        .as_ref()
218                        .ok_or(OAuthResourceServerError::UnsupportedTokenFormat {
219                            token_format: TokenFormat::JWE,
220                        })?
221                        .verify_token_data::<CLAIMS>(
222                            token,
223                            &metadata.jwks,
224                            &OAuthResourceServerMetadata {
225                                issuer: metadata.issuer,
226                                jwks_uri: metadata.jwks_uri,
227                                introspection_url: metadata.introspection_endpoint,
228                            },
229                            &self.policy,
230                        )
231                        .await
232                }
233                Err(error) => Err(error),
234            };
235        }
236
237        #[cfg(not(feature = "jwe"))]
238        {
239            let _ = token;
240            Err(OAuthResourceServerError::UnsupportedTokenFormat {
241                token_format: TokenFormat::JWE,
242            })
243        }
244    }
245
246    #[cfg(feature = "jwe")]
247    async fn verify_jwe_with_current_jwks<CLAIMS>(
248        &self,
249        token: &str,
250    ) -> OAuthResourceServerResult<TokenData<CLAIMS>>
251    where
252        CLAIMS: JwtClaimsTrait,
253    {
254        let metadata = self.provider.metadata().await?;
255        self.jwe
256            .as_ref()
257            .ok_or(OAuthResourceServerError::UnsupportedTokenFormat {
258                token_format: TokenFormat::JWE,
259            })?
260            .verify_token_data::<CLAIMS>(
261                token,
262                &metadata.jwks,
263                &OAuthResourceServerMetadata {
264                    issuer: metadata.issuer,
265                    jwks_uri: metadata.jwks_uri,
266                    introspection_url: metadata.introspection_endpoint,
267                },
268                &self.policy,
269            )
270            .await
271    }
272}
273
274#[cfg(not(feature = "jwe"))]
275fn verify_jwt_with_policy<CLAIMS>(
276    token: &str,
277    jwks: &openidconnect::core::CoreJsonWebKeySet,
278    metadata: &OAuthResourceServerMetadata,
279    policy: &VerificationPolicy,
280) -> OAuthResourceServerResult<TokenData<CLAIMS>>
281where
282    CLAIMS: JwtClaimsTrait,
283{
284    securitydept_creds::verify_token_rfc9068_with_jwks_without_jwe(
285        token,
286        jwks,
287        |mut validation: JwtValidation| {
288            apply_validation_policy(&mut validation, metadata, policy);
289            Ok(validation)
290        },
291    )
292    .map_err(|source| OAuthResourceServerError::TokenValidation { source })
293}
294
295#[cfg(feature = "jwe")]
296fn verify_jwt_with_policy<CLAIMS>(
297    token: &str,
298    jwks: &openidconnect::core::CoreJsonWebKeySet,
299    metadata: &OAuthResourceServerMetadata,
300    policy: &VerificationPolicy,
301) -> OAuthResourceServerResult<TokenData<CLAIMS>>
302where
303    CLAIMS: JwtClaimsTrait,
304{
305    use crate::LocalJweDecryptionKeySet;
306
307    securitydept_creds::verify_token_rfc9068_with_jwks(
308        token,
309        jwks,
310        &LocalJweDecryptionKeySet::new(Vec::new()),
311        |mut validation: JwtValidation| {
312            apply_validation_policy(&mut validation, metadata, policy);
313            Ok(validation)
314        },
315    )
316    .map_err(|source| OAuthResourceServerError::TokenValidation { source })
317}
318
319pub(super) fn apply_validation_policy(
320    validation: &mut JwtValidation,
321    metadata: &OAuthResourceServerMetadata,
322    policy: &VerificationPolicy,
323) {
324    validation.leeway = policy.clock_skew().as_secs();
325    validation.validate_nbf = true;
326    validation.set_required_spec_claims(&["exp", "iss"]);
327    validation.set_issuer(&[metadata.issuer.as_str()]);
328    if !policy.allowed_audiences().is_empty() {
329        validation.set_audience(policy.allowed_audiences());
330    } else {
331        validation.validate_aud = false;
332    }
333}
334
335fn validate_rfc9068_scope_policy(
336    token_data: &TokenData<TokenJwtClaims>,
337    policy: &VerificationPolicy,
338) -> OAuthResourceServerResult<()> {
339    match token_data {
340        TokenData::JWT(data) => validate_scope_policy(data.claims.scope.as_ref(), policy),
341        #[cfg(feature = "jwe")]
342        TokenData::JWE(data) => validate_scope_policy(data.claims().scope.as_ref(), policy),
343        TokenData::Opaque => Err(OAuthResourceServerError::UnsupportedTokenFormat {
344            token_format: TokenFormat::Opaque,
345        }),
346        #[cfg(not(feature = "jwe"))]
347        _ => Err(OAuthResourceServerError::UnsupportedTokenFormat {
348            token_format: TokenFormat::JWE,
349        }),
350    }
351}
352
353fn validate_scope_policy(
354    scope: Option<&securitydept_creds::Scope>,
355    policy: &VerificationPolicy,
356) -> OAuthResourceServerResult<()> {
357    if scope_contains_all(scope, policy.required_scopes()) {
358        return Ok(());
359    }
360
361    Err(OAuthResourceServerError::PolicyViolation {
362        message: if policy.required_scopes().is_empty() {
363            "Access token scopes failed policy validation".to_string()
364        } else {
365            format!(
366                "Access token is missing one or more required scopes: {}",
367                policy.required_scopes().join(", ")
368            )
369        },
370    })
371}
372
373fn should_retry_with_refreshed_jwks(error: &OAuthResourceServerError) -> bool {
374    matches!(
375        error,
376        OAuthResourceServerError::TokenValidation {
377            source: securitydept_creds::CredsError::InvalidCredentialsFormat { .. }
378        }
379    )
380}