securitydept_oauth_resource_server/verifier/
mod.rs1mod 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}