Skip to main content

huskarl_resource_server/validator/
mod.rs

1//! Access token validation: the [`AccessTokenValidator`] trait and ready-made
2//! implementations of it.
3//!
4//! A validator turns an incoming request's headers into a [`ValidatedRequest`]
5//! (or a typed rejection). Pick the one matching how your tokens are verified:
6//! [`rfc9068::Rfc9068Validator`] for self-contained RFC 9068 JWT access tokens,
7//! [`introspection::IntrospectionValidator`] for RFC 7662 introspection, or
8//! [`custom::CustomValidator`] for an authorization server that follows neither.
9//! See [choosing a validator](crate::_docs::explanation::choosing_a_validator)
10//! for the trade-offs, and [`multi_issuer`] to accept more than one issuer.
11
12mod binding;
13mod common;
14pub mod custom;
15pub mod dpop_nonce;
16pub mod dpop_proof;
17pub mod error;
18pub mod extract;
19pub mod introspection;
20pub mod metadata;
21pub mod multi_issuer;
22pub mod observe;
23pub mod rfc9068;
24
25use crate::{
26    core::{
27        jwt::{ConfirmationClaim, validator::ValidatedJwt},
28        platform::{Duration, MaybeSendBoxFuture, MaybeSendSync, SystemTime},
29    },
30    error::ToRfc6750Error,
31};
32
33/// Default clock-skew leeway applied to the temporal checks (`exp`, `nbf`,
34/// `iat`, `DPoP` proof freshness). RFC 9449 §11.1 tells servers to accept
35/// proofs in a reasonable window around the current time; the same reasoning
36/// applies to access-token validation. Ten seconds absorbs real-world NTP
37/// drift without meaningfully weakening proof freshness — override it with
38/// the `clock_leeway` builder option on each validator.
39pub const DEFAULT_CLOCK_LEEWAY: Duration = Duration::from_secs(10);
40
41/// A trait for validators that authenticate and validate access tokens from HTTP requests.
42///
43/// Implementations handle token extraction from request headers, JWT validation,
44/// and sender-constraint binding checks (`DPoP`, mTLS).
45///
46/// The `outcome` field of [`ValidationResult`] is `Ok(None)` when no authentication header is
47/// present (unauthenticated request), `Ok(Some(_))` when a valid token is found, and `Err(_)`
48/// when a token is present but invalid.
49pub trait AccessTokenValidator: MaybeSendSync {
50    /// The application-specific claims type extracted from the token.
51    type Claims: MaybeSendSync;
52    /// The error type returned when validation fails.
53    type Error: ToRfc6750Error;
54
55    /// Validates an access token from the given HTTP request headers.
56    ///
57    /// `uri` must be the **absolute external target URI** the client addressed
58    /// (scheme, authority, and path); it is compared against the `htu` claim of
59    /// any `DPoP` proof (RFC 9449 §4.3). A non-absolute URI fails `DPoP`
60    /// validation with a server-side integration error, never a per-request
61    /// mismatch; deployments that never accept `DPoP` tokens do not use `uri`.
62    /// Behind TLS-terminating or rewriting proxies only the deployment knows this
63    /// URI — [validating DPoP-bound tokens](crate::_docs::guide::dpop) covers
64    /// reconstructing it.
65    ///
66    /// Returns a boxed future so the trait is object-safe: heterogeneous
67    /// validators can be stored as `Box<dyn AccessTokenValidator<…>>` (see
68    /// [`multi_issuer`]). Concrete validators also expose an inherent `validate_request`
69    /// that returns an unboxed future for zero-cost direct use.
70    fn validate_request<'a>(
71        &'a self,
72        headers: &'a http::HeaderMap,
73        method: &'a http::Method,
74        uri: &'a http::Uri,
75        client_cert_der: Option<&'a [u8]>,
76    ) -> MaybeSendBoxFuture<'a, ValidationResult<Self::Claims, Self::Error>>;
77}
78
79/// The result of an [`AccessTokenValidator::validate_request`] call.
80///
81/// To turn an unauthenticated or failed result into the matching HTTP
82/// response (status code, `WWW-Authenticate` challenges, `DPoP-Nonce`
83/// header), use [`rejection`](Self::rejection) — see the
84/// [`rejection`](crate::rejection) module.
85#[derive(Debug)]
86pub struct ValidationResult<C, E> {
87    /// The outcome of the validation.
88    pub outcome: Result<Option<ValidatedRequest<C>>, E>,
89    /// A `DPoP` nonce to include in the response `DPoP-Nonce` header, if any.
90    ///
91    /// Set on failures that demand a nonce (`use_dpop_nonce`) **and on
92    /// successful validations** whose nonce is approaching expiry — echo it
93    /// in both cases, or clients lose nonce freshness and pay a retry.
94    /// [`rejection`](Self::rejection) carries it through automatically on
95    /// the error path; on success it remains yours to send.
96    pub dpop_nonce: Option<String>,
97}
98
99/// A validated access token request, containing the parsed claims and other metadata.
100///
101/// Returned by [`super::AccessTokenValidator::validate_request`].
102#[derive(Debug)]
103pub struct ValidatedRequest<Claims> {
104    /// The issuer of the token, if present.
105    pub issuer: Option<String>,
106    /// The subject of the token, if present.
107    pub subject: Option<String>,
108    /// The audience of the token.
109    pub audience: Vec<String>,
110    /// The token ID, if present.
111    pub jti: Option<String>,
112    /// The issued-at timestamp, if present.
113    pub issued_at: Option<SystemTime>,
114    /// The expiration timestamp, if present.
115    pub expiration: Option<SystemTime>,
116    /// The key confirmation claim (`cnf`, RFC 7800), if present.
117    ///
118    /// Binds the token to a `DPoP` key (`jkt`, RFC 9449) or mTLS certificate
119    /// (`x5t#S256`, RFC 8705).
120    pub cnf: Option<ConfirmationClaim>,
121    /// Additional claims beyond the registered token claim set.
122    ///
123    /// Format-specific and extension claims (e.g. RFC 9068 `client_id` and the
124    /// RFC 9396 `authorization_details`) live here, in the typed claims type —
125    /// not as fields on this struct, which carries only the universal token set.
126    pub claims: Claims,
127    /// Raw introspection JWT (RFC 9701), if the authorization server returned one.
128    ///
129    /// Populated only when using [`crate::validator::introspection::IntrospectionValidator`] and the
130    /// AS responded with `application/token-introspection+jwt`. Useful for forwarding to
131    /// downstream services.
132    pub introspection_jwt: Option<String>,
133}
134
135impl<C> From<ValidatedJwt<C>> for ValidatedRequest<C> {
136    fn from(jwt: ValidatedJwt<C>) -> Self {
137        Self {
138            issuer: jwt.issuer,
139            subject: jwt.subject,
140            audience: jwt.audience,
141            jti: jwt.jti,
142            issued_at: jwt.issued_at,
143            expiration: jwt.expiration,
144            cnf: jwt.cnf,
145            claims: jwt.claims,
146            introspection_jwt: None,
147        }
148    }
149}