oauth_as/server.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (C) 2026 Matthew Jackson
3
4//! The authorization server itself: configuration, the clock seam, and the grant state machines.
5//!
6//! Construction is the crate's ONLY allocation entry point (see the crate docs on zero cost until
7//! enabled): a host that never constructs [`AuthorizationServer`] pays nothing. There is no
8//! background task; every state transition happens inside a host-driven call.
9
10use std::fmt;
11use std::time::{Duration, SystemTime};
12
13use crate::authorization::{
14 AuthorizationCodeRecord, AuthorizationCodeState, AuthorizationError,
15 AuthorizationErrorRedirect, AuthorizationRequest, AuthorizationResponse, CodeChallengeMethod,
16 ValidatedAuthorizationRequest,
17};
18use crate::client::{Client, ClientId};
19#[cfg(feature = "client-assertion")]
20use crate::client_assertion::{verify_assertion, CLIENT_ASSERTION_TYPE};
21use crate::device::{
22 normalize_user_code, DeviceAuthorizationResponse, DeviceGrant, DeviceGrantState,
23};
24#[cfg(feature = "dpop")]
25use crate::dpop::verify_proof;
26use crate::error::{ErrorCode, ErrorResponse};
27use crate::events::{
28 Attempt, AttemptOutcome, ClientAuthFailure, Event, EventSink, Hooks, RateLimitDecision,
29 RateLimiter,
30};
31use crate::grant::GrantType;
32// The crate's ONE lower-case hex encoder (`client::SecretHash` is the other caller). Aliased to
33// the name this module used when it carried its own copy, which is also the name the measurement
34// in `crate::hex` is written against.
35use crate::hex::encode as hex_encode;
36#[cfg(feature = "jwt")]
37use crate::jwt::{AccessTokenClaims, AccessTokenFormat, Jwks};
38use crate::scope::ScopeSet;
39use crate::store::{Storage, StorageError};
40use crate::token::{
41 IntrospectionResponse, IssuedToken, RefreshTokenRecord, RefreshTokenState, TokenResponse,
42 TokenType, TokenTypeHint,
43};
44
45/// Seconds since the Unix epoch, for the RFC 7519 `exp` / `iat` style claims RFC 7662 reuses.
46/// A pre-epoch instant is not representable in that encoding, so it is reported as absent rather
47/// than wrapped into a misleading number.
48/// `base + span`, saturating instead of panicking.
49///
50/// `SystemTime + Duration` PANICS on overflow, and every caller adds a HOST-CONFIGURED `Duration`
51/// to `now`. `ServerConfig`'s TTL fields are public and nothing validates them, so a deployment
52/// that sets one from a config file holding an out-of-range value would panic on an ordinary
53/// request rather than fail at startup. Saturating yields a deadline no clock will reach, which
54/// for an expiry means "does not expire by time" — which is what the absurd configuration asked
55/// for.
56///
57/// The ATTACKER-supplied durations in this crate (`dpop`, `client_assertion`) already use
58/// `checked_add`, with the reasoning written beside them. This is the host-supplied half of the
59/// same rule, and it was missing.
60pub(crate) fn saturating_deadline(base: SystemTime, span: std::time::Duration) -> SystemTime {
61 if let Some(exact) = base.checked_add(span) {
62 return exact;
63 }
64 // Halve until it fits, accumulating what does: this lands within a second of the platform's
65 // ceiling without needing to know what that ceiling is.
66 let mut out = base;
67 let mut span = span;
68 while span > std::time::Duration::from_secs(1) {
69 span /= 2;
70 if let Some(next) = out.checked_add(span) {
71 out = next;
72 }
73 }
74 out
75}
76
77pub(crate) fn unix_seconds(t: SystemTime) -> Option<u64> {
78 t.duration_since(std::time::UNIX_EPOCH)
79 .ok()
80 .map(|d| d.as_secs())
81}
82
83/// The time source. Injectable so grant expiry and poll pacing are testable without sleeping;
84/// production hosts use [`SystemClock`].
85pub trait Clock: Send + Sync {
86 /// The current instant.
87 fn now(&self) -> SystemTime;
88}
89
90/// The real clock.
91#[derive(Debug, Clone, Copy, Default)]
92pub struct SystemClock;
93
94impl Clock for SystemClock {
95 fn now(&self) -> SystemTime {
96 SystemTime::now()
97 }
98}
99
100/// Server configuration. [`ServerConfig::new`] fills RFC-shaped defaults; every field is public so
101/// hosts override what they need.
102#[derive(Debug, Clone, PartialEq, Eq)]
103/// `#[non_exhaustive]`: this struct's field set VARIES WITH CARGO FEATURES, so a host that writes a
104/// full struct literal has a build that breaks the day anything in their dependency graph enables a
105/// feature they did not ask for. Construct with `new()` and assign the fields you want. This is the
106/// one attribute on this type that cannot be added after publication, because by then somebody's
107/// struct literal is in production.
108#[non_exhaustive]
109pub struct ServerConfig {
110 /// The issuer identifier (RFC 8414 `issuer`): the canonical `https` URL of this AS.
111 ///
112 /// RFC 8414 section 2 requires the `https` scheme in production. This crate does NOT enforce
113 /// it, because the same code has to be runnable over plain HTTP on loopback for conformance
114 /// runs and local development; enforcing transport security is the host's job, and the host
115 /// is the only party that knows whether it is behind a TLS terminator.
116 pub issuer: String,
117 /// Where a user goes to enter a device user code (RFC 8628 `verification_uri`).
118 pub verification_uri: String,
119 /// RFC 8414 `authorization_endpoint`. `None` derives `{issuer}/authorize`.
120 pub authorization_endpoint: Option<String>,
121 /// RFC 8414 `token_endpoint`. `None` derives `{issuer}/token`.
122 pub token_endpoint: Option<String>,
123 /// RFC 8628 `device_authorization_endpoint`. `None` derives `{issuer}/device_authorization`.
124 pub device_authorization_endpoint: Option<String>,
125 /// RFC 7662 `introspection_endpoint`. `None` derives `{issuer}/introspect`.
126 pub introspection_endpoint: Option<String>,
127 /// RFC 7009 `revocation_endpoint`. `None` derives `{issuer}/revoke`.
128 pub revocation_endpoint: Option<String>,
129 /// RFC 8414 `jwks_uri`. `None` (the default) means this server publishes no keys, which is
130 /// the truth for opaque access tokens.
131 pub jwks_uri: Option<String>,
132 /// RFC 7591 dynamic client registration. `None` is the DEFAULT and means registration is OFF:
133 /// no `registration_endpoint` is advertised, no route is served, and
134 /// [`AuthorizationServer::register_dynamic_client`] answers
135 /// [`crate::registration::RegistrationFailure::Disabled`].
136 ///
137 /// Turning it on is meant to be a sentence somebody wrote and a reviewer can find:
138 /// `config.registration = Some(Box::new(RegistrationConfig::new()))`. RFC 7591 section 5 is
139 /// why (an open registration endpoint lets anyone mint a client), and enabling it is still not
140 /// sufficient: a [`crate::registration::RegistrationPolicy`] must also be installed or every
141 /// registration is refused. See the [`crate::registration`] module docs.
142 ///
143 /// BOXED so that the overwhelmingly common `None` costs one null pointer on every
144 /// [`ServerConfig`] rather than the whole struct, and allocates nothing.
145 pub registration: Option<Box<crate::registration::RegistrationConfig>>,
146 /// RFC 9126 pushed authorization requests. `None` is the DEFAULT and means PAR is OFF: no
147 /// `pushed_authorization_request_endpoint` is advertised and
148 /// [`AuthorizationServer::pushed_authorization_request`] refuses.
149 ///
150 /// BOXED for the same reason as [`ServerConfig::registration`]: the overwhelmingly common
151 /// `None` costs one null pointer on every [`ServerConfig`] rather than the whole struct, and
152 /// allocates nothing.
153 #[cfg(feature = "par")]
154 pub par: Option<Box<crate::par::ParConfig>>,
155 /// RFC 9101 signed request objects. `None` is the DEFAULT and means JAR is OFF: a `request`
156 /// parameter is answered with `request_not_supported` rather than parsed.
157 #[cfg(feature = "jar")]
158 pub jar: Option<Box<crate::par::JarConfig>>,
159 /// draft-ietf-oauth-client-id-metadata-document-01 client identifier metadata documents. `None`
160 /// is the DEFAULT and means the mechanism is OFF: the RFC 8414 document says
161 /// `client_id_metadata_document_supported: false`.
162 ///
163 /// SETTING IT IS A CLAIM ABOUT THE HOST, not about this crate, and that is why it is a config
164 /// field rather than a constant derived from the cargo feature. This crate performs no fetch
165 /// (see [`crate::cimd`]), so compiling the feature in proves only that the VALIDATOR is
166 /// available; whether this deployment actually dereferences a client identifier URL is
167 /// something only the host knows. Deriving the advertised member from the feature would
168 /// publish a capability the build might always refuse, which is a defect shape this crate has
169 /// already shipped twice.
170 ///
171 /// BOXED for the same reason as [`ServerConfig::registration`].
172 #[cfg(feature = "cimd")]
173 pub cimd: Option<Box<crate::cimd::CimdPolicy>>,
174 /// RFC 8414 `scopes_supported`. `None` omits the member rather than claiming an empty
175 /// catalogue.
176 pub scopes_supported: Option<Vec<String>>,
177 /// The RFC 8707 resource indicators this server is WILLING to issue tokens for.
178 ///
179 /// `None` (the default) means no restriction, which is the pre-existing behaviour and is why
180 /// it is the default: turning refusal on by default would break every deployment already
181 /// using resource indicators. It is also why `None` is a real risk rather than a
182 /// neutral one, and the risk is worth stating here rather than in a changelog.
183 ///
184 /// `Option<Box<[Box<str>]>>` rather than `Vec<String>` so a host that never sets it pays ONE
185 /// pointer on every `ServerConfig`, not a 24 byte vector header. The list is written once at
186 /// construction and only ever iterated.
187 ///
188 /// RFC 8707 section 2 requires `invalid_target` when the server "is unwilling or unable to
189 /// issue an access token" for a requested resource. With this empty, the server has no notion
190 /// of unwilling: any syntactically valid absolute URI is accepted. Under the `jwt` feature the
191 /// requested resource then REPLACES the configured audience in the RFC 9068 `aud` claim, so
192 /// any client can obtain a token this server signed, carrying another resource server's
193 /// identifier in `aud`. That server verifies the signature against our JWKS, sees its own
194 /// identifier, and authorises.
195 ///
196 /// A deployment serving more than one resource server should set this.
197 pub allowed_resources: Option<Box<[Box<str>]>>,
198 /// Which registered clients are RESOURCE SERVERS, and which RFC 8707 resource identifiers each
199 /// one answers for. This is what opens the channel RFC 7662 section 1 describes, in which the
200 /// specification "allows authorized protected resources to query the authorization server";
201 /// empty (the default) means this server introspects for
202 /// the token's own client and nobody else, which is what it did through 0.9.1.
203 ///
204 /// A resource server is NOT a new kind of principal. It registers as an ordinary confidential
205 /// [`crate::Client`] and authenticates to the introspection endpoint with whatever this build
206 /// accepts from any client -- `client_secret_basic`, `client_secret_post`, RFC 7523
207 /// `private_key_jwt` or `client_secret_jwt`, RFC 8705 mutual TLS -- through the same
208 /// `authenticate_client` every other endpoint uses. Section 2.1 requires the endpoint be
209 /// protected; reusing the client credential machinery is how it is protected, and inventing a
210 /// second credential type would have meant a second thing to get constant-time comparison,
211 /// rotation and revocation right on.
212 ///
213 /// What this adds on top of authentication is AUTHORIZATION, and that is the part that is not
214 /// optional. Authenticating as a resource server must not mean reading every token in the
215 /// store: an introspection endpoint that answers any authenticated resource server about any
216 /// token is a token-scanning oracle, which is section 4's warning with a credential stapled to
217 /// it. So a resource server is answered about a token ONLY when the token's own
218 /// [`crate::IssuedToken::resource`] set names one of the identifiers registered here.
219 ///
220 /// AN EMPTY `resource` ON THE TOKEN NAMES NOBODY, AND IS REFUSED TO EVERY RESOURCE SERVER.
221 /// That is the same fail-open reading [`crate::jwt::Audience::names_a_resource_server`] exists
222 /// to refuse, arriving through the other door: a grant that requested no resource indicator is
223 /// restricted to nothing in particular, and reading "restricted to nothing in particular" as
224 /// "so anyone may ask about it" would hand every resource server in the deployment every token
225 /// that did not happen to use RFC 8707. The token's own client can still introspect it, which
226 /// is the pre-0.9.2 behaviour and is unchanged.
227 ///
228 /// # WHAT THIS COSTS YOUR RATE LIMITER, and what to set
229 ///
230 /// SETTING THIS CHANGES THE TRAFFIC SHAPE AT THE CLIENT-AUTHENTICATION BUDGET, and it is the
231 /// one consequence of registering a resource server that is not visible from anything else on
232 /// this page. An introspection is a client authentication like any other -- that is the whole
233 /// point of the paragraph above -- so it is charged
234 /// [`crate::rate_limit::ATTEMPT_COST`] against
235 /// [`crate::events::Attempt::ClientAuthentication`] keyed on the RESOURCE SERVER's
236 /// `client_id`. Through 0.9.1 that budget only ever carried a client asking about tokens it
237 /// had itself been issued, so its volume tracked issuance. A resource server introspects ONCE
238 /// PER CALL AT THE PROTECTED RESOURCE, at a rate set by that API's own clients.
239 ///
240 /// [`crate::rate_limit::DEFAULT_CLIENT_AUTHENTICATION_CAPACITY`] is 6000 a minute, which is
241 /// 100 a second, and it was derived from a client's token traffic. Left alone it becomes the
242 /// protected resource's request ceiling, per node.
243 ///
244 /// AND IT DOES NOT READ AS A THROTTLE WHEN IT BITES. RFC 7662 introspection over the ceiling
245 /// is refused with a bare `invalid_client`, the same answer a wrong secret gets, because a
246 /// distinct code would tell an attacker they had found a live client id. A resource server
247 /// that fails closed then refuses EVERY request it is handling, and its operator is looking at
248 /// what appears to be a credential problem. The [`crate::events::EventSink`] channel is where
249 /// the two are distinguishable:
250 /// [`crate::events::Event::ClientAuthenticationFailed`] carries
251 /// [`crate::events::ClientAuthFailure::RateLimited`] for a throttle and
252 /// [`crate::events::ClientAuthFailure::SecretMismatch`] for a credential that did not verify.
253 /// A deployment that registers resource servers should install one.
254 ///
255 /// So, two things:
256 ///
257 /// - Size the budget for the API, not for a client:
258 /// [`crate::rate_limit::RateLimitConfig::with_client_authentication_capacity_for`] raises
259 /// ONE registration and leaves every other `client_id` where it was. Raising
260 /// `client_authentication_capacity` globally would also raise how many wrong secrets every
261 /// other registration admits per window, each of which can cost the host an argon2id.
262 /// - CACHE THE INTROSPECTION RESPONSE at the resource server, which RFC 7662 section 4
263 /// recommends and which is the only measure that changes the traffic shape rather than the
264 /// ceiling. It costs a bounded delay before a revocation is observed.
265 ///
266 /// A host that implements [`crate::events::RateLimiter`] itself makes the same decision in its
267 /// own terms; there is no introspection-specific [`crate::events::Attempt`] variant to key on,
268 /// deliberately, and the module docs on [`crate::rate_limit`] say why.
269 ///
270 /// `Option<Box<[_]>>` rather than `Vec<_>` for the reason [`ServerConfig::allowed_resources`]
271 /// gives next door and with the same measurement behind it: the list is written once at
272 /// construction and only ever iterated, so the growable shape buys nothing, and a boxed slice
273 /// is 16 bytes against a vector header's 24 on every `ServerConfig` in every deployment.
274 /// MEASURED: `ServerConfig` 464 before this field, 488 as a `Vec`, 480 as this.
275 pub resource_servers: Option<Box<[ResourceServerRegistration]>>,
276 /// RFC 8414 `service_documentation`.
277 pub service_documentation: Option<String>,
278 /// RFC 9396 section 10 `authorization_details_types_supported`: the authorization
279 /// details types this deployment actually implements.
280 ///
281 /// `None` is the DEFAULT and means NO type is supported, so every `authorization_details`
282 /// request is refused with `invalid_authorization_details`. That is not conservatism for
283 /// its own sake, it is section 5: "The AS MUST refuse to process any unknown
284 /// authorization details type", and a server that has been told nothing about a type
285 /// cannot be said to know it. Compiling the `rar` feature in is therefore not the same
286 /// as turning it on; a host turns it on by naming its types here.
287 #[cfg(feature = "rar")]
288 pub authorization_details_types_supported: Option<Vec<String>>,
289 /// RFC 9728 section 4 `protected_resources`: the resource identifiers of the protected
290 /// resources this AS issues tokens for. `None` (the default) omits the member; see
291 /// [`crate::metadata::AuthorizationServerMetadata::protected_resources`], and note that
292 /// this is the AS half only. The DOCUMENT each of those resources publishes is
293 /// [`crate::resource_metadata::ProtectedResourceMetadata`], and publishing it is the
294 /// resource's own job, not this server's.
295 #[cfg(feature = "resource-metadata")]
296 pub protected_resources: Option<Vec<String>>,
297 /// What the client receives as its `access_token`. Defaults to [`AccessTokenFormat::Opaque`],
298 /// which is the behaviour of this crate without the `jwt` feature; setting
299 /// [`AccessTokenFormat::Jwt`] makes the wire token an RFC 9068 `at+jwt` while the AS-side
300 /// record is still persisted, so introspection and revocation are unchanged.
301 #[cfg(feature = "jwt")]
302 pub access_token_format: AccessTokenFormat,
303 /// Authorization code lifetime. RFC 6749 section 4.1.2 recommends a maximum of 10 minutes;
304 /// the default is 60 seconds, which is ample for a redirect round trip.
305 pub authorization_code_ttl: Duration,
306 /// Whether device authorization responses include `verification_uri_complete`
307 /// (`{verification_uri}?user_code={code}`). `false` by default.
308 ///
309 /// RFC 8628 section 5.4 (Remote Phishing) is why this is a decision and not a convenience
310 /// setting. The attack is that an attacker starts a device grant for their OWN client and mails
311 /// the victim the link ("click here to finish setting up your TV"); the victim, already signed
312 /// in, lands on a page that needs one click, and the attacker collects the tokens. Section 5.4
313 /// names TYPING THE CODE as the friction that makes this hard, and this member is precisely the
314 /// removal of that friction: the code arrives pre-filled from a URL the user did not compose.
315 ///
316 /// OFF by default as of 0.9.1, having been on. Section 3.3.1 makes the member OPTIONAL, so
317 /// omitting it is conformant and costs a deployment only the QR-code convenience, while
318 /// including it by default made every host that never read this paragraph pay for a capability
319 /// it did not ask for. That is the same posture the rest of this config takes:
320 /// [`ServerConfig::registration`] and the PAR and JAR blocks are all off until a host says
321 /// otherwise. A host that turns this ON should pair it with a verification page that names the
322 /// client and the scope and requires an affirmative action, which is what section 3.3 asks for
323 /// and what the `http` feature's page does.
324 pub include_verification_uri_complete: bool,
325 /// Device code and user code lifetime. Default 600 seconds.
326 pub device_code_ttl: Duration,
327 /// Initial minimum poll spacing (RFC 8628 `interval`). Default 5 seconds.
328 pub poll_interval: Duration,
329 /// How much a `slow_down` raises the required spacing. RFC 8628 section 3.5 mandates the
330 /// client add 5 seconds, which is the default.
331 pub slow_down_increment: Duration,
332 /// Access token lifetime. Default 3600 seconds.
333 pub access_token_ttl: Duration,
334 /// Whether user-approved grants also issue a refresh token. Default true.
335 pub issue_refresh_tokens: bool,
336 /// RFC 8693: whether a SENDER-CONSTRAINED subject token may be exchanged.
337 ///
338 /// `false` by default, which means the exchange is REFUSED with `invalid_request` when the
339 /// subject token carries an RFC 9449 DPoP or RFC 8705 mutual-TLS binding. See the "A
340 /// SENDER-CONSTRAINED subject token is REFUSED" section of the [`crate::token_exchange`] module
341 /// docs for the full argument; the short form is that the token this server would hand back
342 /// belongs to the EXCHANGING client, which does not hold the original client's key, so it can
343 /// only be a plain bearer token. Anyone able to authenticate as any client registered for this
344 /// grant could then post a stolen bound token and receive a spendable one, and the property the
345 /// deployment turned DPoP on to buy would be gone.
346 ///
347 /// # What turning it on gives up
348 ///
349 /// Exactly that property, and it is worth being blunt about it: with `true`, a sender-
350 /// constrained token becomes exchangeable for an UNBOUND bearer token, so a leaked bound token
351 /// is once again worth something to whoever finds it, via one request to this endpoint. The
352 /// binding is not propagated (a `cnf` naming a key the new holder cannot prove would be a
353 /// broken grant dressed as a secure one), it is DROPPED, and nothing downstream is told.
354 ///
355 /// It exists because 0.9.0 and earlier did exactly this silently, so a deployment that has
356 /// already built on the downgrade needs a way to keep running while it migrates. It is not a
357 /// tuning knob: a host that sets it has decided that its delegation topology is trusted enough
358 /// to hold the binding for it, and that decision belongs in a sentence somebody wrote and a
359 /// reviewer can find.
360 pub allow_sender_constrained_exchange: bool,
361 /// Allow an RFC 8693 exchange of a subject token that carries RFC 9396 `authorization_details`,
362 /// propagating those details onto a token issued to a DIFFERENT client. `false` by default, and
363 /// the default is the safe one.
364 ///
365 /// # Why this is off
366 ///
367 /// The exchange applies TWO ceilings to scope: the subject token's granted scope (RFC 6749
368 /// section 6 narrowing) and then the exchanging client's own
369 /// [`crate::client::Client::allowed_scopes`], because the issued token belongs to a different
370 /// principal. It applied NONE to `authorization_details`, which are strictly more specific:
371 /// RFC 9396 exists precisely because a scope token cannot say "transfer 50 euros to IBAN X".
372 ///
373 /// So the weaker rule was applied to the more dangerous grant. A downstream service registered
374 /// for `read`, which receives a payments client's token because forwarding the caller's token
375 /// is exactly what this grant is for, could exchange it asking for `read`, pass both scope
376 /// ceilings, and walk away with a token issued to ITSELF carrying the full payment
377 /// authorization, signed into the RFC 9068 claim, visible over RFC 7662 introspection, and with
378 /// a fresh lifetime that outlives the token it came from.
379 ///
380 /// # Why an opt-in rather than a per-client ceiling
381 ///
382 /// The correct ceiling is a per-client registration of the detail types a client may hold, the
383 /// analogue of `allowed_scopes`. [`crate::client::Client`] has no such field, and adding one is
384 /// a breaking change to a type hosts construct. Until it exists there is nothing to narrow
385 /// against, so the honest choice is to refuse and let a host that has reasoned about its own
386 /// delegation topology say so. Setting this to `true` accepts that any client permitted this
387 /// grant may inherit any detail any subject token carries.
388 pub allow_authorization_details_exchange: bool,
389 /// Absolute refresh chain lifetime. Rotation preserves the chain's original expiry rather than
390 /// sliding it, so this is a ceiling on the whole chain and not on one token.
391 ///
392 /// `None` (the default) means NO TIME EXPIRY AT ALL: a refresh chain established once lives
393 /// until something revokes it. `None` is the default because it is the pre-existing behaviour
394 /// and turning expiry on by default would sign every deployment's users out on an interval
395 /// nobody chose, so, exactly as with [`ServerConfig::allowed_resources`], `None` is a real risk
396 /// rather than a neutral one and the risk belongs here rather than in a changelog.
397 ///
398 /// What it costs: a refresh token exfiltrated once is a credential for the user's account
399 /// FOREVER, and rotation does not fix that. RFC 9700 section 4.14.2 reuse detection catches the
400 /// thief only if the legitimate client comes back and presents the token the thief already
401 /// spent; a thief who steals a chain the user has abandoned, or who simply rotates it faster
402 /// than the real client does, is never detected by anything and holds access indefinitely.
403 /// Setting this bounds that to a window, and OAuth 2.1 draft section 6.1 asks for either a
404 /// finite lifetime or rotation on the same grounds.
405 ///
406 /// A deployment whose users are humans, and whose refresh tokens sit on devices those humans
407 /// lose, should set this.
408 pub refresh_token_ttl: Option<Duration>,
409 /// How long a ROTATED (spent) refresh token is retained purely so that its reuse can be
410 /// detected, when its chain has no absolute expiry of its own. Default 30 days.
411 ///
412 /// Reuse detection (OAuth 2.1 draft section 6.1, RFC 9700 section 4.14.2) only works while the
413 /// superseded token is still recognisable, so this is the window in which a stolen-and-rotated
414 /// token still triggers revocation of its family. Past it the record is sweepable and a
415 /// presentation reads as an unknown token. When the chain HAS an absolute expiry, that expiry
416 /// is used instead: there is nothing left to protect once the chain itself is dead.
417 pub refresh_reuse_window: Duration,
418 /// RFC 9449: whether EVERY token request must carry a DPoP proof.
419 ///
420 /// `false` by default, which means "DPoP is available, and a client that wants a
421 /// sender-constrained token asks for one by presenting a proof". `true` is the FAPI 2.0
422 /// posture: it refuses every token request without a proof, which is a breaking change for
423 /// every existing client of the deployment and therefore a sentence somebody has to write on
424 /// purpose rather than a default anybody inherits.
425 #[cfg(feature = "dpop")]
426 pub require_dpop: bool,
427 /// User code length in symbols, excluding the display hyphen. Default
428 /// [`MIN_USER_CODE_LENGTH`] (about 34 bits over the 20-symbol alphabet, the RFC 8628 section
429 /// 6.1 example shape).
430 ///
431 /// Values below [`MIN_USER_CODE_LENGTH`] are CLAMPED UP at generation, not honoured. This is
432 /// not tuning: 4 symbols is about 160,000 possibilities, which is seconds of guessing against
433 /// an endpoint this library cannot rate limit, and 0 produces an empty code that every grant
434 /// collides on. Clamping rather than rejecting keeps a misconfiguration from becoming a
435 /// runtime failure at the one moment a user is standing in front of a device.
436 pub user_code_length: usize,
437}
438
439/// The floor [`ServerConfig::user_code_length`] is clamped up to: the RFC 8628 section 6.1 example
440/// shape, about 34 bits over the 20-symbol alphabet.
441///
442/// Section 6.1 is explicit that this entropy is adequate only IN COMBINATION WITH rate limiting of
443/// user-code entry. This library performs none and cannot: it never sees a request, only the host
444/// does. See [`AuthorizationServer::approve_device`].
445pub const MIN_USER_CODE_LENGTH: usize = 8;
446
447/// The largest number of RFC 8707 `resource` indicators one request may carry.
448///
449/// # Why there is a cap at all
450///
451/// Section 2 makes `resource` a REPEATABLE parameter, and it is accepted at the authorization
452/// endpoint, which takes no client credential: an unauthenticated caller chooses `n`. Validation is
453/// O(n) per element against [`ServerConfig::allowed_resources`] plus an O(n) dedup scan, so the
454/// cost is quadratic in a number the caller picks, and it is paid before anything has authenticated
455/// anybody. This is the same argument, and the same remedy, as
456/// [`crate::rar::MAX_AUTHORIZATION_DETAILS_ELEMENTS`]: the other repeatable array a request can
457/// carry. Leaving one capped and the other not was the defect.
458///
459/// # Why 16
460///
461/// It is the number of DISTINCT resource servers one access token may be good at. RFC 8707's own
462/// security considerations push the other way, toward narrow audiences, and a deployment that
463/// genuinely needs one token accepted at more than sixteen separate resource servers has an
464/// audience so wide that the indicator has stopped restricting anything. Sixteen is also what
465/// RFC 9396 already allows for `authorization_details`, so the two arrays a request can repeat are
466/// bounded alike and a reader does not have to hold two numbers.
467///
468/// # Why the scan stayed linear
469///
470/// At sixteen, a `HashSet<String>` is slower, not faster: it pays a SipHash of the whole string per
471/// lookup against at most sixteen comparisons that mostly fail on the length. The defect was never
472/// the scan, it was that nothing bounded `n`. A cap is the entire fix.
473///
474/// # It refuses, it does not truncate
475///
476/// Silently dropping the indicators past the cap would issue a token whose audience is not the one
477/// the client asked for, with nothing told to anybody: the exact failure `crate::rar` refuses for
478/// unknown members. `invalid_target` (section 2) is the honest answer.
479pub const MAX_RESOURCE_INDICATORS: usize = 16;
480
481/// Which of RFC 7662's two legitimate callers an introspection request is being answered as.
482///
483/// Not public: it is the shape of one decision inside
484/// [`AuthorizationServer::introspection_response_with_credential`], and a host that could name it
485/// would be able to depend on a split that exists to serve the response document rather than to
486/// describe the deployment.
487enum IntrospectionView {
488 /// The client the token was issued to. Sees the whole record, including every resource
489 /// identifier the grant was restricted to.
490 OwningClient,
491 /// A registered resource server, carrying the identifiers of ITS OWN that this token names.
492 /// Never empty: an empty intersection is not a resource-server view, it is `active: false`.
493 ResourceServer(Vec<String>),
494}
495
496/// The RFC 9396 details a RESOURCE SERVER may be told about, given the identifiers `mine` it is
497/// registered for and answers for.
498///
499/// Section 9.2: the details are "filtered and extended for the RS making the introspection
500/// request". This is the filtering half; this crate does no extending, because an extension would
501/// be an assertion about an API vocabulary it does not know (see [`crate::rar`] on `other`).
502///
503/// The rule, and why each arm is the safe one:
504///
505/// - an element with NO `locations` is KEPT. Section 2.2 makes the member optional, so its absence
506/// says nothing about where the element belongs; dropping it would withhold a detail the resource
507/// owner did approve from the only party in a position to enforce it, which is a fail-open move
508/// dressed as a privacy one.
509/// - an element whose `locations` names one of `mine` is kept, with its `locations` REDUCED to the
510/// intersection. Keeping the element whole would let a detail addressed to two resource servers
511/// hand each of them the other's URI, which is the disclosure this function exists to stop
512/// arriving one level down.
513/// - anything else is DROPPED: its `locations` names other services only, and a resource server
514/// that is not named in an element has no business acting on it or knowing it exists.
515///
516/// The reduction cannot produce an element with an EMPTY `locations`, because an empty intersection
517/// is the dropped arm. That matters beyond tidiness: empty is how this crate spells "the member was
518/// absent", so an element narrowed to nothing would read on the wire as one that was never located
519/// at all -- a widening performed by a filter.
520///
521/// Filtering everything away yields an EMPTY set, and
522/// [`crate::token::IntrospectionResponse::authorization_details`] omits the member rather than
523/// serializing `[]`, so the resource server sees exactly what it sees for a grant that carried no
524/// details at all. That is the "not granted" / "not for you" indistinguishability at its widest,
525/// and it is the intended shape: see this method's caller for why that direction is the harmless
526/// one.
527#[cfg(feature = "rar")]
528fn details_for_resource_server(
529 details: &crate::rar::AuthorizationDetails,
530 mine: &[String],
531) -> crate::rar::AuthorizationDetails {
532 crate::rar::AuthorizationDetails::from_elements(
533 details
534 .iter()
535 .filter_map(|detail| {
536 if detail.locations.is_empty() {
537 return Some(detail.clone());
538 }
539 let locations: Vec<Box<str>> = detail
540 .locations
541 .iter()
542 .filter(|at| mine.iter().any(|id| id.as_str() == &***at))
543 .cloned()
544 .collect();
545 (!locations.is_empty()).then(|| crate::rar::AuthorizationDetail {
546 locations: locations.into_boxed_slice(),
547 ..detail.clone()
548 })
549 })
550 .collect(),
551 )
552}
553
554/// One resource server, as [`ServerConfig::resource_servers`] declares it: the registered client
555/// identity it authenticates as, and the RFC 8707 resource identifiers it is the protected
556/// resource FOR.
557///
558/// The two halves answer two different questions and neither substitutes for the other.
559/// `client_id` answers "who is calling", and it is checked by the ordinary client authentication
560/// every endpoint uses, so a resource server needs a real credential and gets constant-time
561/// verification, rotation and revocation for free. `resources` answers "what may it ask about",
562/// and it is checked against the token's own [`crate::IssuedToken::resource`] set, so a resource
563/// server is told about tokens addressed to it and is told `{"active": false}` about every other
564/// token in the store.
565///
566/// Registering the same `client_id` twice is not an error and not special: the identifier sets are
567/// considered in order and a match in any of them is a match. It is simply a longer way of writing
568/// one entry with both lists concatenated.
569#[derive(Debug, Clone, PartialEq, Eq)]
570/// `#[non_exhaustive]`: this is a DEPLOYMENT POLICY object for a channel that will grow. A per-RS
571/// claim filter, a per-RS introspection policy and a `token_endpoint_auth_method` constraint are
572/// all plausible next fields, and each one would be a major-version event if a host could write a
573/// struct literal here. Its sibling [`crate::cimd::CimdPolicy`] is sealed for the same reason and
574/// states it plainly: "A host writes a full struct literal today and has a build that breaks on a
575/// patch release; `new()` plus assignment does not. The attribute cannot be added after
576/// publication, because by then the literal is in somebody's production tree."
577///
578/// It is sealed HERE rather than later because 0.9.2 is the release that introduces it. The
579/// `tests/host_api_shape.rs` gate does not catch this one and is not wrong to miss it -- that scan
580/// flags types whose field set VARIES WITH A CARGO FEATURE, and this one's does not. The rule the
581/// crate actually follows is broader than the gate that enforces part of it.
582#[non_exhaustive]
583pub struct ResourceServerRegistration {
584 /// The registered client this resource server authenticates as. It must be a CONFIDENTIAL
585 /// client: introspection refuses public clients (see
586 /// [`AuthorizationServer::introspection_response_with_credential`]), and naming a public client
587 /// here therefore registers a resource server that can never successfully call.
588 pub client_id: ClientId,
589 /// The RFC 8707 resource identifiers this server is the protected resource for. An entry with
590 /// an EMPTY list can never match any token, because matching requires naming an identifier the
591 /// token carries; it registers a resource server with no authority rather than one with
592 /// universal authority, which is the fail-closed direction.
593 pub resources: Vec<String>,
594}
595
596impl ResourceServerRegistration {
597 /// Declare `client_id` to be the resource server for `resources`.
598 pub fn new(
599 client_id: ClientId,
600 resources: impl IntoIterator<Item = impl Into<String>>,
601 ) -> Self {
602 Self {
603 client_id,
604 resources: resources.into_iter().map(Into::into).collect(),
605 }
606 }
607}
608
609/// The RFC 8693 section 4.1 actor an issuance carries, in a wrapper that is ZERO SIZED without the
610/// `token-exchange` feature.
611///
612/// Exactly the same device as [`GrantedAuthentication`] below and for exactly the same reason: one
613/// `issue` signature in every feature configuration, because a `cfg` on an argument cannot be
614/// matched by a `cfg` at the call site. Only the delegation branch of a token exchange ever fills
615/// it; every other grant passes the default, which is what `None` on the record means.
616#[derive(Default, Clone, PartialEq, Eq)]
617pub(crate) struct GrantedActor {
618 #[cfg(feature = "token-exchange")]
619 pub(crate) act: Option<Box<crate::token_exchange::ActClaim>>,
620}
621
622/// The host-reported authentication an issuance carries, in a wrapper that is ZERO SIZED without
623/// the `consent` feature.
624///
625/// It exists so [`AuthorizationServer::issue`] and its five call sites have ONE signature in every
626/// feature configuration. The alternative, a `cfg` on the argument, cannot be matched by a `cfg` at
627/// the call site, and duplicating five call sites under a `cfg` is five places to get it wrong in
628/// the configuration nobody builds locally. Same reason `Bound` exists for the RFC 9449 binding.
629#[derive(Default, Clone, PartialEq, Eq)]
630pub(crate) struct GrantedAuthentication {
631 #[cfg(feature = "consent")]
632 pub(crate) authentication: Option<Box<crate::consent::Authentication>>,
633}
634
635impl GrantedAuthentication {
636 /// What an authorization code carries into the token it mints.
637 #[cfg(feature = "consent")]
638 pub(crate) fn from_code(record: &AuthorizationCodeRecord) -> Self {
639 GrantedAuthentication {
640 authentication: record.authentication.clone(),
641 }
642 }
643
644 /// Without the feature there is no field to fill, and no field on the record to fill it from.
645 #[cfg(not(feature = "consent"))]
646 pub(crate) fn from_code(_record: &AuthorizationCodeRecord) -> Self {
647 GrantedAuthentication {}
648 }
649
650 /// What a refresh chain carries across a rotation: the ORIGINAL authentication, unchanged. See
651 /// [`crate::token::RefreshTokenRecord::authentication`] on why a rotation is not a new one.
652 #[cfg(feature = "consent")]
653 pub(crate) fn from_refresh(record: &RefreshTokenRecord) -> Self {
654 GrantedAuthentication {
655 authentication: record.authentication.clone(),
656 }
657 }
658
659 /// Without the feature, as above.
660 #[cfg(not(feature = "consent"))]
661 pub(crate) fn from_refresh(_record: &RefreshTokenRecord) -> Self {
662 GrantedAuthentication {}
663 }
664}
665
666/// A statement, by the HOST, that a resource owner saw one validated authorization request and
667/// agreed to it. The only thing [`AuthorizationServer::issue_authorization_code`] will mint from.
668///
669/// # Why this type exists
670///
671/// The `http` feature's `ServiceBuilder` REFUSES TO BUILD without a consent resolver, so a host on
672/// that path cannot reach code issuance without having written the word "approve". The direct API
673/// had no such step, and the direct API is the path this crate's DEFAULT BUILD invites: no HTTP
674/// surface, no listener, the host owning its own routes. `issue_authorization_code(&validated,
675/// "alice")` read like a lookup, compiled, passed the host's own tests, and shipped an
676/// authorization server that approved everything. The refusal existed on one of two supported
677/// adoption paths, which is the same as not existing.
678///
679/// # What it is, and what it is not
680///
681/// It is NOT a proof, and no type this crate could define would be one: this library has no user,
682/// no session and no screen, so "the user agreed" is a fact only the host holds. Nor is it weaker
683/// than the seam it mirrors. A host can wire `|_| ApprovalDecision::Approve` into the `http` path
684/// just as it can call [`UserApproval::granted`] here, so what BOTH seams buy is the same and is
685/// the whole of what a library at this boundary can buy: the approval becomes a sentence the host
686/// WROTE rather than a default it inherited, and the host that never considered RFC 6749 section
687/// 10.12 gets a compile error naming it instead of a working forgery endpoint.
688///
689/// It also closes a bug class the two-argument form left open. The approval BORROWS the request it
690/// approves, so there is no second request parameter left to disagree with it: a host cannot prompt
691/// for one request and issue for another, approving a `read` and minting a `read write`.
692///
693/// # Allocation
694///
695/// A borrow plus the `subject` String, which is the same one allocation the old
696/// `subject: impl Into<String>` argument made on its way into the record. Nothing on the token path
697/// changes, and nothing here is on it.
698pub struct UserApproval<'a> {
699 request: &'a ValidatedAuthorizationRequest,
700 subject: String,
701 /// When the decision this approval reports was MADE, if the host knows.
702 ///
703 /// `None` means "now", which is right for a host that prompted the user during this request
704 /// and wrong for one that is acting on a standing approval it read a moment ago. See
705 /// [`UserApproval::decided_at`] for why the difference is load bearing.
706 decided_at: Option<std::time::SystemTime>,
707}
708
709impl<'a> UserApproval<'a> {
710 /// The resource owner named by `subject` approved `request`.
711 ///
712 /// CALLING THIS IS AN ASSERTION. It says a real user was really asked about really this
713 /// request, in whatever the deployment's consent step is, and said yes. This crate cannot check
714 /// that and will not try; what it can do is refuse to mint anything until someone writes it.
715 ///
716 /// `subject` is the authenticated resource owner, in the host's own vocabulary for users, and
717 /// is what the issued code and every token it mints will carry.
718 pub fn granted(request: &'a ValidatedAuthorizationRequest, subject: impl Into<String>) -> Self {
719 UserApproval {
720 request,
721 subject: subject.into(),
722 decided_at: None,
723 }
724 }
725
726 /// The same approval, dated.
727 ///
728 /// WHAT THIS IS FOR, and it is not bookkeeping. A revocation barrier refuses a write whose
729 /// GRANT predates the revocation, and the grant instant a code carries is the instant of the
730 /// decision it rests on. For a host that prompted the user during this request those are the
731 /// same moment and [`UserApproval::granted`] is right. For a host acting on a STANDING
732 /// approval — a remembered consent it read at the top of the request — they are not, and
733 /// dating a months-old approval at this instant makes it outrank a withdrawal recorded in
734 /// between.
735 ///
736 /// The failure that costs is precise. The user opens the authorization page; the host reads
737 /// their standing consent; the user, elsewhere, clicks "remove this application", and the
738 /// withdrawal cascades away every token and records its barrier; the first request then
739 /// resumes and mints a code on the strength of the pre-withdrawal snapshot. Nothing refuses
740 /// the code — `put_authorization_code` is deliberately barrier-exempt — and at redemption a
741 /// code dated NOW postdates the barrier, so the token is issued and its refresh chain
742 /// inherits the same instant, rotating happily long after the barrier is swept.
743 ///
744 /// Pass the instant the decision was made: the standing record's own `granted_at`, or the
745 /// instant the request was received, whichever the host can honestly claim. Both are earlier
746 /// than any withdrawal that has not yet been read, which is the whole of what is needed.
747 pub fn granted_at(
748 request: &'a ValidatedAuthorizationRequest,
749 subject: impl Into<String>,
750 decided_at: std::time::SystemTime,
751 ) -> Self {
752 UserApproval {
753 request,
754 subject: subject.into(),
755 decided_at: Some(decided_at),
756 }
757 }
758
759 /// When the decision was made, if the host said. See [`UserApproval::granted_at`].
760 pub fn decided_at(&self) -> Option<std::time::SystemTime> {
761 self.decided_at
762 }
763
764 /// The request this approves.
765 pub fn request(&self) -> &'a ValidatedAuthorizationRequest {
766 self.request
767 }
768
769 /// The resource owner who approved it.
770 pub fn subject(&self) -> &str {
771 &self.subject
772 }
773}
774
775/// Hand-written: `subject` is a user identifier, and this crate does not print those into whatever
776/// caught a `{:?}`. Which REQUEST is being approved stays visible, because that is the whole of
777/// what anybody debugging an issuance needs.
778impl fmt::Debug for UserApproval<'_> {
779 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
780 f.debug_struct("UserApproval")
781 .field("client_id", &self.request.client_id)
782 .field("scope", &self.request.scope)
783 .field("subject", &"[redacted]")
784 .finish()
785 }
786}
787
788/// How many times user-code generation may redraw on a collision before giving up.
789///
790/// A collision at the floor length is a roughly one-in-a-hundred-billion event per live grant, so
791/// a run of this many is not chance: it is a store that is full, broken, or under an allocation
792/// flood. Bounded rather than unbounded because an endpoint that spins forever under load is a
793/// worse failure than one that answers `server_error`.
794const USER_CODE_GENERATION_ATTEMPTS: usize = 8;
795
796impl ServerConfig {
797 /// A config with RFC-shaped defaults; `issuer` and `verification_uri` have no sane default and
798 /// are required.
799 pub fn new(issuer: impl Into<String>, verification_uri: impl Into<String>) -> Self {
800 ServerConfig {
801 issuer: issuer.into(),
802 verification_uri: verification_uri.into(),
803 authorization_endpoint: None,
804 token_endpoint: None,
805 device_authorization_endpoint: None,
806 introspection_endpoint: None,
807 revocation_endpoint: None,
808 jwks_uri: None,
809 // OFF. See the field's own docs, and RFC 7591 section 5.
810 registration: None,
811 // OFF. PAR is a capability a host opts into, not a default: see the field's docs.
812 #[cfg(feature = "par")]
813 par: None,
814 #[cfg(feature = "jar")]
815 jar: None,
816 // OFF, and off means the RFC 8414 document says so: a host that has not wired the
817 // fetch must not advertise that it did. See the field's own docs.
818 #[cfg(feature = "cimd")]
819 cimd: None,
820 scopes_supported: None,
821 allowed_resources: None,
822 resource_servers: None,
823 service_documentation: None,
824 // OFF. An undeclared catalogue supports no types: see the field's own docs and
825 // RFC 9396 section 5.
826 #[cfg(feature = "rar")]
827 authorization_details_types_supported: None,
828 #[cfg(feature = "resource-metadata")]
829 protected_resources: None,
830 #[cfg(feature = "jwt")]
831 access_token_format: AccessTokenFormat::Opaque,
832 authorization_code_ttl: Duration::from_secs(60),
833 // OFF. RFC 8628 s5.4 remote phishing: see the field's own docs.
834 include_verification_uri_complete: false,
835 device_code_ttl: Duration::from_secs(600),
836 poll_interval: Duration::from_secs(5),
837 slow_down_increment: Duration::from_secs(5),
838 access_token_ttl: Duration::from_secs(3600),
839 issue_refresh_tokens: true,
840 // OFF: an exchange that drops a sender constraint is a decision, not a default. See the
841 // field's own docs for what turning it on gives up.
842 allow_sender_constrained_exchange: false,
843 allow_authorization_details_exchange: false,
844 refresh_token_ttl: None,
845 // 30 days: long enough that a chain abandoned by a client that later comes back with
846 // a stale token is still recognised as reuse rather than as noise.
847 refresh_reuse_window: Duration::from_secs(30 * 24 * 60 * 60),
848 #[cfg(feature = "dpop")]
849 require_dpop: false,
850 user_code_length: MIN_USER_CODE_LENGTH,
851 }
852 }
853}
854
855/// A parsed token-endpoint request (RFC 6749 section 3.2). The host parses the form body and the
856/// `Authorization` header into this; `client_secret` is `None` for public clients.
857///
858/// `Debug` is hand-written (see below) rather than derived. Every variant of this type is built
859/// directly out of an inbound request and every variant carries at least one credential: RFC 6749
860/// section 2.3.1 makes `client_secret` a password, and section 4.1.2, section 6 and RFC 8628
861/// section 3.4 each make the grant artifact (`code`, `refresh_token`, `device_code`) a bearer
862/// credential in its own right. This is the type a host is most likely to debug-print, since it is
863/// the request it just parsed, so a derived `Debug` here would be the single easiest way to end up
864/// with plaintext credentials in a host's logs.
865#[derive(Clone, PartialEq, Eq)]
866pub enum TokenRequest {
867 /// RFC 6749 section 4.1.3: `grant_type=authorization_code`, with the RFC 7636 `code_verifier`
868 /// that OAuth 2.1 makes mandatory.
869 AuthorizationCode {
870 /// The redeeming client.
871 client_id: ClientId,
872 /// The client secret, when the client is confidential.
873 client_secret: Option<String>,
874 /// The code from the authorization response (single use).
875 code: String,
876 /// The redirect URI the authorization request used; must match exactly.
877 redirect_uri: Option<String>,
878 /// The PKCE verifier for the challenge recorded against the code.
879 code_verifier: Option<String>,
880 },
881 /// RFC 6749 section 4.4: `grant_type=client_credentials`. Confidential clients only, and no
882 /// refresh token is issued (section 4.4.3: the client can simply request another token).
883 ClientCredentials {
884 /// The client acting on its own behalf.
885 client_id: ClientId,
886 /// The client secret. A public client has none, and cannot use this grant.
887 client_secret: Option<String>,
888 /// Optional narrowing scope.
889 scope: Option<ScopeSet>,
890 },
891 /// RFC 8628 section 3.4: `grant_type=urn:ietf:params:oauth:grant-type:device_code`.
892 DeviceCode {
893 /// The polling client.
894 client_id: ClientId,
895 /// The client secret, when the client is confidential.
896 client_secret: Option<String>,
897 /// The `device_code` from the device authorization response.
898 device_code: String,
899 },
900 /// RFC 6749 section 6: `grant_type=refresh_token`, with OAuth 2.1 rotation.
901 RefreshToken {
902 /// The refreshing client.
903 client_id: ClientId,
904 /// The client secret, when the client is confidential.
905 client_secret: Option<String>,
906 /// The refresh token being redeemed (single use).
907 refresh_token: String,
908 /// Optional narrowing scope; widening is `invalid_scope`.
909 scope: Option<ScopeSet>,
910 },
911}
912
913/// Hand-written so no credential reaches a debug format, while everything that identifies WHICH
914/// request this is stays visible: the variant name (so the grant type is readable), `client_id`
915/// (RFC 6749 section 2.2 makes it explicitly not a secret), `redirect_uri` and `scope`.
916///
917/// `client_secret` and `code_verifier` are `Option`s, and the Some/None distinction is kept: it is
918/// not a credential, it is the difference between "a secret was presented" and "none was", which
919/// is exactly what someone debugging an `invalid_client` (RFC 6749 section 5.2) or a missing-PKCE
920/// rejection needs, and it can be read off the request's shape without the value.
921impl fmt::Debug for TokenRequest {
922 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
923 // `Option<&str>` rather than a bare string, so `Some("[redacted]")` / `None` prints and
924 // the presence of the credential stays legible while its value does not.
925 fn redact_opt<T>(value: &Option<T>) -> Option<&'static str> {
926 value.as_ref().map(|_| "[redacted]")
927 }
928 match self {
929 TokenRequest::AuthorizationCode {
930 client_id,
931 client_secret,
932 code: _,
933 redirect_uri,
934 code_verifier,
935 } => f
936 .debug_struct("AuthorizationCode")
937 .field("client_id", client_id)
938 .field("client_secret", &redact_opt(client_secret))
939 .field("code", &"[redacted]")
940 .field("redirect_uri", redirect_uri)
941 .field("code_verifier", &redact_opt(code_verifier))
942 .finish(),
943 TokenRequest::ClientCredentials {
944 client_id,
945 client_secret,
946 scope,
947 } => f
948 .debug_struct("ClientCredentials")
949 .field("client_id", client_id)
950 .field("client_secret", &redact_opt(client_secret))
951 .field("scope", scope)
952 .finish(),
953 TokenRequest::DeviceCode {
954 client_id,
955 client_secret,
956 device_code: _,
957 } => f
958 .debug_struct("DeviceCode")
959 .field("client_id", client_id)
960 .field("client_secret", &redact_opt(client_secret))
961 // RFC 8628 section 3.4 redeems the device code with no further proof from a public
962 // client, so it is as much a bearer credential as an authorization code is.
963 .field("device_code", &"[redacted]")
964 .finish(),
965 TokenRequest::RefreshToken {
966 client_id,
967 client_secret,
968 refresh_token: _,
969 scope,
970 } => f
971 .debug_struct("RefreshToken")
972 .field("client_id", client_id)
973 .field("client_secret", &redact_opt(client_secret))
974 .field("refresh_token", &"[redacted]")
975 .field("scope", scope)
976 .finish(),
977 }
978 }
979}
980
981/// How a client is authenticating on one request.
982///
983/// A value of its own rather than more fields on every [`TokenRequest`] variant, for the same
984/// reason RFC 8707's `resource` is a separate argument: client authentication is a property of the
985/// REQUEST and is identical across every grant, so putting it on each variant would state the same
986/// thing four times, grow an enum every host copies around, and make each future grant repeat it
987/// again.
988///
989/// [`Default`] is a PUBLIC client: no secret, no assertion.
990///
991/// `Debug` is HAND-WRITTEN (below) and does not print the secret or the assertion. It derived one
992/// until 0.9.2, which made the guarantee on `crate::http`'s private `Credentials` -- "DELIBERATELY NOT
993/// `Debug` ... a derived `Debug` would put all of it verbatim into a host's logs the first time
994/// somebody wrote `tracing::debug!(?creds)`" -- last exactly as long as the one function call that
995/// converts the one into the other. And this is the worse of the two to leave open: it is PUBLIC
996/// API, so it is the value a host builds by hand for [`AuthorizationServer::token`], and a host
997/// that never touches `http::Credentials` reaches it anyway.
998#[derive(Clone, Copy, Default, PartialEq, Eq)]
999/// `#[non_exhaustive]`: `client-assertion` adds two fields and `mtls` adds a third, so this is four
1000/// different structs depending on the flag set. Three named constructors already cover the three
1001/// ways a client can authenticate ([`ClientCredential::secret`], [`ClientCredential::assertion`],
1002/// [`ClientCredential::certificate`]), and the RFC 8705 section 4 case of binding a token for a
1003/// client that authenticated some other way is a field assignment on top of one of them, which is
1004/// exactly what that field's own documentation already tells a host to do.
1005#[non_exhaustive]
1006pub struct ClientCredential<'a> {
1007 /// The RFC 6749 section 2.3.1 shared secret, from `Authorization: Basic` or from the form body.
1008 /// `None` for a public client, and `None` when an assertion is presented instead.
1009 pub client_secret: Option<&'a str>,
1010 /// RFC 7521 section 4.2 `client_assertion_type`. It MUST be
1011 /// [`crate::client_assertion::CLIENT_ASSERTION_TYPE`]; any other value is refused rather than
1012 /// ignored, because an assertion format this server does not implement is a credential it
1013 /// cannot check, and "cannot check" must never read as "checked out".
1014 #[cfg(feature = "client-assertion")]
1015 pub client_assertion_type: Option<&'a str>,
1016 /// RFC 7523 section 2.2 `client-assertion`: the signed JWT itself.
1017 #[cfg(feature = "client-assertion")]
1018 pub client_assertion: Option<&'a str>,
1019 /// The RFC 8705 client certificate the HOST has ALREADY VERIFIED for this connection.
1020 ///
1021 /// READ [`crate::mtls`]'s trust boundary section before setting this. This library
1022 /// never sees a socket, so it cannot validate a chain it did not negotiate: a host that
1023 /// fills this in from an unverified source (an unstripped `X-Client-Cert` header, a
1024 /// terminator that requests but does not require a certificate) has authenticated
1025 /// nobody, and every comparison this crate then makes is against a value the caller
1026 /// chose.
1027 ///
1028 /// It does two separate jobs, either of which can apply on its own:
1029 ///
1030 /// - section 2, AUTHENTICATION: a client registered with
1031 /// [`crate::client::ClientAuth::Mtls`] is authenticated by this certificate and by
1032 /// nothing else. Such a client cannot authenticate through a call that leaves this
1033 /// `None`, which is the point: a host that forgets to pass the certificate gets
1034 /// `invalid_client`, never a token.
1035 /// - section 3, BINDING: the issued access token is bound to this certificate whatever
1036 /// the client's authentication method was, including a public client (section 4).
1037 /// Binding is not conditional on a per-client flag, because a bound token is never
1038 /// less safe than the unbound one it replaces, and a client that does not want
1039 /// binding does not present a certificate.
1040 #[cfg(feature = "mtls")]
1041 pub certificate: Option<&'a crate::mtls::ClientCertificate<'a>>,
1042}
1043
1044/// Hand-written so neither the shared secret nor the assertion ever prints, in the same shape
1045/// [`crate::token::TokenResponse`] uses: the `Some`/`None` distinction is KEPT, because WHICH
1046/// credential a request presented is the diagnostic an operator actually needs and is not itself
1047/// secret, while the value is. `client_assertion_type` prints in full: RFC 7521 section 4.2 makes
1048/// it a fixed registered URN, so it identifies the mechanism rather than the holder. The
1049/// certificate prints through its own `Debug`, which is a public document by construction.
1050impl fmt::Debug for ClientCredential<'_> {
1051 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1052 fn redact_opt<T>(value: &Option<T>) -> Option<&'static str> {
1053 value.as_ref().map(|_| "[redacted]")
1054 }
1055 let mut out = f.debug_struct("ClientCredential");
1056 out.field("client_secret", &redact_opt(&self.client_secret));
1057 #[cfg(feature = "client-assertion")]
1058 out.field("client_assertion_type", &self.client_assertion_type);
1059 #[cfg(feature = "client-assertion")]
1060 out.field("client_assertion", &redact_opt(&self.client_assertion));
1061 #[cfg(feature = "mtls")]
1062 out.field("certificate", &self.certificate);
1063 out.finish()
1064 }
1065}
1066
1067impl<'a> ClientCredential<'a> {
1068 /// The credential of a client presenting a shared secret, or of a public client presenting
1069 /// none.
1070 pub fn secret(client_secret: Option<&'a str>) -> Self {
1071 ClientCredential {
1072 client_secret,
1073 #[cfg(feature = "client-assertion")]
1074 client_assertion_type: None,
1075 #[cfg(feature = "client-assertion")]
1076 client_assertion: None,
1077 #[cfg(feature = "mtls")]
1078 certificate: None,
1079 }
1080 }
1081
1082 /// The RFC 7523 credential: the assertion, and the type that names its format.
1083 #[cfg(feature = "client-assertion")]
1084 pub fn assertion(client_assertion_type: Option<&'a str>, client_assertion: &'a str) -> Self {
1085 ClientCredential {
1086 client_secret: None,
1087 client_assertion_type,
1088 client_assertion: Some(client_assertion),
1089 #[cfg(feature = "mtls")]
1090 certificate: None,
1091 }
1092 }
1093
1094 /// The RFC 8705 credential: the client certificate the host verified during the TLS
1095 /// handshake, and no secret at all.
1096 ///
1097 /// For a client that authenticates some OTHER way and still wants its token bound
1098 /// (RFC 8705 section 4, including a public client), set
1099 /// [`ClientCredential::certificate`] on the credential it is already using rather than
1100 /// replacing it with this one.
1101 #[cfg(feature = "mtls")]
1102 pub fn certificate(certificate: &'a crate::mtls::ClientCertificate<'a>) -> Self {
1103 ClientCredential {
1104 certificate: Some(certificate),
1105 ..ClientCredential::secret(None)
1106 }
1107 }
1108
1109 /// Fall back to the secret carried on the [`TokenRequest`] variant when the context named none,
1110 /// so a host may present it either way and neither is silently ignored.
1111 fn or_secret(mut self, secret: Option<&'a str>) -> Self {
1112 if self.client_secret.is_none() {
1113 self.client_secret = secret;
1114 }
1115 self
1116 }
1117}
1118
1119/// Everything about a token request that is not part of the grant itself.
1120///
1121/// Passed by reference to [`AuthorizationServer::token_with_context`]. Growing this struct is
1122/// cheap; growing [`TokenRequest`] is not, because a host copies that around and
1123/// `tests/allocation.rs` holds it to a size budget.
1124#[derive(Debug, Clone, Copy, Default)]
1125/// `#[non_exhaustive]`: `rar` and `dpop` each add a field, and this is the type a host assembles on
1126/// EVERY token request, so it is the single most likely struct literal in a host's codebase and the
1127/// most expensive one to break. "Growing this struct is cheap" above is only true while growing it
1128/// is not a semver-major change, which is what the attribute buys.
1129///
1130/// Build it with [`TokenRequestContext::new`] and assign what the request carried; `Default` is
1131/// still there for a request with no credential at all, though the credential is the one thing
1132/// every request has an answer for, which is why it is the constructor's only argument.
1133#[non_exhaustive]
1134pub struct TokenRequestContext<'a> {
1135 /// How the client is authenticating.
1136 pub credential: ClientCredential<'a>,
1137 /// The RFC 8707 `resource` parameters, in wire order.
1138 pub resources: &'a [String],
1139 /// The RFC 9396 `authorization_details` parameter, raw and unparsed.
1140 ///
1141 /// Here rather than on each [`TokenRequest`] variant for the reason `resources` is
1142 /// here: section 6 defines it as a parameter of the token REQUEST, independent of
1143 /// `grant_type`. What it MEANS does depend on the grant, and section 6 is what decides:
1144 /// `authorization_code` and `refresh_token` may narrow what the authorization request
1145 /// obtained and never widen it; `client_credentials` has no prior authorization request,
1146 /// so its details are checked against the supported types and used; and the device grant
1147 /// refuses any at all, because the RFC 8628 section 3.1 request cannot carry them in
1148 /// this crate and so granted nothing for a poll to narrow to.
1149 ///
1150 /// NOT FEATURE GATED, for the reason
1151 /// [`crate::authorization::AuthorizationRequest::authorization_details`] is not: a build
1152 /// without `rar` still has to be TOLD the parameter arrived, because refusing it is what
1153 /// RFC 9396 section 5 requires of exactly that build. Setting it in such a build makes the
1154 /// request an error rather than making the field meaningless.
1155 pub authorization_details: Option<&'a str>,
1156 /// The RFC 9449 `DPoP` request header, verbatim and unparsed.
1157 ///
1158 /// `None` means the client sent none, which is refused only when
1159 /// [`ServerConfig::require_dpop`] is set. When it is present and valid, the issued token is
1160 /// BOUND to the proof's key: `token_type` becomes `DPoP` and RFC 7662 introspection reports
1161 /// `cnf.jkt`.
1162 #[cfg(feature = "dpop")]
1163 pub dpop_proof: Option<&'a str>,
1164}
1165
1166impl<'a> TokenRequestContext<'a> {
1167 /// The context of a request that carried nothing but its client authentication, which is every
1168 /// request in a deployment that has enabled none of the parameters the other fields exist for.
1169 ///
1170 /// The RFC 8707 `resource` list, the RFC 9396 `authorization_details` and the RFC 9449 `DPoP`
1171 /// header are public fields on the returned value, so a host's token endpoint reads as the
1172 /// sequence of parameters it actually found on the wire.
1173 pub fn new(credential: ClientCredential<'a>) -> Self {
1174 TokenRequestContext {
1175 credential,
1176 resources: &[],
1177 authorization_details: None,
1178 #[cfg(feature = "dpop")]
1179 dpop_proof: None,
1180 }
1181 }
1182
1183 /// The RFC 8707 `resource` parameters the request carried, in wire order.
1184 pub fn with_resources(mut self, resources: &'a [String]) -> Self {
1185 self.resources = resources;
1186 self
1187 }
1188
1189 /// The RFC 9396 `authorization_details` parameter, raw and unparsed. Available in every
1190 /// build: without `rar` what it buys is a REFUSAL rather than a grant, which is what RFC 9396
1191 /// section 5 asks of a server that supports no detail type.
1192 pub fn with_authorization_details(mut self, authorization_details: &'a str) -> Self {
1193 self.authorization_details = Some(authorization_details);
1194 self
1195 }
1196
1197 /// The RFC 9449 `DPoP` request header, verbatim.
1198 #[cfg(feature = "dpop")]
1199 pub fn with_dpop_proof(mut self, dpop_proof: &'a str) -> Self {
1200 self.dpop_proof = Some(dpop_proof);
1201 self
1202 }
1203}
1204
1205/// What each grant helper needs about the REQUEST rather than about the grant.
1206///
1207/// One reference wide at every call site, which is actually SMALLER than the `Option<&str>` client
1208/// secret it replaces there. That is not incidental: these helpers are the token future, and
1209/// `tests/allocation.rs` fails if that future crosses tokio's 2048-byte debug boxing threshold.
1210pub(crate) struct Bound<'a> {
1211 /// The credential to authenticate with.
1212 pub(crate) cred: ClientCredential<'a>,
1213 /// The RFC 9449 section 6.1 thumbprint the issued token must be bound to, when the request
1214 /// carried a valid proof.
1215 #[cfg(feature = "dpop")]
1216 pub(crate) jkt: Option<&'a str>,
1217}
1218
1219impl<'a> Bound<'a> {
1220 /// A request authenticating with a shared secret and asking for no RFC 9449 binding.
1221 ///
1222 /// For the grant surfaces that reach `issue` from outside this module (RFC 8693 token
1223 /// exchange). They get an unbound token, which is honest: they have not been given a proof to
1224 /// bind one to. Wiring DPoP into them is a matter of threading a `Bound` in, not of changing
1225 /// anything here.
1226 ///
1227 /// `dead_code` because its only caller is behind another slice's cargo feature, and gating it
1228 /// on that feature by name would tie this module to a flag it has no other business knowing.
1229 #[allow(dead_code)]
1230 pub(crate) fn secret(client_secret: Option<&'a str>) -> Self {
1231 Bound {
1232 cred: ClientCredential::secret(client_secret),
1233 #[cfg(feature = "dpop")]
1234 jkt: None,
1235 }
1236 }
1237}
1238
1239/// Rejections for the host-driven verification-UI actions ([`AuthorizationServer::approve_device`]
1240/// / [`AuthorizationServer::deny_device`]). These are NOT wire errors: the RFC leaves the
1241/// verification interaction to the implementation, and the host renders these however its UI
1242/// wants.
1243///
1244/// `#[non_exhaustive]`, for the reason `lib.rs` gives for re-exporting it at all: a host's
1245/// verification UI is expected to MATCH on this, so a later release that has a new way to refuse an
1246/// entered code must be able to say so without that being a semver-major change for every host.
1247/// Every other host-facing failure enum in this crate carries the same attribute
1248/// ([`crate::registration::RegistrationFailure`], [`crate::events::ClientAuthFailure`],
1249/// [`crate::consent::AuthenticationRequirement`]); this one was the exception, and there was no argument
1250/// for the exception.
1251#[derive(Debug, Clone, PartialEq, Eq)]
1252#[non_exhaustive]
1253pub enum DeviceApprovalError {
1254 /// No live grant matches the entered code.
1255 UnknownUserCode,
1256 /// The grant existed but its lifetime has passed.
1257 Expired,
1258 /// The grant was already approved or denied.
1259 NotPending,
1260 /// The host's own [`crate::events::RateLimiter`] refused the attempt before the code was
1261 /// looked up at all. RFC 8628 section 5.1 makes throttling user-code entry a REQUIREMENT of
1262 /// the deployment, not an optimisation, so this is a first-class answer and not an error.
1263 RateLimited,
1264 /// The storage seam failed.
1265 Storage(StorageError),
1266}
1267
1268impl std::fmt::Display for DeviceApprovalError {
1269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270 match self {
1271 DeviceApprovalError::UnknownUserCode => f.write_str("unknown user code"),
1272 DeviceApprovalError::Expired => f.write_str("the code has expired"),
1273 DeviceApprovalError::NotPending => f.write_str("the code was already used"),
1274 DeviceApprovalError::RateLimited => f.write_str("too many attempts"),
1275 DeviceApprovalError::Storage(e) => write!(f, "{e}"),
1276 }
1277 }
1278}
1279
1280impl std::error::Error for DeviceApprovalError {}
1281
1282/// The RFC 8628 section 6.1 example alphabet: 20 consonants, chosen upstream to avoid vowels
1283/// (accidental words) and easily confused symbols.
1284const USER_CODE_ALPHABET: &[u8; 20] = b"BCDFGHJKLMNPQRSTVWXZ";
1285
1286/// Fresh OS randomness, hex encoded: `n` bytes of entropy, `2n` characters. Used for device codes
1287/// and tokens; 32 bytes = 256 bits, far past any brute-force horizon for a 10-minute artifact.
1288///
1289/// `None` means the OS would not give this process randomness. That is a real runtime condition —
1290/// an exhausted file descriptor table on the platforms where `getrandom` opens `/dev/urandom`, a
1291/// seccomp policy, a container without the syscall — and it is a condition every OTHER fallible
1292/// operation on these paths reports as [`ErrorCode::ServerError`] and returns from. Panicking
1293/// instead means a library aborting the HOST's request handler, and in a host built with
1294/// `panic = "abort"` it means taking the whole process down: an authorization server that stops
1295/// serving the requests it COULD still serve because one of them could not be given 32 bytes.
1296///
1297/// THERE IS NO PANICKING FORM ANY MORE, and its removal is the point. A `random_hex` that
1298/// `expect`ed survived here through 0.9.0 with a doc claiming its remaining call sites were
1299/// "outside the request path" — `crate::registration`'s minting and `crate::par`'s `request_uri`.
1300/// Both halves were false by the time the doc was read: PAR had already moved to this function,
1301/// and RFC 7591 dynamic registration is an ordinary unauthenticated `POST /register` route. A
1302/// function that cannot be called is the only reliable way to keep that from happening again.
1303///
1304/// The DRAW and the ENCODING are separate calls so that one `getrandom` call can feed several
1305/// artifacts. `getrandom::fill` is a SYSCALL, and the measurement that matters is that its cost is
1306/// almost entirely per CALL rather than per byte: 875 ns for one byte against 1025 ns for
1307/// thirty-two on the machine benches/README.md names. So the number of calls is the thing to
1308/// reduce, and a caller that needs two 32-byte artifacts should draw 64 bytes once rather than 32
1309/// bytes twice. See `issue`.
1310pub(crate) fn try_random_hex(n_bytes: usize) -> Option<String> {
1311 let mut buf = vec![0u8; n_bytes];
1312 getrandom::fill(&mut buf).ok()?;
1313 Some(hex_encode(&buf))
1314}
1315
1316/// The refusal a request-reachable randomness failure becomes, in the one spelling all three sites
1317/// use.
1318///
1319/// Modelled on [`storage_error`], and for the same reason: the host learns what happened through
1320/// its own logs, the wire gets the opaque RFC 6749 section 5.2 `server_error` and nothing about
1321/// this server's internals. It IS a server error in the section's sense — "the authorization
1322/// server encountered an unexpected condition that prevented it from fulfilling the request" — and
1323/// the client's correct response, retrying later, is the same one it would make to a store that
1324/// was briefly unavailable.
1325/// The fixed input [`AuthorizationServer::dummy_assertion_verify`] verifies over.
1326///
1327/// It is not a JWS signing input and does not need to be: an ES256 verification costs the same
1328/// whatever it is handed, and the string exists only so the operation is well defined.
1329#[cfg(feature = "client-assertion")]
1330const DUMMY_ASSERTION_SIGNING_INPUT: &str = "oauth-as dummy verification input";
1331
1332/// A real ES256 signature over [`DUMMY_ASSERTION_SIGNING_INPUT`], made once by a throwaway key.
1333///
1334/// IT IS A COST, NOT A CREDENTIAL, exactly as the dummy secret hash beside it is: the private half
1335/// was never kept, no registration names the public half below, and the value it signs is a
1336/// constant rather than a token request. What it buys is a verification that runs to completion —
1337/// a malformed signature or a point off the curve would be refused in the parse and cost a
1338/// fraction of the real work, which is the leak again.
1339#[cfg(feature = "client-assertion")]
1340const DUMMY_ASSERTION_SIGNATURE: [u8; 64] = [
1341 91, 15, 217, 171, 65, 158, 255, 105, 97, 207, 103, 199, 34, 188, 42, 123, 113, 63, 9, 92, 242,
1342 81, 20, 20, 147, 223, 209, 148, 122, 59, 212, 156, 132, 79, 44, 44, 108, 53, 228, 247, 251,
1343 153, 155, 251, 71, 102, 34, 231, 227, 160, 80, 16, 215, 84, 84, 74, 117, 3, 91, 5, 148, 20, 28,
1344 47,
1345];
1346
1347/// The public half of that throwaway key.
1348///
1349/// BUILT PER CALL, not cached in a `OnceLock`: this crate promises no global statics and no lazy
1350/// singletons (the crate docs say so, and `tests/allocation.rs` enforces it), and the four small
1351/// allocations a [`crate::jwt::PublicJwk`] costs are invisible beside the ES256 verification they
1352/// are there to feed — which is the whole point, since the KNOWN-id path this is matching pays
1353/// that verification too. The `Jwk` literal is the crate's own publishing shape, whose coordinates
1354/// are by construction the 32-byte base64url a verifier expects.
1355#[cfg(feature = "client-assertion")]
1356fn dummy_assertion_key() -> crate::jwt::PublicJwk {
1357 crate::jwt::Jwk {
1358 kty: "EC",
1359 crv: "P-256",
1360 x: "LIZkYOSRaSLc5uMxzlzV9pgt1ARaDl_3tZfRkt9mzFY".to_string(),
1361 y: "fBSzqWfCploda0TpKf3N56v6fk-fORAiVsXUmkWYWkw".to_string(),
1362 kid: "oauth-as-dummy-verification-key".to_string(),
1363 use_: "sig",
1364 alg: "ES256",
1365 }
1366 .to_public_jwk()
1367}
1368
1369fn randomness_error() -> ErrorResponse {
1370 ErrorResponse::new(ErrorCode::ServerError)
1371}
1372
1373/// WHAT THIS REQUEST'S PRESENTED CREDENTIAL HAS ALREADY BEEN CHARGED FOR, carried from wherever the
1374/// work happened to the ONE exit that refuses a client authentication.
1375///
1376/// This exists because the previous shape had no such carrier: every branch of
1377/// [`AuthorizationServer::authenticate_client`] was also an EXIT, so every branch had to remember to
1378/// charge the dummy verification the unknown-id path charges, and four consecutive audit rounds
1379/// found a branch that had forgotten. A flag that records what WAS spent, plus a single exit that
1380/// spends the remainder, cannot forget: adding a refusal adds a `return Ok(Refused(..))` that
1381/// carries this ledger unchanged, and the charge happens whether or not the author thought about it.
1382///
1383/// Both fields mean "a REAL verification of this kind has already been performed on this request",
1384/// so the exit owes the dummy for whichever kind the request PRESENTED and did not get. Nothing here
1385/// is a fact about the registration, which is the point: what a refusal costs must be a function of
1386/// what arrived on the wire, never of what the store holds.
1387#[derive(Default)]
1388struct CredentialCost {
1389 /// A secret verification ran through [`crate::client::ClientAuth::verify_with`].
1390 secret: bool,
1391 /// An RFC 7523 assertion verification was ATTEMPTED against a registration's keys.
1392 ///
1393 /// "Attempted" rather than "performed", and that is the residual documented as FOURTH on
1394 /// [`AuthorizationServer::authenticate_client`]: `verify_assertion` refuses a malformed
1395 /// assertion, and one whose `alg` is not the registration's, before it reaches any signature
1396 /// work. Setting the flag at the call preserves EXACTLY what this crate charged before this
1397 /// restructure, which is what makes the restructure reviewable as a mechanical change; closing
1398 /// that last gap needs `verify_assertion` to report whether it reached the signature, which is
1399 /// a change to a public function and is not this one.
1400 #[cfg(feature = "client-assertion")]
1401 assertion: bool,
1402}
1403
1404/// The outcome of examining a presented credential, BEFORE anything is charged, recorded or
1405/// emitted for it.
1406///
1407/// A value rather than a return: this is what lets the five refusal decisions in
1408/// [`AuthorizationServer::classify_client_credential`] be decisions instead of exits. Every one of
1409/// them hands back `Refused(failure)` and the single exit does the identical three things to all of
1410/// them — settle the credential's cost, record the failed attempt, tell the audit channel which
1411/// failure it was — before returning the one bare `invalid_client` RFC 6749 section 5.2 collapses
1412/// them into.
1413enum ClientAuthVerdict {
1414 /// The credential verified. The `Arc` is the registration the caller asked for.
1415 Authenticated(std::sync::Arc<Client>),
1416 /// The credential did not verify, for the reason the HOST's audit channel is told. The wire is
1417 /// told nothing beyond `invalid_client`.
1418 Refused(ClientAuthFailure),
1419}
1420
1421/// The rejection-sampling bound: the largest multiple of [`USER_CODE_ALPHABET`]'s length that fits
1422/// in a byte. Values at or above it are redrawn rather than folded, because folding them would
1423/// hand the low-index symbols extra probability.
1424const USER_CODE_REJECT_AT: u8 = 240;
1425
1426/// Map one random byte to a user-code symbol, or reject it for a redraw.
1427///
1428/// This is split out of [`random_user_code`] ON PURPOSE, and the reason is testability rather than
1429/// structure. The property that matters here is UNIFORMITY, and uniformity is not a property of any
1430/// single generated code: a test that can only look at sampled output has to argue statistically,
1431/// which means either a test that is flaky or a test that draws hundreds of thousands of samples to
1432/// notice a bias of a few percent. As a total function of one byte it can instead be checked
1433/// EXHAUSTIVELY over all 256 inputs, which settles the question outright (see
1434/// `src/tests/server.rs`).
1435///
1436/// The numbers are load bearing. 240 is the largest multiple of 20 below 256, so the accepted
1437/// values 0..=239 cover each of the 20 symbols exactly 12 times. Accepting one more value would
1438/// give symbol 0 a thirteenth preimage, an 8% excess over its peers, and RFC 8628 section 5.1 is
1439/// explicit that the user code's entropy is already only just sufficient (in combination with host
1440/// rate limiting) because the code is short enough for a human to type.
1441fn user_code_symbol(byte: u8) -> Option<u8> {
1442 if byte < USER_CODE_REJECT_AT {
1443 Some(USER_CODE_ALPHABET[(byte % 20) as usize])
1444 } else {
1445 None
1446 }
1447}
1448
1449/// A user code of `len` symbols over [`USER_CODE_ALPHABET`], unbiased via rejection sampling.
1450///
1451/// # The entropy is drawn in ONE call, not one per symbol
1452///
1453/// `getrandom::fill` is a SYSCALL and its cost is almost entirely per CALL rather than per byte:
1454/// MEASURED on the machine `benches/README.md` names, 875 ns for one byte and 1025 ns for
1455/// thirty-two. Drawing a byte at a time therefore made the call COUNT the entire cost, and sweeping
1456/// `user_code_length` showed a slope of roughly 960 to 1160 ns per SYMBOL. At the default eight
1457/// symbols that was about 8.2 us of `device_authorization`'s 9.27 us: 85% of an endpoint that takes
1458/// no credential from a public client, spent on syscall entry.
1459///
1460/// Being honest about the magnitude: on Linux with a vDSO `getrandom` (kernel 6.11 and glibc 2.42
1461/// or newer) the per-call cost is far lower, so the figures above are partly a macOS and BSD
1462/// `getentropy` number. THE CALL COUNT IS THE PORTABLE DEFECT, which is why this is worth doing
1463/// regardless of where it runs.
1464///
1465/// The uniformity argument is untouched. Every byte still goes through [`user_code_symbol`], values
1466/// at or above [`USER_CODE_REJECT_AT`] are still redrawn rather than folded, and the buffer is
1467/// simply refilled when it runs out, so a run of rejections costs another draw exactly as it did.
1468/// The buffer is a fixed stack array, so this adds no allocation: 64 bytes is enough that the
1469/// probability of needing a second draw for the default eight-symbol code is negligible (each byte
1470/// is accepted with probability 240/256), while staying small enough to sit in a frame.
1471///
1472/// `None` for the reason [`try_random_hex`] gives: this runs on RFC 8628 section 3.1's device
1473/// authorization endpoint, which is an ordinary request, and a library must not abort its host's
1474/// process because the OS momentarily would not hand over 64 bytes.
1475fn random_user_code(len: usize) -> Option<String> {
1476 let mut out = String::with_capacity(len);
1477 let mut buf = [0u8; 64];
1478 while out.len() < len {
1479 getrandom::fill(&mut buf).ok()?;
1480 for &byte in buf.iter() {
1481 if out.len() == len {
1482 break;
1483 }
1484 if let Some(symbol) = user_code_symbol(byte) {
1485 out.push(symbol as char);
1486 }
1487 }
1488 }
1489 Some(out)
1490}
1491
1492/// `WDJBMJHT` to `WDJB-MJHT`: hyphenate the middle for display when the length is even and at
1493/// least 4; otherwise the raw run is the display form.
1494fn display_user_code(raw: &str) -> String {
1495 // `% 2 == 0` rather than `is_multiple_of`, which did not stabilise until well after this
1496 // crate's supported floor. A library should compile on the oldest toolchain it reasonably can,
1497 // and this reads no worse.
1498 if raw.len() >= 4 && raw.len() % 2 == 0 {
1499 let mid = raw.len() / 2;
1500 format!("{}-{}", &raw[..mid], &raw[mid..])
1501 } else {
1502 raw.to_string()
1503 }
1504}
1505
1506/// Whether a `code_challenge` has the RFC 7636 section 4.2 S256 shape: the base64url (no padding)
1507/// encoding of a 32 byte digest, which is exactly 43 characters of the base64url alphabet.
1508///
1509/// Section 4.1's ABNF admits 43 to 128 characters generally, but that range covers the `plain`
1510/// method, where the challenge is the verifier itself. For S256 the length is fixed by the digest
1511/// size, so anything else was never produced by SHA-256 and cannot match any verifier.
1512fn challenge_is_well_formed(challenge: &str) -> bool {
1513 challenge.len() == 43
1514 && challenge
1515 .bytes()
1516 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_'))
1517}
1518
1519/// The number of decimal digits `n` is written in, which is what `replay_key` has to reserve for
1520/// its length prefix. `0` is one digit, and there is no case with none.
1521#[cfg(any(feature = "client-assertion", feature = "dpop"))]
1522fn decimal_width(n: usize) -> usize {
1523 let mut width = 1;
1524 let mut rest = n / 10;
1525 while rest > 0 {
1526 rest /= 10;
1527 width += 1;
1528 }
1529 width
1530}
1531
1532/// The storage key one single-use identifier is claimed under.
1533///
1534/// NAMESPACED, and both halves matter. `kind` keeps an RFC 7523 assertion's `jti` from colliding
1535/// with an RFC 9449 proof's, which are different credentials with different lifetimes that a client
1536/// may well number from the same counter. `owner` (the client id for an assertion, the key
1537/// thumbprint for a proof) keeps one client from locking another out by choosing its `jti` values:
1538/// without it, an attacker could spend a victim's future `jti` values in advance, which is a denial
1539/// of service bought for the price of a refused request.
1540///
1541/// # Why the length prefix, and why a separator alone was not enough
1542///
1543/// The encoding is `kind ":" LEN(owner) ":" owner jti`, and it is INJECTIVE, which is a stronger
1544/// statement than "the parts are separated" and is the statement that matters:
1545///
1546/// - `kind` is one of this file's own two constants (`ca`, `dpop`), neither of which contains a
1547/// colon, so the first colon ends it;
1548/// - what follows is the decimal byte length of `owner`, digits only, so the next colon ends it;
1549/// - that length says exactly where `owner` stops and `jti` starts, whatever either of them
1550/// contains.
1551///
1552/// Every part is therefore recoverable from the key, so no two distinct `(kind, owner, jti)`
1553/// triples can produce the same one.
1554///
1555/// The previous encoding, `kind:owner:jti`, was NOT injective, and the counterexample is ordinary
1556/// rather than exotic. `ClientId::new` imposes no character restriction and URN-style client ids
1557/// are common, so a client registered as `urn` presenting the `jti` `client:foo:42` produced
1558/// `ca:urn:client:foo:42`, which is exactly what the client registered as `urn:client:foo` gets
1559/// for its `jti` `42`. Whoever claimed it first denied it to the other, so one client could spend
1560/// another's single-use slot and the victim's conforming assertion came back `invalid_client` as a
1561/// replay of something nobody sent. `tests/replay_key_collision.rs` runs that attack.
1562///
1563/// One allocation per assertion or proof, on a path that only exists when the feature is on: the
1564/// capacity below is exact, and `write!` into a `String` with room does not grow it.
1565#[cfg(any(feature = "client-assertion", feature = "dpop"))]
1566fn replay_key(kind: &str, owner: &str, jti: &str) -> String {
1567 use std::fmt::Write as _;
1568 let mut key = String::with_capacity(
1569 kind.len() + 1 + decimal_width(owner.len()) + 1 + owner.len() + jti.len(),
1570 );
1571 key.push_str(kind);
1572 key.push(':');
1573 // Infallible: `fmt::Write` for `String` cannot fail, and there is no error to handle.
1574 let _ = write!(key, "{}", owner.len());
1575 key.push(':');
1576 key.push_str(owner);
1577 key.push_str(jti);
1578 key
1579}
1580
1581fn storage_error(e: StorageError) -> ErrorResponse {
1582 // The host sees the real error through its own Storage impl; the wire gets the opaque code.
1583 let _ = e;
1584 ErrorResponse::new(ErrorCode::ServerError)
1585}
1586
1587/// The RFC 9396 authorization details flowing through one issuance: what a grant carries,
1588/// what a token request asked to narrow it to, and what the issued token ends up with.
1589///
1590/// A WRAPPER rather than the details themselves, and the reason is structural. `issue` and
1591/// the grant helpers have to have exactly ONE signature in every feature configuration: an
1592/// argument can carry a `cfg`, but the ARGUMENT AT THE CALL SITE cannot, so a gated
1593/// parameter would mean duplicating five call sites under `cfg` and giving the eight
1594/// existing arguments five more places to drift. This is the same reasoning `Bound` above
1595/// records for the RFC 9449 key binding.
1596///
1597/// Without `rar` this struct has no fields, so it is zero sized, every construction of it
1598/// compiles to nothing, and the default build's token future keeps the size
1599/// `tests/allocation.rs` pins. That gate exists because crossing tokio's 2048-byte debug
1600/// boxing threshold costs an allocation on every request that reaches the endpoint.
1601#[derive(Debug, Clone, Default, PartialEq, Eq)]
1602pub(crate) struct GrantedDetails {
1603 /// BOXED, and an `Option` so that the common case is a null pointer. The token future
1604 /// is 1824 bytes against tokio's 2048-byte debug boxing threshold, and this value is
1605 /// live in it at six points; carrying the details inline (three words) crossed the
1606 /// threshold and cost a 2 KB allocation on EVERY token request, which is exactly the
1607 /// regression `tests/allocation.rs` was written to catch, and it caught this one. One
1608 /// word costs 48 bytes of the future instead of 144, and the allocation behind the
1609 /// `Some` is paid only by a request that actually carries authorization details.
1610 #[cfg(feature = "rar")]
1611 inner: Option<Box<crate::rar::AuthorizationDetails>>,
1612}
1613
1614impl GrantedDetails {
1615 /// Wrap details read off a grant record, keeping the empty case a null pointer.
1616 #[cfg(feature = "rar")]
1617 fn of(details: &crate::rar::AuthorizationDetails) -> Self {
1618 GrantedDetails {
1619 inner: (!details.is_empty()).then(|| Box::new(details.clone())),
1620 }
1621 }
1622
1623 /// What an authorization code granted (RFC 9396 section 7: the details as approved by
1624 /// the resource owner and assigned to the token this code mints).
1625 fn of_code(record: &AuthorizationCodeRecord) -> Self {
1626 #[cfg(feature = "rar")]
1627 {
1628 GrantedDetails::of(&record.authorization_details)
1629 }
1630 #[cfg(not(feature = "rar"))]
1631 {
1632 let _ = record;
1633 GrantedDetails {}
1634 }
1635 }
1636
1637 /// What a refresh chain carries, which is what the previous leg narrowed it to.
1638 fn of_refresh(record: &RefreshTokenRecord) -> Self {
1639 #[cfg(feature = "rar")]
1640 {
1641 GrantedDetails::of(&record.authorization_details)
1642 }
1643 #[cfg(not(feature = "rar"))]
1644 {
1645 let _ = record;
1646 GrantedDetails {}
1647 }
1648 }
1649
1650 /// What an already-issued token carries, for a grant that continues it: the RFC 8693
1651 /// exchange, where the exchanged token inherits the subject token's details.
1652 ///
1653 /// `dead_code` for the same reason [`Bound::secret`] carries it: its only caller is
1654 /// behind another slice's cargo feature, and gating this on that feature by name would
1655 /// tie this module to a flag it has no other business knowing.
1656 #[allow(dead_code)]
1657 pub(crate) fn of_token(token: &IssuedToken) -> Self {
1658 #[cfg(feature = "rar")]
1659 {
1660 GrantedDetails::of(&token.authorization_details)
1661 }
1662 #[cfg(not(feature = "rar"))]
1663 {
1664 let _ = token;
1665 GrantedDetails {}
1666 }
1667 }
1668
1669 /// The owned details to write onto a record.
1670 #[cfg(feature = "rar")]
1671 fn into_details(self) -> crate::rar::AuthorizationDetails {
1672 self.inner.map(|d| *d).unwrap_or_default()
1673 }
1674
1675 /// Whether anything was asked for at all.
1676 ///
1677 /// `dead_code` for the same reason [`GrantedDetails::of_token`] carries it, and NOT because
1678 /// nothing calls it: `token_with_resources` uses it on the device-code branch to refuse
1679 /// `authorization_details` on a grant that never carried any. That call site is behind
1680 /// `#[cfg(feature = "rar")]`, so in a build without that feature this genuinely has no caller,
1681 /// and gating the allow on the feature by name would tie this wrapper to a flag whose whole
1682 /// purpose is to be invisible from here.
1683 #[allow(dead_code)]
1684 fn is_empty(&self) -> bool {
1685 #[cfg(feature = "rar")]
1686 {
1687 self.inner.is_none()
1688 }
1689 #[cfg(not(feature = "rar"))]
1690 {
1691 true
1692 }
1693 }
1694
1695 /// The details an issuance gets: `requested` may NARROW what `self` carries and may
1696 /// never widen it (RFC 9396 section 6). Delegated to [`crate::rar`], which is where the
1697 /// comparison rule and the argument for it live; this is the seam, not the rule.
1698 fn narrow(&self, requested: &GrantedDetails) -> Result<GrantedDetails, ErrorResponse> {
1699 #[cfg(feature = "rar")]
1700 {
1701 let requested = match &requested.inner {
1702 // Nothing asked for, so nothing to narrow: the grant passes through.
1703 None => return Ok(self.clone()),
1704 Some(requested) => requested.as_ref(),
1705 };
1706 // A grant carrying none narrows to nothing, and `narrow` refuses accordingly:
1707 // widening from nothing is still widening. The empty set allocates nothing.
1708 let empty = crate::rar::AuthorizationDetails::none();
1709 let granted = self.inner.as_deref().unwrap_or(&empty);
1710 Ok(GrantedDetails::of(&granted.narrow(requested)?))
1711 }
1712 #[cfg(not(feature = "rar"))]
1713 {
1714 let _ = requested;
1715 Ok(GrantedDetails {})
1716 }
1717 }
1718}
1719
1720/// The refresh chain an issuance CONTINUES: carried from the redeemed record to its replacement,
1721/// so that rotation preserves both the family (RFC 9700 section 4.14.2 revokes by grant) and the
1722/// absolute lifetime (a chain must not slide its own expiry forward every time it rotates).
1723pub(crate) struct RefreshChain {
1724 family_id: String,
1725 expires_at: Option<SystemTime>,
1726}
1727
1728/// The authorization server. Generic over the host's [`Storage`] and (for tests) the [`Clock`].
1729pub struct AuthorizationServer<S: Storage, C: Clock = SystemClock> {
1730 config: ServerConfig,
1731 store: S,
1732 clock: C,
1733 /// The RFC 8414 `token_endpoint` this server answers on, derived once from `config`.
1734 ///
1735 /// It exists as a field because it is compared against on every RFC 9449 proof and every RFC
1736 /// 7523 assertion (see [`AuthorizationServer::token_endpoint`]), and it is fixed for the life
1737 /// of the server: `config` is moved in here and there is no way to mutate it afterwards.
1738 /// `Box<str>` rather than `String` because it is never appended to, which keeps the field at
1739 /// 16 bytes instead of 24 on a struct `tests/allocation.rs` holds to a size budget.
1740 #[cfg(any(feature = "client-assertion", feature = "dpop"))]
1741 token_endpoint: Box<str>,
1742 /// The host's optional seams (audit sink, rate limiter, secret verifier). ONE pointer wide and
1743 /// null until the host installs something: see [`Hooks`] for why the three do not sit here as
1744 /// three separate fields.
1745 hooks: Hooks,
1746}
1747
1748impl<S: Storage> AuthorizationServer<S, SystemClock> {
1749 /// Construct with the real clock. This is the crate's allocation entry point: call it when
1750 /// (and only when) host config enables the AS.
1751 pub fn new(config: ServerConfig, store: S) -> Self {
1752 Self::with_clock(config, store, SystemClock)
1753 }
1754}
1755
1756impl<S: Storage, C: Clock> AuthorizationServer<S, C> {
1757 /// Construct with an injected clock (tests).
1758 pub fn with_clock(config: ServerConfig, store: S, clock: C) -> Self {
1759 // Derived HERE and not at each use, and derived exactly as
1760 // `AuthorizationServerMetadata::from_config` derives it, because a server whose own idea
1761 // of its token endpoint differs from the one it publishes refuses every conforming client.
1762 #[cfg(any(feature = "client-assertion", feature = "dpop"))]
1763 let token_endpoint: Box<str> = match &config.token_endpoint {
1764 Some(endpoint) => endpoint.as_str().into(),
1765 None => format!("{}/token", config.issuer.trim_end_matches('/')).into_boxed_str(),
1766 };
1767 AuthorizationServer {
1768 config,
1769 store,
1770 clock,
1771 #[cfg(any(feature = "client-assertion", feature = "dpop"))]
1772 token_endpoint,
1773 hooks: Hooks::new(),
1774 }
1775 }
1776
1777 /// Install the audit sink (RFC-agnostic; see [`crate::events`]). Builder-style so a host wires
1778 /// it at construction: `AuthorizationServer::new(cfg, store).with_event_sink(Box::new(sink))`.
1779 ///
1780 /// This crate logs nothing by itself. Without a sink, the two events that are evidence of
1781 /// compromise (authorization code replay, refresh token reuse) revoke silently, which means an
1782 /// operator learns about a stolen grant from a support ticket rather than from a log line.
1783 pub fn with_event_sink(mut self, sink: Box<dyn EventSink>) -> Self {
1784 self.hooks.install_event_sink(sink);
1785 self
1786 }
1787
1788 /// Install the rate limiter. THIS LIBRARY DOES NOT RATE LIMIT: RFC 8628 section 5.1 makes user
1789 /// code entropy adequate only IN COMBINATION WITH rate limiting of code entry, and only the
1790 /// host has a caller, an IP or a session to count against. See
1791 /// [`AuthorizationServer::approve_device`].
1792 pub fn with_rate_limiter(mut self, limiter: Box<dyn RateLimiter>) -> Self {
1793 self.hooks.install_rate_limiter(limiter);
1794 self
1795 }
1796
1797 /// Install the client secret verifier, for [`crate::client::SecretHash`] schemes this crate
1798 /// does not implement (argon2id, scrypt, an HSM). The built-in scheme needs no verifier and is
1799 /// never delegated to one.
1800 pub fn with_secret_verifier(
1801 mut self,
1802 verifier: Box<dyn crate::client::SecretVerifier>,
1803 ) -> Self {
1804 self.hooks.install_secret_verifier(verifier);
1805 self
1806 }
1807
1808 /// Install the RFC 7591 registration policy: who may create a client here.
1809 ///
1810 /// Required, not optional, for any host that sets [`ServerConfig::registration`]: with no
1811 /// policy installed every registration is refused, because an endpoint that mints clients and
1812 /// has been told nothing about who may use it is the abuse vector RFC 7591 section 5
1813 /// describes. See [`crate::registration::RegistrationPolicy`].
1814 pub fn with_registration_policy(
1815 mut self,
1816 policy: Box<dyn crate::registration::RegistrationPolicy>,
1817 ) -> Self {
1818 self.hooks.install_registration_policy(policy);
1819 self
1820 }
1821
1822 /// Install the RFC 9101 request object verification keys: which public key, under which
1823 /// algorithm, each client registered for signing request objects.
1824 ///
1825 /// Required, not optional, for a host that sets [`ServerConfig::jar`]: with no key source
1826 /// installed every `request` parameter is refused, because a server that cannot check a
1827 /// signature must not act on the claims under it. See [`crate::par::RequestObjectKeys`].
1828 #[cfg(feature = "jar")]
1829 pub fn with_request_object_keys(
1830 mut self,
1831 keys: Box<dyn crate::par::RequestObjectKeys>,
1832 ) -> Self {
1833 self.hooks.install_request_object_keys(keys);
1834 self
1835 }
1836
1837 /// Install the ES256 backend this server VERIFIES signatures with: RFC 9449 DPoP proofs, RFC
1838 /// 9101 request objects, RFC 7523 client assertions.
1839 ///
1840 /// Required unless `jwt-p256` is compiled in, which installs [`crate::jwt::P256Verifier`] as
1841 /// the default. With neither, every signed credential is REFUSED, exactly as an absent
1842 /// [`crate::par::RequestObjectKeys`] or an absent registration policy refuses: a server that
1843 /// cannot check a signature must never behave as though it had checked one.
1844 ///
1845 /// A verifier installed here WINS over the built-in one, because it was installed. That is the
1846 /// whole of the precedence rule, and it is why nothing in this crate's feature set is mutually
1847 /// exclusive: a dependency graph that unifies `jwt-p256` on cannot take a host's choice away.
1848 ///
1849 /// Run [`crate::signer_conformance`] against whatever you install here before you deploy it.
1850 // NO `#[must_use]`, for the crate-wide reason `tests/host_api_shape.rs` states and gates: this
1851 // is one of twenty-nine consuming builders, all of which move their receiver, so dropping the
1852 // result is a compile error at the next use of it rather than a setting silently lost. One
1853 // marked builder out of twenty-nine is the state that gate exists to refuse.
1854 #[cfg(feature = "jwt")]
1855 #[cfg_attr(docsrs, doc(cfg(feature = "jwt")))]
1856 pub fn with_es256_verifier(
1857 mut self,
1858 verifier: std::sync::Arc<dyn crate::jwt::Es256Verifier>,
1859 ) -> Self {
1860 self.hooks.install_es256_verifier(verifier);
1861 self
1862 }
1863
1864 /// The ES256 verifier this server will use, or `None` when it has none and must refuse.
1865 ///
1866 /// THE ONE PLACE the precedence rule lives: the host's installed verifier, else the built-in
1867 /// `jwt-p256` backend when that feature is compiled in, else nothing. Every caller
1868 /// (`verify_dpop`, the RFC 7523 assertion check, the RFC 9101 request object check) asks here
1869 /// and refuses on `None`, so there is exactly one definition of "no backend installed" and no
1870 /// path that can accidentally read it as "checked out".
1871 // Gated on the features that actually VERIFY rather than on `jwt`: a build that signs and
1872 // never checks anybody else's signature has no caller for this, and an uncalled resolver is
1873 // one more thing a reader has to work out is not reachable.
1874 #[cfg(any(feature = "dpop", feature = "jar", feature = "client-assertion"))]
1875 pub(crate) fn es256_verifier(&self) -> Option<&dyn crate::jwt::Es256Verifier> {
1876 match self.hooks.es256_verifier() {
1877 Some(installed) => Some(&**installed),
1878 #[cfg(feature = "jwt-p256")]
1879 None => Some(&crate::jwt::P256Verifier),
1880 #[cfg(not(feature = "jwt-p256"))]
1881 None => None,
1882 }
1883 }
1884
1885 /// The installed host seams, for a host that wants to emit its own events onto the same
1886 /// channel (a consent decision, say) or to consult its own limiter.
1887 pub fn hooks(&self) -> &Hooks {
1888 &self.hooks
1889 }
1890
1891 /// This server's clock, for the parts of the crate that live in other modules
1892 /// ([`crate::registration`]) and cannot reach the private field.
1893 pub(crate) fn now(&self) -> SystemTime {
1894 self.clock.now()
1895 }
1896
1897 /// How long a [`crate::store::RevocationBarrier`] this server records has to stand.
1898 ///
1899 /// A barrier exists to refuse a write from a request that was ALREADY HOLDING a record when
1900 /// the revocation ran. What bounds that is not the request's wall time, which nothing here can
1901 /// know, but the lifetime of what such a request could still write: no in-flight issuance can
1902 /// produce a credential that outlives the longest this server is configured to mint. So the
1903 /// deadline is now plus that longest lifetime, and a barrier that has stood for it has
1904 /// outlived everything it was recorded to refuse.
1905 ///
1906 /// All four durations are considered rather than just the access token TTL, and the
1907 /// `refresh_reuse_window` is in the list on purpose: a chain with NO absolute lifetime
1908 /// (`refresh_token_ttl: None`) has its spent records retained for exactly that window, so it
1909 /// is the longest-lived thing such a family produces.
1910 ///
1911 /// Erring long costs storage that [`crate::store::Storage::sweep_expired`] reclaims. Erring
1912 /// short costs the revocation itself, silently, which is the failure this whole mechanism
1913 /// exists to prevent, so the asymmetry is taken deliberately.
1914 /// Put a taken refresh record back, after a judgement that is NOT evidence of compromise.
1915 ///
1916 /// [`crate::store::Storage::take_refresh_token`] removes the record before anything about it
1917 /// has been judged, so every refusal that is not reuse has to restore it, or a client that
1918 /// merely asked for the wrong scope would lose its chain. Five refusal paths do that, and they
1919 /// all handle a refused write the same way, so they call this rather than each writing the
1920 /// handling out: the last time this crate had one operation at several seams, the copy that
1921 /// diverged was the one that failed open.
1922 ///
1923 /// A [`crate::store::WriteOutcome::RefusedRevoked`] here is neither an error nor a surprise.
1924 /// It means a revocation reached this family, client or consent while the record was out of
1925 /// the store, and the record STAYING gone is exactly what that revocation asked for. The
1926 /// caller's own refusal is the answer to the client either way. A genuine storage failure is
1927 /// still fatal, because then it is not known whether the chain survived.
1928 async fn restore_refresh_token(
1929 &self,
1930 record: crate::token::RefreshTokenRecord,
1931 ) -> Result<(), ErrorResponse> {
1932 let _outcome = self
1933 .store
1934 .put_refresh_token(record)
1935 .await
1936 .map_err(storage_error)?;
1937 Ok(())
1938 }
1939
1940 /// Take back an access token this server wrote and then decided not to hand out.
1941 ///
1942 /// Issuance is two writes and [`crate::store::Storage`] has no transaction spanning them (see
1943 /// the trait's own docs on why requiring cross-key atomicity would exclude most stores). A
1944 /// revocation landing between them leaves the first write standing, so issuance has to be able
1945 /// to reverse itself, and this is that reversal.
1946 ///
1947 /// FAILURE IS SWALLOWED, deliberately, and this is the one judgement in it. The caller is
1948 /// already returning an error and the client will never see the token; the alternative is to
1949 /// report a storage failure INSTEAD of the revocation, which tells the caller the wrong thing
1950 /// about why it was refused. What is left behind on a failed undo is one orphaned access token
1951 /// that [`crate::store::Storage::sweep_expired`] reclaims at its own expiry, and which no
1952 /// client holds the string for.
1953 async fn undo_issuance(&self, access_token: &str) {
1954 let _ = self.store.delete_token(access_token).await;
1955 }
1956
1957 /// The window a revocation happening NOW should record: the instant it happened, and the
1958 /// instant past which nothing it was entitled to kill can still be in flight.
1959 ///
1960 /// Both come from ONE reading of the clock. Taking `now` twice would let the two instants
1961 /// straddle a tick, and `recorded_at` is compared against by every subsequent write, so a
1962 /// `recorded_at` even fractionally later than the revocation's own effect is a grant
1963 /// wrongly refused.
1964 pub(crate) fn revocation_window(&self) -> crate::store::RevocationWindow {
1965 let longest = self
1966 .config
1967 .access_token_ttl
1968 .max(self.config.refresh_token_ttl.unwrap_or_default())
1969 .max(self.config.refresh_reuse_window)
1970 .max(self.config.authorization_code_ttl)
1971 .max(self.config.device_code_ttl);
1972 let recorded_at = self.clock.now();
1973 crate::store::RevocationWindow {
1974 recorded_at,
1975 until: saturating_deadline(recorded_at, longest),
1976 }
1977 }
1978
1979 /// Turn the freshly minted random token into what actually goes on the wire: itself when the
1980 /// format is opaque (the byte-for-byte pre-feature behaviour), or an RFC 9068 access token
1981 /// carrying it as `jti` when the host configured signing.
1982 ///
1983 /// SYNC, and it stops one step short of the signature, which is what the awkward return type
1984 /// buys. The host's [`crate::jwt::Es256Signer`] may be a network round trip, so signing is
1985 /// async; if this function were async instead, the whole [`AccessTokenClaims`] value below
1986 /// would live across that suspension point and join the token endpoint's coroutine frame,
1987 /// which `tests/allocation.rs` holds under tokio's 2048-byte debug boxing threshold. Splitting
1988 /// here means the claims are built and consumed on the sync side and only a `String` crosses
1989 /// the await. MEASURED: 1344 bytes before the seam, 1360 after; an async `wire_access_token`
1990 /// measured 1656.
1991 // Eight arguments, since the RFC 8705 binding is a property of the REQUEST rather than of the
1992 // grant. Same allow and same reason as `issue` below: a private function with one call site,
1993 // whose arguments would have to live across every await in the token future if they were
1994 // bundled into a struct to satisfy a lint.
1995 #[allow(clippy::too_many_arguments)]
1996 #[cfg(feature = "jwt")]
1997 fn access_token_signing_input(
1998 &self,
1999 client: &Client,
2000 subject: Option<&str>,
2001 scope: &ScopeSet,
2002 resource: &[String],
2003 details: &GrantedDetails,
2004 now: SystemTime,
2005 // The instant the caller has decided this token dies at, which is NOT always
2006 // `now + access_token_ttl`: see the `lifetime_ceiling` argument of
2007 // [`AuthorizationServer::issue`]. Handed in rather than recomputed here, because a signed
2008 // `exp` that disagrees with the stored expiry is a token two halves of one deployment
2009 // enforce differently.
2010 expires_at: SystemTime,
2011 jti: String,
2012 bound: &Bound<'_>,
2013 actor: &GrantedActor,
2014 // What the HOST reported about the resource owner's login, for RFC 9470 s6.1. Handed in
2015 // rather than read off the record being written next door, for the reason `expires_at` is:
2016 // the signed claim and the persisted record must be the one value stated twice, and the
2017 // record is written after this returns.
2018 authentication: &GrantedAuthentication,
2019 ) -> Result<Result<(&crate::jwt::JwtConfig, String), String>, ErrorResponse> {
2020 // Only the RFC 8705 binding is read out of it here; without that feature the
2021 // signed claim set does not depend on how the client authenticated.
2022 #[cfg(not(feature = "mtls"))]
2023 let _ = bound;
2024 // Likewise the RFC 9396 details, which reach the claim set only under `rar`. This is not
2025 // decoration: `http,jwt` without `rar` is exactly what the conformance server builds, and
2026 // neither a default build (where this function does not exist) nor `--all-features` (where
2027 // `details` IS read) compiles that combination. CI caught it; local testing had not.
2028 #[cfg(not(feature = "rar"))]
2029 let _ = details;
2030 let jwt = match &self.config.access_token_format {
2031 // `Err` is not a failure here: it is the OPAQUE arm, carrying the random string
2032 // through unchanged. Two arms of one `Result` rather than an `Option` plus a moved-out
2033 // `jti`, because the opaque path must not copy the string it already has.
2034 AccessTokenFormat::Opaque => return Ok(Err(jti)),
2035 AccessTokenFormat::Jwt(jwt) => jwt,
2036 };
2037 // Without the feature the wrapper is empty and genuinely unused here, exactly as `bound`
2038 // is without `dpop`; see the note at the top of `issue`.
2039 #[cfg(not(feature = "token-exchange"))]
2040 let _ = actor;
2041 // Same for the RFC 9470 report: without `consent` there is no field on the claim set to
2042 // fill and no field on the wrapper to fill it from.
2043 #[cfg(not(feature = "consent"))]
2044 let _ = authentication;
2045 let claims = AccessTokenClaims {
2046 // The SAME spelling the RFC 8414 document publishes, the RFC 9207 `iss` parameter
2047 // carries and introspection reports. `issuer_identifier` trims a trailing slash, and
2048 // the raw config value does not, so a host configuring "https://as.example/" used to
2049 // publish "https://as.example" everywhere except here. A resource server doing the
2050 // byte comparison RFC 9068 s4 and RFC 8414 s3.3 call for would then reject every
2051 // token this server signs, or be patched to compare loosely, which disables the
2052 // mix-up defence RFC 9207 exists to provide. One server, one identity, everywhere it
2053 // states it.
2054 iss: self.issuer_identifier().to_string(),
2055 exp: crate::jwt::unix_seconds(expires_at)
2056 .map_err(|_| ErrorResponse::new(ErrorCode::ServerError))?,
2057 // RFC 8707 s2 with RFC 9068 s2.2: when the client named the resource server(s) it
2058 // means to call, THAT is the audience, and the configured default is not. The default
2059 // is a deployment-wide statement made before any request arrived; the resource
2060 // indicator is this grant's own, and honouring the wider one would hand back a token
2061 // valid somewhere the client did not ask for and the user did not approve. Single
2062 // resource stays the plain-string form RFC 7519 s4.1.3 allows, because that is what
2063 // most resource servers actually parse.
2064 aud: match resource {
2065 [] => jwt.audience().clone(),
2066 [one] => crate::jwt::Audience::One(one.clone()),
2067 many => crate::jwt::Audience::Many(many.to_vec()),
2068 },
2069 // RFC 9068 section 2.2: `sub` is REQUIRED. Where there is no resource owner (a
2070 // client-only grant) the RFC's own answer is the client identifier, so the claim is
2071 // never absent and never invented.
2072 sub: subject
2073 .unwrap_or_else(|| client.client_id.as_str())
2074 .to_string(),
2075 client_id: client.client_id.as_str().to_string(),
2076 iat: crate::jwt::unix_seconds(now)
2077 .map_err(|_| ErrorResponse::new(ErrorCode::ServerError))?,
2078 jti,
2079 scope: (!scope.is_empty()).then(|| scope.to_string()),
2080 // RFC 9396 s9.1: the AS is RECOMMENDED to add the authorization details as a
2081 // top-level claim, so a resource server holding a JWT does not have to call
2082 // introspection to learn what the token actually authorizes. NOT filtered per
2083 // audience, which s9.1 also suggests: filtering means deciding which detail
2084 // belongs to which resource server, and only the API that defined the `type`
2085 // knows that (s6.1). A detail that names its own `locations` has already said
2086 // so, in a form the resource server can check for itself.
2087 #[cfg(feature = "rar")]
2088 authorization_details: details.clone().into_details(),
2089 // RFC 9470 s6.1, in the token itself. s6.2 (introspection) was the only channel this
2090 // crate answered on through 0.9.1, and it is the channel a JWT deployment does not
2091 // use: a resource server that verifies the signature locally never asks this server
2092 // anything, so a step-up it could not see in the claims was one it had to take the
2093 // client's word for. That is the failure the s3 challenge exists to prevent.
2094 //
2095 // The SAME conversion the introspection response uses, deliberately: `unix_seconds`
2096 // answers `None` for an instant before the epoch, so a host-reported `auth_time` this
2097 // server cannot state is stated by NEITHER channel rather than by one of them. Two
2098 // channels disagreeing about one token is worse than both being silent, and silence
2099 // here re-challenges (s3) rather than admitting anything.
2100 #[cfg(feature = "consent")]
2101 auth_time: authentication
2102 .authentication
2103 .as_ref()
2104 .and_then(|a| unix_seconds(a.auth_time)),
2105 #[cfg(feature = "consent")]
2106 acr: authentication
2107 .authentication
2108 .as_ref()
2109 .and_then(|a| a.acr.as_deref().map(str::to_string)),
2110 // EVERY binding the AS-side record carries, in the form a resource server can check
2111 // for itself without calling introspection at all. RFC 9449 s6.1 for `jkt`, RFC 8705
2112 // s3.1 for `x5t#S256`, built the same way introspection builds it so the two can never
2113 // disagree about what a token is bound to.
2114 #[cfg(any(feature = "dpop", feature = "mtls"))]
2115 cnf: {
2116 let cnf = crate::token::Confirmation {
2117 #[cfg(feature = "dpop")]
2118 jkt: bound.jkt.map(str::to_string),
2119 #[cfg(feature = "mtls")]
2120 x5t_s256: bound.cred.certificate.map(|c| *c.thumbprint()),
2121 };
2122 (!cnf.is_empty()).then_some(cnf)
2123 },
2124 // RFC 8693 s4.1, in the token itself. A JWT is typically validated offline by a
2125 // resource server that never introspects, so the record alone would not reach it.
2126 #[cfg(feature = "token-exchange")]
2127 act: actor.act.as_deref().cloned(),
2128 };
2129 // `claims` dies HERE, before the caller awaits the signature. See this function's doc.
2130 jwt.signing_input(&claims)
2131 .map(|input| Ok((&**jwt, input)))
2132 .map_err(|e| {
2133 // The host sees the real error through its own logging of the config it supplied; the
2134 // wire gets the opaque code, as with storage failures.
2135 let _ = e;
2136 ErrorResponse::new(ErrorCode::ServerError)
2137 })
2138 }
2139
2140 /// The RFC 7517 key set to serve at `jwks_uri`, or `None` when tokens are opaque. PUBLIC key
2141 /// parameters only.
2142 #[cfg(feature = "jwt")]
2143 pub fn jwks(&self) -> Option<Jwks> {
2144 match &self.config.access_token_format {
2145 AccessTokenFormat::Opaque => None,
2146 AccessTokenFormat::Jwt(jwt) => Some(jwt.jwks()),
2147 }
2148 }
2149
2150 /// The configured `jwks_uri`, or `None`. An RFC 8414 metadata document must advertise
2151 /// `jwks_uri` exactly when this is `Some`: advertising a key set for an AS that signs nothing
2152 /// is a lie, and signing without advertising leaves resource servers unable to verify.
2153 #[cfg(feature = "jwt")]
2154 pub fn jwks_uri(&self) -> Option<&str> {
2155 match &self.config.access_token_format {
2156 AccessTokenFormat::Opaque => None,
2157 AccessTokenFormat::Jwt(jwt) => jwt.jwks_uri(),
2158 }
2159 }
2160
2161 /// The configuration.
2162 pub fn config(&self) -> &ServerConfig {
2163 &self.config
2164 }
2165
2166 /// The RFC 8414 document THIS server would publish, which is the one a host should serve.
2167 ///
2168 /// Different from [`crate::metadata::AuthorizationServerMetadata::from_config`], and the
2169 /// difference is the point: `from_config` sees the configuration and nothing else, while some
2170 /// of what the document promises depends on a seam the host INSTALLED on the server. RFC 7523
2171 /// `private_key_jwt` is the case that forced this. It is ES256, so it is honest exactly when
2172 /// this server can check an ES256 signature, and that is a property of
2173 /// [`AuthorizationServer::with_es256_verifier`] plus the `jwt-p256` feature, neither of which
2174 /// a `&ServerConfig` can see. A method the document names and the token endpoint refuses
2175 /// every time is not a defect a client can work around: it did what it was told.
2176 ///
2177 /// `from_config` therefore advertises only what the CONFIGURATION alone establishes, and this
2178 /// adds back exactly what the installed seams establish. It is the direction that fails safe:
2179 /// a host that ignores this method under-advertises rather than inviting clients to use a
2180 /// method that cannot work. [`crate::http::ServiceBuilder::build`] uses this one.
2181 pub fn metadata(&self) -> crate::metadata::AuthorizationServerMetadata {
2182 #[allow(unused_mut)]
2183 let mut meta = crate::metadata::AuthorizationServerMetadata::from_config(&self.config);
2184 // Only the ES256-dependent members need adjusting, and only in a build that could verify
2185 // at all: the `cfg` is exactly the set `es256_verifier` is gated on, because those three
2186 // features are the three that advertise something an ES256 signature check has to back.
2187 #[cfg(any(feature = "client-assertion", feature = "jar", feature = "dpop"))]
2188 if self.es256_verifier().is_some() {
2189 meta.es256_verification_is_available();
2190 }
2191 meta
2192 }
2193
2194 /// This server's issuer identifier, in the ONE spelling it publishes.
2195 ///
2196 /// RFC 9207 section 2.4 has the client compare the `iss` authorization response parameter
2197 /// against the issuer it started the flow with, for EQUALITY, and the value it started from is
2198 /// the RFC 8414 `issuer` metadata member. `AuthorizationServerMetadata::from_config` trims a
2199 /// trailing slash off the configured issuer, so this trims it too: a host that wrote
2200 /// `https://as.example/` must not end up with two spellings of its own identity, because the
2201 /// mismatch would read to a conforming client as a mix-up attack in progress.
2202 // `pub(crate)` so `par.rs` can check an RFC 9101 request object's `aud` claim against the
2203 // ONE spelling this server publishes, rather than re-deriving it and risking a second one.
2204 pub(crate) fn issuer_identifier(&self) -> &str {
2205 self.config.issuer.trim_end_matches('/')
2206 }
2207
2208 /// Validate one RFC 8707 `resource` list from the wire into the owned form the grant records.
2209 ///
2210 /// Section 2 gives `invalid_target` as the answer for a value this server will not issue a
2211 /// token for, which covers both a malformed indicator and (at the token endpoint, see
2212 /// [`AuthorizationServer::narrow_resources`]) one that was never granted.
2213 pub(crate) fn validate_resources<'a>(
2214 &self,
2215 requested: impl IntoIterator<Item = &'a str>,
2216 ) -> Result<Vec<String>, ErrorResponse> {
2217 // The cap is checked as the list is walked rather than up front, because `requested` is an
2218 // iterator (both call sites hand in a borrowed `map`, so there is nothing to count without
2219 // collecting first, and collecting is the allocation the cap exists to bound). Refusing at
2220 // the moment the cap is exceeded means at most `MAX_RESOURCE_INDICATORS` elements are ever
2221 // examined however many were sent.
2222 let mut out = Vec::new();
2223 // Counted on the INPUT, not on `out`. Counting deduplicated survivors would leave a caller
2224 // free to send ten thousand copies of one URI: each still costs a full scan of `out`, so
2225 // the work is unbounded even though the result is small.
2226 let mut seen = 0usize;
2227 for value in requested {
2228 seen += 1;
2229 if seen > MAX_RESOURCE_INDICATORS {
2230 return Err(ErrorResponse::new(ErrorCode::InvalidTarget)
2231 .with_description("too many resource indicators (RFC 8707 s2)"));
2232 }
2233 if !crate::authorization::is_valid_resource_indicator(value) {
2234 // The offending value is NOT echoed: RFC 6749 section 5.2 restricts
2235 // error_description to a charset an attacker-supplied URI need not respect, and
2236 // naming the parameter is enough for the developer who sent it.
2237 return Err(
2238 ErrorResponse::new(ErrorCode::InvalidTarget).with_description(
2239 "resource must be an absolute URI with no fragment (RFC 8707 s2)",
2240 ),
2241 );
2242 }
2243 // SYNTAX IS NOT AUTHORISATION. RFC 8707 section 2 requires `invalid_target` when the
2244 // server "is unwilling or unable to issue an access token" for a named resource, and
2245 // until this check existed the server had no notion of unwilling: any well-formed URI
2246 // was accepted. That matters most with the `jwt` feature on, where the requested
2247 // resource REPLACES the configured audience in the RFC 9068 `aud` claim, so a client
2248 // registered for one resource server could name another and receive a token this
2249 // server had signed, with that other server's identifier in `aud`. The second server
2250 // fetches our JWKS, the signature verifies, `aud` names it, and it authorises on a
2251 // scope string the two happen to share. Scope-name collision between resource servers
2252 // is the ordinary case, not an exotic one.
2253 //
2254 // FACTORED OUT rather than written here, because RFC 8693 s2.1.1 `audience` names the
2255 // same thing as `resource` in a spelling that need not be a URI: it must skip the
2256 // syntax check above and must NOT skip this one. See `target_is_permitted`.
2257 self.target_is_permitted(value)?;
2258 // A repeated identical indicator is the same request twice, not two audiences.
2259 if !out.iter().any(|kept: &String| kept == value) {
2260 out.push(value.to_string());
2261 }
2262 }
2263 Ok(out)
2264 }
2265
2266 /// Whether this server is willing to issue a token naming `value` at all: the
2267 /// [`ServerConfig::allowed_resources`] check, on its own.
2268 ///
2269 /// SYNTAX IS NOT AUTHORISATION, and the two questions had been welded together. RFC 8707
2270 /// section 2 requires `invalid_target` when the server "is unwilling or unable to issue an
2271 /// access token" for a named target, and that is a statement about the DEPLOYMENT: it is how an
2272 /// operator decommissions a resource server, and until it existed any well-formed URI was
2273 /// accepted. It matters most with the `jwt` feature on, where the requested target REPLACES the
2274 /// configured audience in the RFC 9068 `aud` claim, so a client registered for one resource
2275 /// server could name another and receive a token this server had signed, with that other
2276 /// server's identifier in `aud`. The second server fetches our JWKS, the signature verifies,
2277 /// `aud` names it, and it authorises on a scope string the two happen to share. Scope-name
2278 /// collision between resource servers is the ordinary case, not an exotic one.
2279 ///
2280 /// SPLIT OUT OF [`AuthorizationServer::validate_resources`] because RFC 8693 section 2.1.1
2281 /// `audience` names the same target in a spelling that need not be a URI. It therefore has to
2282 /// skip the absolute-URI check and must not thereby skip this one, which is what it did through
2283 /// 0.9.1: an operator who decommissioned a resource server by removing it from the allowlist
2284 /// went on handing out signed tokens naming it, to any client holding a token whose grant
2285 /// recorded it, via `audience` on an exchange.
2286 ///
2287 /// An EMPTY allowlist keeps the previous behaviour rather than refusing everything, because
2288 /// refusing would break every deployment that already relies on resource indicators. That means
2289 /// this check protects only hosts that configure it, which is stated plainly on the config field
2290 /// rather than left for someone to discover.
2291 pub(crate) fn target_is_permitted(&self, value: &str) -> Result<(), ErrorResponse> {
2292 if let Some(allowed) = &self.config.allowed_resources {
2293 if !allowed.iter().any(|a| &**a == value) {
2294 // The offending value is NOT echoed, for the reason `validate_resources` gives:
2295 // RFC 6749 section 5.2 restricts `error_description` to a charset an
2296 // attacker-supplied value need not respect.
2297 return Err(ErrorResponse::new(ErrorCode::InvalidTarget)
2298 .with_description("this server does not issue tokens for that resource"));
2299 }
2300 }
2301 Ok(())
2302 }
2303
2304 /// The resource set an issuance actually gets: the request may NARROW what the grant carries,
2305 /// never widen it (RFC 8707 section 2).
2306 ///
2307 /// This is deliberately the same shape as the RFC 6749 section 6 scope rule that
2308 /// [`AuthorizationServer::refresh_token`] applies, because it is the same argument: the user
2309 /// approved a specific thing, and a later leg of the same grant asking for MORE than that is
2310 /// either a client bug or an escalation attempt, and the AS cannot tell which.
2311 ///
2312 /// A grant that named no resource has nothing to narrow, so a token request that names one is
2313 /// widening from nothing and is refused. Answering it any other way would let the token
2314 /// endpoint mint an audience the authorization request never obtained.
2315 ///
2316 /// # On the O(requested * granted) scan
2317 ///
2318 /// It needs no cap of its own, and deliberately does not have one, but the reason is now a
2319 /// count rather than the claim it used to make. That claim was that BOTH sides came through
2320 /// [`AuthorizationServer::validate_resources`], and it stopped being true when RFC 8693 token
2321 /// exchange started calling this: `granted` is still a validated grant record, but `requested`
2322 /// there is `resource` values (validated) PLUS section 2.1.1 `audience` values, which skip the
2323 /// URI-syntax check by design. What still bounds both sides is a CAP EACH:
2324 /// [`MAX_RESOURCE_INDICATORS`] on the resource list, and
2325 /// [`crate::token_exchange::MAX_AUDIENCE_VALUES`], deliberately the same number, on the
2326 /// audience list. So the worst case is 16 * 32 string comparisons, most of which fail on the
2327 /// length, and a third constant here would be a third number to keep in step with the other two
2328 /// for no gain.
2329 pub(crate) fn narrow_resources(
2330 granted: &[String],
2331 requested: &[String],
2332 ) -> Result<Vec<String>, ErrorResponse> {
2333 if requested.is_empty() {
2334 return Ok(granted.to_vec());
2335 }
2336 for want in requested {
2337 if !granted.iter().any(|g| g == want) {
2338 return Err(ErrorResponse::new(ErrorCode::InvalidTarget)
2339 .with_description("resource was not granted by the authorization request"));
2340 }
2341 }
2342 Ok(requested.to_vec())
2343 }
2344
2345 /// [`AuthorizationServer::narrow_resources`], then the allowlist ON WHAT SURVIVES.
2346 ///
2347 /// THE ALLOWLIST IS A PROPERTY OF THE ISSUED SET, NOT OF THE REQUESTED ONE, and that is the
2348 /// whole of this function. [`AuthorizationServer::target_is_permitted`] was consulted only
2349 /// about values a REQUEST named, and `narrow_resources` returns the grant's recorded list
2350 /// verbatim when the request names none, which is the ordinary case: a refresh sending no
2351 /// `resource`, an RFC 8693 exchange sending neither `resource` nor `audience`. So the values
2352 /// that reached the issued token on that path were never checked at all.
2353 ///
2354 /// What that cost is exactly what `target_is_permitted`'s own doc describes for the `audience`
2355 /// spelling, reached by a shorter road: an operator decommissions a resource server by removing
2356 /// it from [`ServerConfig::allowed_resources`], and every grant that had already recorded it
2357 /// goes on minting tokens naming it. Under `jwt` the issued set REPLACES the configured
2358 /// audience in the RFC 9068 `aud` claim, so those are freshly SIGNED tokens naming a server the
2359 /// operator believes they switched off, and with `refresh_token_ttl` defaulting to `None` the
2360 /// chain that mints them never expires.
2361 ///
2362 /// # Refused when NAMED, dropped when INHERITED, and the difference is what the client asked
2363 ///
2364 /// A request that NAMES a decommissioned target is asking for something this server will not
2365 /// issue, and RFC 8707 section 2's answer to that is `invalid_target`. That is what
2366 /// `narrow_resources` above already delivers, through the same
2367 /// [`AuthorizationServer::target_is_permitted`] every named value goes through.
2368 ///
2369 /// A request that names NOTHING has asked for whatever the grant still supports, so a target
2370 /// the server has since retired is DROPPED rather than made fatal. Refusing there would mean an
2371 /// operator retiring one resource server instantly breaks every live chain that ever recorded
2372 /// it, including clients that only ever call the ones still standing, over a value none of them
2373 /// mentioned. That is a bigger outage than the decommissioning itself, and it is avoidable.
2374 ///
2375 /// UNLESS NOTHING SURVIVES, which is the one case that must refuse. An empty resource list does
2376 /// not mean "no audience restriction" further down: [`AuthorizationServer::issue`] falls back
2377 /// to the configured [`crate::jwt::JwtConfig`] audience when the list is empty, so silently
2378 /// emptying a grant that DID name resources would WIDEN the token to this deployment's default
2379 /// audience, which is the opposite of what dropping was meant to do.
2380 pub(crate) fn narrow_and_permit(
2381 &self,
2382 granted: &[String],
2383 requested: &[String],
2384 ) -> Result<Vec<String>, ErrorResponse> {
2385 let issued = Self::narrow_resources(granted, requested)?;
2386 if !requested.is_empty() {
2387 // Every value here is one the request named, so each has already been through
2388 // `target_is_permitted` at its own endpoint, and naming a retired one is fatal there.
2389 return Ok(issued);
2390 }
2391 let permitted: Vec<String> = issued
2392 .into_iter()
2393 .filter(|value| self.target_is_permitted(value).is_ok())
2394 .collect();
2395 if permitted.is_empty() && !granted.is_empty() {
2396 return Err(
2397 ErrorResponse::new(ErrorCode::InvalidTarget).with_description(
2398 "this server no longer issues tokens for any resource this grant names",
2399 ),
2400 );
2401 }
2402 Ok(permitted)
2403 }
2404
2405 /// The storage seam, so the host can administer its own store.
2406 ///
2407 /// The one administrative operation this crate REQUIRES of the host is eviction:
2408 /// [`Storage::sweep_expired`] must be called on some host-chosen schedule, because nothing in
2409 /// this crate ever evicts anything on its own. There is no background task here and there will
2410 /// not be one (see the crate docs on zero cost until enabled), so a host that never sweeps has
2411 /// a store that only grows: consumed authorization codes and spent refresh records are
2412 /// retained ON PURPOSE until their expiry (that retention is what makes replay and reuse
2413 /// detectable), and expired access tokens and abandoned device grants are simply never looked
2414 /// at again. Anything else the host wants to do here, such as listing, is its own store's
2415 /// business and not this trait's.
2416 pub fn store(&self) -> &S {
2417 &self.store
2418 }
2419
2420 /// Register (or replace) a client the HOST provisioned: no policy is consulted, no credential
2421 /// is minted, and whatever is handed in is what the store holds.
2422 ///
2423 /// This is the out-of-band half of registration. RFC 7591 dynamic client registration is the
2424 /// other half and it is built:
2425 /// [`AuthorizationServer::register_dynamic_client`] layers on this one, adding the
2426 /// [`crate::registration::RegistrationPolicy`] check, the minted `client_id` and secret, and
2427 /// the RFC 7592 management credential. A host calling THIS method is asserting that the
2428 /// registration was authorised somewhere it can point to.
2429 ///
2430 /// # What IS checked, despite "no policy is consulted"
2431 ///
2432 /// Every `redirect_uris` entry, against the same rule RFC 7591 registration applies (RFC 6749
2433 /// section 3.1.2: an absolute URI with no fragment, and nothing outside printable ASCII, which
2434 /// RFC 3986 requires of a URI anyway). This is not policy: it is whether the value can work at
2435 /// all, and the two ways of creating a client used to disagree about it, with the DIRECT one —
2436 /// the one a default build has, since `http` and dynamic registration are optional — being the
2437 /// permissive half.
2438 ///
2439 /// The reason it is worth a refusal here rather than being left to fail later is WHERE it fails
2440 /// later, which is three layers away from its cause. A redirect URI containing a space passes
2441 /// the authorization endpoint's exact-string match, the host's resolver approves, an
2442 /// authorization code is MINTED AND PERSISTED, and only then does building the `Location`
2443 /// header fail: the user sees a 500, the client is never reached, and the code sits in storage
2444 /// until it expires while every retry does it again. Refusing at registration turns that into
2445 /// one error, at startup, in front of the person who wrote the value.
2446 ///
2447 /// A [`StorageError`] rather than a new error type, and it is the honest reading rather than a
2448 /// convenience: the answer is that this registration cannot be stored as given. The message
2449 /// names the offending URI, because the caller is the operator who just supplied it.
2450 pub async fn register_client(&self, client: Client) -> Result<(), StorageError> {
2451 for uri in &client.redirect_uris {
2452 // The SAME function `crate::registration`'s `redirect_uri_is_registerable` delegates
2453 // to, called directly rather than copied: two implementations of one rule is how the
2454 // two registration paths came to disagree in the first place.
2455 if !crate::authorization::is_valid_resource_indicator(uri) {
2456 return Err(StorageError::new(format!(
2457 "redirect_uri {uri:?} is not registerable: RFC 6749 s3.1.2 requires an \
2458 absolute URI with no fragment, and the authorization endpoint matches it by \
2459 exact string, so a registration this server cannot reproduce is a client that \
2460 can never complete a flow"
2461 )));
2462 }
2463 }
2464 // A registration whose `default_scopes` exceed its `allowed_scopes` is NOT refused here,
2465 // and that is a decision rather than an omission. It is a real state an operator reaches by
2466 // the control this crate tells them to reach for: `tests/registration_narrowing.rs` narrows
2467 // `allowed_scopes` alone, exactly as an operator correcting an over-broad registration
2468 // does, and refusing that would turn a security control into an error message. What made
2469 // the disagreement dangerous was the GRANT it produced, not the registration itself, and
2470 // that is closed at the point of issuance instead: see
2471 // `AuthorizationServer::granted_default_scope`, which trims the default to the allowance so
2472 // no grant is ever minted that the rotation ceiling would later destroy.
2473 self.store.put_client(client).await
2474 }
2475
2476 /// Verify `presented` against a stored verifier NOBODY holds the secret for, and throw the
2477 /// answer away.
2478 ///
2479 /// The point is the elapsed time, not the result: see the timing section on
2480 /// [`AuthorizationServer::authenticate_client`], which is the only caller. The scheme comes
2481 /// from the installed [`crate::client::SecretVerifier`] when it offers one, so the dummy costs
2482 /// what that host's real registrations cost; otherwise it is the crate's own `sha256-hex`,
2483 /// which prices the built-in scheme exactly and any other scheme not at all.
2484 ///
2485 /// `None` presented does no work, and that is not an omission FOR A SECRET: `verify_with`
2486 /// answers `false` for a confidential registration with no secret presented without verifying
2487 /// anything either, so doing nothing here is what MATCHES the known-id path rather than what
2488 /// diverges from it. It is NOT a general claim, and reading it as one is what left the RFC
2489 /// 7523 path uncovered through 0.9.0: a `private_key_jwt` request carries an assertion and no
2490 /// secret at all, so on that path the known id paid an ES256 verification and the unknown id
2491 /// paid nothing. [`AuthorizationServer::dummy_assertion_verify`] is that half.
2492 ///
2493 /// [`std::hint::black_box`] because the whole call is dead code by every rule the optimiser
2494 /// has: a pure function whose result is discarded. Without it the hashing this exists to spend
2495 /// is exactly what a release build is entitled to delete.
2496 fn dummy_verify(&self, presented: Option<&str>) {
2497 let presented = match presented {
2498 Some(p) => p,
2499 None => return,
2500 };
2501 let verifier = self.hooks.secret_verifier();
2502 let hash = match verifier.and_then(|v| v.dummy_hash()) {
2503 Some(hash) => hash,
2504 // The crate's own, over a constant that is not a secret and does not need to be: no
2505 // registration names this hash, so nothing authenticates by presenting the string it
2506 // was built from. It is a cost, not a credential.
2507 None => crate::client::SecretHash::sha256(
2508 "oauth-as dummy verification input; no registration is stored under this",
2509 ),
2510 };
2511 let dummy = crate::client::ClientAuth::ConfidentialSecretHash { hash };
2512 let _ = std::hint::black_box(dummy.verify_with(Some(presented), verifier));
2513 }
2514
2515 /// [`AuthorizationServer::dummy_verify`]'s twin for RFC 7523 client assertions: one ES256
2516 /// verification through the installed seam, over a key nobody registered, answer discarded.
2517 ///
2518 /// WHY IT IS NEEDED SEPARATELY. A `private_key_jwt` request carries a `client_assertion` and
2519 /// NO `client_secret` — `authenticate_by_assertion` refuses a request carrying both — so
2520 /// `dummy_verify` was handed `None` and returned immediately, while a KNOWN id on the same
2521 /// request went on to a real ES256 verification, which `crate::jwt` prices at about 133
2522 /// microseconds. The probed id is attacker-chosen and free: `crate::http` reads it from the
2523 /// UNSIGNED `sub` of the assertion when the form carries no `client_id`, and a garbage
2524 /// signature never reaches `claim_replay_id`, so the probe is repeatable and averageable while
2525 /// per-id throttling sees exactly one request per candidate — the same shape the secret case
2526 /// above describes.
2527 ///
2528 /// WHICH VERIFICATION, and why it is not always the ES256 one. RFC 7523 has TWO client
2529 /// authentication methods and this crate implements both: `private_key_jwt` is ES256 through
2530 /// the installed seam, and `client_secret_jwt` is an HS256 HMAC over the registered secret,
2531 /// which `verify_assertion` performs itself and which therefore needs no seam at all. So the
2532 /// real cost of a known id depends on the deployment, and the dummy tracks it: an ES256
2533 /// verification where a verifier is installed, and an HS256 tag verification where none is,
2534 /// which is exactly the shape of a `client_secret_jwt`-only deployment. This used to return
2535 /// early on a missing verifier on the grounds that "the real path refuses without verifying
2536 /// anything either", and that was true only of `private_key_jwt`: a `client_secret_jwt`
2537 /// deployment with no ES256 backend had its known ids paying an HMAC while unknown ids paid
2538 /// nothing.
2539 ///
2540 /// WHAT REMAINS, stated rather than left to be discovered: a deployment running BOTH methods
2541 /// can still be timed to tell which method a KNOWN, EXISTING client is registered for, because
2542 /// an HMAC and an ES256 verification are three orders of magnitude apart and no single dummy
2543 /// can be both. That is a much weaker fact than the one this closes: it says nothing about
2544 /// whether an id exists, so it is not an enumeration primitive, and it is only readable for an
2545 /// id the attacker already knows is registered.
2546 ///
2547 /// WHAT IT COSTS, stated rather than left to be found: a probe that could have reached a
2548 /// verification now buys one of this server's time whether or not its id exists. That is a
2549 /// denial-of-service consideration, it was already true for every KNOWN id, and the
2550 /// [`RateLimiter`] is charged for the attempt either way. The aim of the whole mechanism is
2551 /// that the two ids cost the same; see the residuals section on
2552 /// [`AuthorizationServer::authenticate_client`] for where they still do not, because that
2553 /// claim has now been false in three different ways across three audit rounds and stating it
2554 /// without the exceptions is what let each one survive.
2555 #[cfg(feature = "client-assertion")]
2556 fn dummy_assertion_verify(&self) {
2557 match self.es256_verifier() {
2558 Some(verifier) => {
2559 let _ = std::hint::black_box(verifier.verify(
2560 &dummy_assertion_key(),
2561 DUMMY_ASSERTION_SIGNING_INPUT.as_bytes(),
2562 &DUMMY_ASSERTION_SIGNATURE,
2563 ));
2564 }
2565 // The `client_secret_jwt` cost. The secret is the signing input itself, which is a
2566 // constant: as with the ES256 signature above this is a COST and not a credential, and
2567 // an HMAC costs the same whatever key it is handed.
2568 None => {
2569 let _ = std::hint::black_box(crate::jwt::verify_hs256(
2570 DUMMY_ASSERTION_SIGNING_INPUT.as_bytes(),
2571 DUMMY_ASSERTION_SIGNING_INPUT.as_bytes(),
2572 &DUMMY_ASSERTION_SIGNATURE[..32],
2573 ));
2574 }
2575 }
2576 }
2577
2578 /// Whether this request COULD have reached an assertion verification had its client id
2579 /// existed, which is the only condition under which charging
2580 /// [`AuthorizationServer::dummy_assertion_verify`] makes the two ids cost the same.
2581 ///
2582 /// It mirrors, exactly, the three refusals `authenticate_by_assertion` makes before it decodes
2583 /// a byte: an assertion has to be present, the RFC 7521 section 4.2 `client_assertion_type` has
2584 /// to be the one this server implements, and RFC 6749 section 2.3 forbids a `client_secret`
2585 /// alongside. Charging on the presence of the assertion ALONE, which is what this used to do,
2586 /// reopened the leak pointing the other way: a known id sending a garbage
2587 /// `client_assertion_type` was refused in nanoseconds while an unknown id sending the same
2588 /// bytes paid a full ES256 verification, so one request per candidate separated "registered"
2589 /// from "not registered" again. The two conditions have to be kept in step; if a refusal is
2590 /// ever added there before the verification, it belongs here too.
2591 #[cfg(feature = "client-assertion")]
2592 fn assertion_could_be_verified(cred: &ClientCredential<'_>) -> bool {
2593 cred.client_assertion.is_some()
2594 && cred.client_assertion_type == Some(CLIENT_ASSERTION_TYPE)
2595 && cred.client_secret.is_none()
2596 }
2597
2598 /// Authenticate a client for a token-plane call: unknown id and failed secret verification
2599 /// collapse into the same `invalid_client` so an attacker cannot probe which ids exist.
2600 ///
2601 /// The WIRE keeps that collapse. The host's audit channel does NOT: an installed
2602 /// [`crate::events::EventSink`] is told which of the two it actually was, because the host is
2603 /// not the attacker and "a thousand unknown client ids" and "a thousand wrong secrets for one
2604 /// real client" are different incidents.
2605 ///
2606 /// The host's [`RateLimiter`] is asked FIRST, before the store is touched, so a refused
2607 /// attempt costs nothing and reveals nothing (RFC 9700 section 4.13 on credential stuffing at
2608 /// the token endpoint).
2609 ///
2610 /// # The collapse is a TIMING property too, and how far this goes
2611 ///
2612 /// Answering both cases with the same code is worth nothing if the two take visibly different
2613 /// wall times, and the unknown-id path is naturally the cheaper one: there is no secret to
2614 /// verify. So this function performs a DUMMY verification when the id is unknown, through the
2615 /// same [`crate::client::ClientAuth::verify_with`] every real authentication goes through.
2616 ///
2617 /// What that covers exactly:
2618 ///
2619 /// - [`crate::client::ClientAuth::ConfidentialSecretHash`] in the built-in `sha256-hex`
2620 /// scheme, and [`crate::client::ClientAuth::ConfidentialSecret`], which this crate can price
2621 /// for itself because it performs the comparison itself.
2622 /// - A HOST scheme, whenever the installed [`crate::client::SecretVerifier`] implements
2623 /// [`crate::client::SecretVerifier::dummy_hash`]. This is the case that matters, because a
2624 /// host scheme is the expensive one.
2625 /// - RFC 7523 CLIENT ASSERTIONS, through
2626 /// [`AuthorizationServer::dummy_assertion_verify`]: a request presenting a
2627 /// `client_assertion` for an unknown id pays one verification, which is what the known id
2628 /// pays. This was uncovered through 0.9.0 because such a request carries no
2629 /// `client_secret`, so the secret dummy above had nothing to do and the doc for that
2630 /// `None` case read as though nothing needed doing.
2631 /// - THE ASSERTION CASE IN BOTH DIRECTIONS, which the first fix for it did not. The dummy is
2632 /// charged only when the request could have reached a verification at all
2633 /// (`assertion_could_be_verified`), and a KNOWN id whose registration does not use
2634 /// assertions pays it too before `authenticate_by_assertion` returns `WrongPrincipal`.
2635 /// Without the second half the mechanism ran backwards: an id registered for
2636 /// `client_secret_basic` refused in nanoseconds while an unknown id sending identical bytes
2637 /// paid 133 microseconds, which separates "registered, but not this way" from "not
2638 /// registered" in one un-throttleable request per candidate.
2639 ///
2640 /// - THE KNOWN-ID PATHS THAT DID NO VERIFICATION AT ALL, which is the same rule pointing the
2641 /// other way and had three separate instances. An expired `client_secret_expires_at` returns
2642 /// before every credential branch; `verify_with` answers `false` in nanoseconds for a
2643 /// `Public` or `ConfidentialAssertion` registration handed a posted secret; a mutual-TLS
2644 /// registration refuses on a thumbprint comparison. Each of those was FASTER than the
2645 /// unknown-id path, which pays `dummy_verify` through the host's scheme, so "fast" positively
2646 /// identified a registered id. Each is charged the dummy by the one exit below.
2647 ///
2648 /// - THE REFUSALS THAT PRECEDE THE ASSERTION VERIFICATION, which the fix for the case above
2649 /// introduced. `authenticate_by_assertion` refuses three requests before it decodes a byte,
2650 /// and all three are charged `dummy_verify` by the same exit, rather than through a comment
2651 /// asking two functions to be kept in step. They were not kept in step: a request carrying an
2652 /// assertion AND a `client_secret` was refused by a known id for free while an unknown id
2653 /// paid the host's secret scheme, so the fix for the previous bullet reopened the bullet
2654 /// before it. See the comment at the top of that function.
2655 ///
2656 /// # ONE EXIT, and why the list above is a history rather than a checklist
2657 ///
2658 /// Every bullet above was fixed at its own SITE, and each fix was found incomplete by the next
2659 /// audit round: round 7 added a dummy, round 8 found the dummy made an unknown id cost MORE
2660 /// than a known one, round 9 found three known-id paths costing nothing, and round 10 found
2661 /// that round 9's own guard had two refusal sites it did not reach. The diagnosis recorded here
2662 /// after round 10 was that the costly half is not the branching — it is that THE BRANCHES WERE
2663 /// ALSO THE EXITS. Every refusal returned from where it was decided, so every refusal had to
2664 /// remember to charge, and that is the obligation that was forgotten four times.
2665 ///
2666 /// So this function no longer refuses anywhere. It asks
2667 /// [`AuthorizationServer::classify_client_credential`] for a [`ClientAuthVerdict`], a VALUE, and
2668 /// there is exactly one place that turns a `Refused` into a wire answer. That place charges
2669 /// [`AuthorizationServer::settle_credential_cost`] unconditionally, records the failed attempt
2670 /// and emits the failure, in that order, for every refusal there is or ever will be. What a
2671 /// refusal costs is therefore a function of what the request PRESENTED (through
2672 /// [`CredentialCost`], which records only what was really spent) and of nothing the store holds,
2673 /// which is the property the four rounds above were each trying to reach one site at a time.
2674 ///
2675 /// A reviewer checks this by COUNTING: one `ClientAuthVerdict::Refused` arm, one
2676 /// `settle_credential_cost` call on it, and no `ErrorCode::InvalidClient` built anywhere in the
2677 /// credential path except there. Adding a refusal means adding a `return Ok(Refused(..))`, which
2678 /// cannot skip the charge because it does not do the charging.
2679 ///
2680 /// THE RATE-LIMIT GATE IS NOT ONE OF THOSE REFUSALS and is deliberately kept out of the count.
2681 /// It is answered before the store is touched, from a public `client_id` and nothing else, so it
2682 /// cannot vary with any fact about a registration — and charging a dummy verification there is
2683 /// exactly the amplification [`crate::rate_limit::CLIENT_AUTHENTICATION_FAILURE_CEILING_DIVISOR`]
2684 /// exists to bound: past the failure ceiling a denial is what an attacker can buy at
2685 /// [`crate::rate_limit::ATTEMPT_COST`] apiece, thousands per window per client id, and each one
2686 /// would then buy the host's argon2id as well. The gate refuses free, on purpose, and it is
2687 /// separated into [`AuthorizationServer::admit_client_authentication`] so that the credential
2688 /// decision below has one exit rather than nearly one.
2689 ///
2690 /// # What it does NOT cover, stated rather than left to be discovered
2691 ///
2692 /// FIRST, a host scheme whose verifier returns `None` from `dummy_hash`. This crate cannot
2693 /// invent a well-formed argon2id or bcrypt encoding to hand such a verifier, and one in the
2694 /// wrong scheme would be rejected on inspection in microseconds, which is the leak again. A
2695 /// deployment using a slow custom scheme SHOULD implement that method; until it does, the wire
2696 /// answer is still collapsed but the wall time is not. That residual is unfixable HERE, unlike
2697 /// the assertion one above, which is why the assertion one was closed rather than written down
2698 /// beside it.
2699 ///
2700 /// SECOND, WHICH RFC 7523 METHOD an id that is known to exist is registered for, in a
2701 /// deployment running both. `private_key_jwt` costs an ES256 verification and
2702 /// `client_secret_jwt` costs an HMAC, three orders of magnitude apart, and one dummy cannot be
2703 /// both; [`AuthorizationServer::dummy_assertion_verify`] picks whichever matches the
2704 /// deployment. This is not an enumeration primitive: it says nothing about whether an id
2705 /// exists, and it is readable only for one the attacker already knows does.
2706 ///
2707 /// THIRD, and in the same class: a `ConfidentialSecret` registration compares its secret with
2708 /// [`crate::client::constant_time_eq`], two SHA-256 digests, while the dummy an unknown id pays
2709 /// goes through the INSTALLED verifier's scheme. In a deployment that installs argon2id and
2710 /// still holds plaintext-secret registrations those differ by milliseconds, so such a
2711 /// registration is distinguishable from an unknown id. A deployment that stores hashes
2712 /// throughout, which is what [`crate::client::SecretHash`] exists for and what RFC 9700 section
2713 /// 4.13 expects, has nothing here to read. The structural fix above is what closes it properly;
2714 /// charging a second dummy on top of the real comparison would only make the same registration
2715 /// distinguishable in the other direction.
2716 ///
2717 /// FOURTH, and FOUND BY THIS RESTRUCTURE rather than closed by it, because closing it is not a
2718 /// mechanical change. A registration that DOES authenticate by assertion, handed an assertion
2719 /// that `verify_assertion` refuses before any signature work — one over
2720 /// [`crate::client_assertion::MAX_ASSERTION_BYTES`], one that is not a compact JWS, or one whose
2721 /// `alg` is not the registration's — pays nothing, while an unknown id sending the same bytes
2722 /// pays [`AuthorizationServer::dummy_assertion_verify`]. That separates "registered for RFC 7523
2723 /// with these keys" from "not registered", which is a narrower fact than the ones above (it is
2724 /// only readable for the assertion-registered subset) but it is the same shape. Closing it needs
2725 /// `verify_assertion` to say whether it reached the signature; deciding it from the returned
2726 /// [`crate::client_assertion::AssertionFailure`] variant would be exactly the "kept in step by a
2727 /// comment" coupling this restructure exists to remove. `CredentialCost::assertion` records
2728 /// where that boundary currently is.
2729 ///
2730 /// RFC 8705 mutual TLS is not on this list because there is nothing to price: the HOST
2731 /// verified the certificate before this crate saw it, and what happens here is a thumbprint
2732 /// comparison.
2733 pub(crate) async fn authenticate_client(
2734 &self,
2735 client_id: &ClientId,
2736 cred: &ClientCredential<'_>,
2737 ) -> Result<std::sync::Arc<Client>, ErrorResponse> {
2738 let attempt = Attempt::ClientAuthentication {
2739 client_id: client_id.as_str(),
2740 };
2741 self.admit_client_authentication(attempt, client_id)?;
2742
2743 // WHAT WAS SPENT, carried to the exit. Nothing reads it except `settle_credential_cost`,
2744 // and nothing writes it except the two places that perform a real verification.
2745 let mut paid = CredentialCost::default();
2746 // THE DECISION, as a value. Its `?` is a STORAGE failure and not a refusal: it is
2747 // `server_error`, it says nothing about whether the id exists (the store answered nothing at
2748 // all), and it is the same propagation every other endpoint in this crate makes.
2749 let verdict = self
2750 .classify_client_credential(client_id, cred, &mut paid)
2751 .await?;
2752 match verdict {
2753 ClientAuthVerdict::Authenticated(client) => {
2754 self.hooks.record(attempt, AttemptOutcome::Succeeded);
2755 Ok(client)
2756 }
2757 // THE ONE EXIT. Every refusal in the credential path arrives here, and this is the only
2758 // place any of them costs, records or reports anything.
2759 ClientAuthVerdict::Refused(failure) => {
2760 // UNCONDITIONAL, and the order is the one every site used before: charge, record,
2761 // emit. What is charged depends on what the request PRESENTED and on what has
2762 // already been spent for it — never on which branch decided the refusal.
2763 self.settle_credential_cost(cred, &paid);
2764 self.hooks.record(attempt, AttemptOutcome::Failed);
2765 self.hooks.emit(|| Event::ClientAuthenticationFailed {
2766 client_id: client_id.as_str(),
2767 failure,
2768 });
2769 // The one bare `invalid_client` (RFC 6749 section 5.2). The host's audit channel was
2770 // told which refusal it was one line up; the wire is told nothing, because the
2771 // difference between "no such client", "expired", "wrong secret" and "wrong kind of
2772 // credential" is exactly what tells a caller that an id is real.
2773 Err(ErrorResponse::new(ErrorCode::InvalidClient))
2774 }
2775 }
2776 }
2777
2778 /// The host's [`RateLimiter`] gate, asked FIRST, before the store is touched.
2779 ///
2780 /// SEPARATE FROM THE CREDENTIAL DECISION on purpose; see "ONE EXIT" on
2781 /// [`AuthorizationServer::authenticate_client`]. It refuses on a public `client_id` and nothing
2782 /// else, so it cannot vary with a fact about a registration, and it must stay FREE: a denial is
2783 /// what an attacker can buy in bulk once the failure ceiling is reached, so charging a dummy
2784 /// verification here would sell them the host's password hashing at
2785 /// [`crate::rate_limit::ATTEMPT_COST`] apiece.
2786 fn admit_client_authentication(
2787 &self,
2788 attempt: Attempt<'_>,
2789 client_id: &ClientId,
2790 ) -> Result<(), ErrorResponse> {
2791 if self.hooks.check(attempt) == RateLimitDecision::Deny {
2792 self.hooks.emit(|| Event::ClientAuthenticationFailed {
2793 client_id: client_id.as_str(),
2794 failure: ClientAuthFailure::RateLimited,
2795 });
2796 // The same `invalid_client` a wrong secret gets. A distinct code would tell an
2797 // attacker that they had found a live client id and merely hit the throttle.
2798 return Err(ErrorResponse::new(ErrorCode::InvalidClient));
2799 }
2800 Ok(())
2801 }
2802
2803 /// PAY FOR WHAT THIS REQUEST PRESENTED AND DID NOT GET, at the one exit that refuses.
2804 ///
2805 /// The unknown-id path is naturally the cheapest one — there is no secret to verify and no key
2806 /// to verify against — so the collapse of "no such client" and "wrong credential" into one
2807 /// `invalid_client` is worth nothing unless the two take the same wall time. Every refusal
2808 /// therefore ends here, and here spends whatever a request of this shape would have spent had it
2809 /// got as far as a verification:
2810 ///
2811 /// - [`AuthorizationServer::dummy_verify`] unless a real secret verification already ran. It
2812 /// does nothing when no secret was presented, which is what MATCHES the known-id path:
2813 /// `verify_with` refuses a confidential registration with no secret without verifying anything
2814 /// either.
2815 /// - [`AuthorizationServer::dummy_assertion_verify`] unless a real assertion verification
2816 /// already ran, and only when this request COULD have reached one
2817 /// ([`AuthorizationServer::assertion_could_be_verified`]) — a request every registration would
2818 /// have refused before decoding a byte must not pay for a verification on either path.
2819 ///
2820 /// Both conditions are facts about `cred` and about work already done. NEITHER is a fact about
2821 /// the registration, and that is the whole property: no caller of this can make a refusal cheap
2822 /// by knowing something about the client.
2823 fn settle_credential_cost(&self, cred: &ClientCredential<'_>, paid: &CredentialCost) {
2824 if !paid.secret {
2825 self.dummy_verify(cred.client_secret);
2826 }
2827 #[cfg(feature = "client-assertion")]
2828 if !paid.assertion && Self::assertion_could_be_verified(cred) {
2829 self.dummy_assertion_verify();
2830 }
2831 }
2832
2833 /// EXAMINE the presented credential and say what it amounts to. Charge nothing, record nothing,
2834 /// emit nothing: those belong to the single exit in
2835 /// [`AuthorizationServer::authenticate_client`], which is what keeps them from being forgotten.
2836 ///
2837 /// Every branch here is the one it was before this became a function of its own — the RFC 7591
2838 /// section 3.2.1 expiry gate, the RFC 7523 assertion path, the RFC 8705 mutual-TLS path and the
2839 /// shared-secret comparison, in that order — and each answers with a value instead of a wire
2840 /// refusal. `paid` is written by the two places that perform real verification work, so the exit
2841 /// can charge the remainder.
2842 async fn classify_client_credential(
2843 &self,
2844 client_id: &ClientId,
2845 cred: &ClientCredential<'_>,
2846 paid: &mut CredentialCost,
2847 ) -> Result<ClientAuthVerdict, ErrorResponse> {
2848 let found = self
2849 .store
2850 .get_client(client_id)
2851 .await
2852 .map_err(storage_error)?;
2853 let client = match found {
2854 Some(client) => client,
2855 // THE COLLAPSE IS ALSO A TIMING PROPERTY. Answering here in the time of one store read,
2856 // while a real id additionally pays a full secret verification, enumerates the whole
2857 // registry at one request per candidate: under the shape `with_secret_verifier` exists
2858 // to serve — a `ConfidentialSecretHash` in a host scheme such as argon2id — that is
2859 // roughly two milliseconds against two hundred, and per-id throttling cannot see it
2860 // because the attacker never repeats an id. Nothing is charged HERE any more; the exit
2861 // charges it, for this refusal and for every other one, through the same `verify_with`
2862 // seam a real authentication uses. See `SecretVerifier::dummy_hash` for the half only
2863 // the host can supply.
2864 None => return Ok(ClientAuthVerdict::Refused(ClientAuthFailure::UnknownClient)),
2865 };
2866 // RFC 7591 section 3.2.1 `client_secret_expires_at`. THIS SERVER MINTS THAT VALUE AND
2867 // PUBLISHES IT TO THE REGISTRANT, and until this check existed it never looked at it again:
2868 // a secret this deployment had itself declared dead on the wire went on authenticating
2869 // forever. A rotation window a server announces and does not enforce is worse than none,
2870 // because the operator believes the old secret stopped working on the day the response
2871 // said it would.
2872 //
2873 // Checked HERE, before every credential branch below, rather than beside the secret
2874 // comparison: `client_secret_jwt` (RFC 7523) also authenticates with the shared secret, so
2875 // a check attached only to the direct comparison would let the same expired secret keep
2876 // working through the assertion path. A registration with no secret carries `None` and is
2877 // unaffected, and `private_key_jwt` and mutual TLS do not authenticate with a secret at
2878 // all, so refusing them here costs nothing they could have used.
2879 //
2880 // Section 3.2.1: `0` means the secret NEVER expires. It is not "expired at the epoch", and
2881 // reading it that way would break every registration that took the default.
2882 if let Some(registration) = &client.registration {
2883 if let Some(expires_at) = registration.client_secret_expires_at {
2884 let expired = expires_at != 0
2885 && self
2886 .clock
2887 .now()
2888 .duration_since(std::time::UNIX_EPOCH)
2889 .map(|since| since.as_secs() >= expires_at)
2890 // A clock before the epoch cannot say anything has expired yet, and is the
2891 // host's problem rather than a reason to refuse a live credential.
2892 .unwrap_or(false);
2893 if expired {
2894 // NOTHING IS VERIFIED ON THIS PATH, and that is why it was once the fastest
2895 // refusal in the function: an expired registration answered in the time of one
2896 // store read while an unknown id paid a full `dummy_verify` through the host's
2897 // scheme, so "fast" meant "this id is registered", the one fact the bare
2898 // `invalid_client` exists to withhold. `paid` is untouched, so the exit charges
2899 // exactly what the unknown-id refusal above charges for the same request.
2900 return Ok(ClientAuthVerdict::Refused(ClientAuthFailure::SecretExpired));
2901 }
2902 }
2903 }
2904
2905 // RFC 7523 client authentication, when the request presented an assertion. Handled apart
2906 // from the secret comparison below because it is a different KIND of credential: there is
2907 // nothing to compare, there is a signature to verify against the REGISTRATION's key and a
2908 // `jti` to spend so the request cannot be repeated.
2909 #[cfg(feature = "client-assertion")]
2910 if cred.client_assertion.is_some() {
2911 // NOT boxed, and that is a measurement rather than a style, in both directions.
2912 //
2913 // It WAS `Box::pin(..)` through 0.9.0: `authenticate_client` inlines into all four
2914 // grant helpers and so into the token future, which `tests/allocation.rs` holds under
2915 // tokio's 2048-byte debug boxing threshold, and inlining the assertion state (a claim
2916 // set, two owned Strings, a storage future) once pushed it over. It no longer does.
2917 // Measured both ways: 1144 under `client-assertion` alone, 1256 with `rar`, 1344 with
2918 // `dpop,mtls,consent,rar,par` and with `--all-features`, IDENTICAL boxed and unboxed,
2919 // because the token future's high-water mark moved elsewhere when the endpoint was
2920 // restructured (see `token_with_context`).
2921 //
2922 // What the box was still costing is one allocation on every token request that
2923 // presents an assertion, which for a `private_key_jwt` deployment is every token
2924 // request it makes: precisely the deployments RFC 7523 exists for, and the ones FAPI
2925 // 2.0 requires it of. `client_assertion_verification_bound` in
2926 // `tests/allocation_paths.rs` pins the result.
2927 let outcome = self.authenticate_by_assertion(&client, cred, paid).await;
2928 return Ok(match outcome {
2929 Ok(()) => ClientAuthVerdict::Authenticated(client),
2930 // The reason is carried to the audit channel by the single exit. The wire answer is
2931 // one bare `invalid_client` for every one of them, which is why there is nothing
2932 // here to choose between.
2933 Err(reason) => {
2934 ClientAuthVerdict::Refused(ClientAuthFailure::AssertionInvalid { reason })
2935 }
2936 });
2937 }
2938
2939 // RFC 8705 s2 mutual-TLS client authentication, handled apart from the secret
2940 // comparison below for the same reason the assertion above is: it is a different
2941 // KIND of credential. There is nothing to compare; there is a certificate the HOST
2942 // verified, matched against what the registration says it expects to see.
2943 //
2944 // Dispatched on the REGISTRATION, never on what the request happened to present.
2945 // That direction is load bearing in both senses: a certificate presented by a
2946 // secret-authenticating client never reaches this path (it is for section 3 binding
2947 // only), and a mutual-TLS client can never fall through to the secret comparison
2948 // below.
2949 #[cfg(feature = "mtls")]
2950 if matches!(client.auth, crate::client::ClientAuth::Mtls { .. }) {
2951 return Ok(match crate::mtls::verify_certificate(&client, cred) {
2952 Ok(()) => ClientAuthVerdict::Authenticated(client),
2953 // A thumbprint comparison is microseconds, so an mTLS registration handed a
2954 // posted `client_secret` refuses far faster than an unknown id pays
2955 // `dummy_verify`. `paid` is untouched — no secret was verified — so the exit
2956 // charges the dummy, and it does nothing at all for the `None` a real mutual-TLS
2957 // request carries, which leaves the legitimate path unchanged.
2958 Err(failure) => ClientAuthVerdict::Refused(failure),
2959 });
2960 }
2961
2962 // WHICH REGISTRATIONS ACTUALLY VERIFY A SECRET, recorded BEFORE the call so the exit knows
2963 // what it still owes. `verify_with` answers `false` in nanoseconds for `Public` and for
2964 // `ConfidentialAssertion` (see the arms in `crate::client`: there is no presented string
2965 // that could be right for either), so a request posting junk as `client_secret` separated
2966 // those registrations from an unknown id by wall time alone — and the unknown id was the
2967 // SLOW one, argon2id milliseconds against nanoseconds under a host scheme. The two kinds
2968 // that DO verify are marked paid, so they are never charged twice.
2969 paid.secret = matches!(
2970 client.auth,
2971 crate::client::ClientAuth::ConfidentialSecret { .. }
2972 | crate::client::ClientAuth::ConfidentialSecretHash { .. }
2973 );
2974 // `verify_with` rather than `verify`: a registration stored as a hash in a scheme this
2975 // crate does not implement is decided by the host's verifier (see
2976 // `crate::client::SecretVerifier`), and by nobody at all when none is installed.
2977 if !client
2978 .auth
2979 .verify_with(cred.client_secret, self.hooks.secret_verifier())
2980 {
2981 return Ok(ClientAuthVerdict::Refused(
2982 ClientAuthFailure::SecretMismatch,
2983 ));
2984 }
2985 Ok(ClientAuthVerdict::Authenticated(client))
2986 }
2987
2988 /// RFC 7523 section 3, plus the single-use claim that makes it worth anything.
2989 ///
2990 /// Returns `Ok(())` for an authenticated client. Every refusal is the SAME bare
2991 /// `invalid_client` the wrong-secret path returns, with no description: this function is only
2992 /// reached once the client id is known to exist, so a description naming which check failed
2993 /// would be the difference between "this client id is real" and "it is not", which is exactly
2994 /// the distinction `authenticate_client` collapses on purpose. The host's audit channel is
2995 /// told (`ClientAuthFailure::AssertionInvalid { reason }`); the wire is not.
2996 ///
2997 /// RETURNS THE REASON rather than a built `ErrorResponse`, which is what makes that sentence
2998 /// true. Every refusal here is byte for byte the same bare `invalid_client`, so there was
2999 /// nothing for the caller to choose between and the `ErrorResponse` was constructed here only
3000 /// to be discarded by a `map_err(|_| ..)` that also discarded the `AssertionFailure` with it.
3001 /// The caller builds the one response and emits the reason, so the audit channel gets what
3002 /// `AssertionFailure` documents itself as existing for.
3003 #[cfg(feature = "client-assertion")]
3004 async fn authenticate_by_assertion(
3005 &self,
3006 client: &Client,
3007 cred: &ClientCredential<'_>,
3008 paid: &mut CredentialCost,
3009 ) -> Result<(), crate::client_assertion::AssertionFailure> {
3010 use crate::client_assertion::AssertionFailure;
3011 // THE THREE REFUSALS THAT PRECEDE ANY DECODING ARE ONE EXPRESSION, and it is the SAME
3012 // expression `authenticate_client` uses to decide whether to charge the assertion dummy on
3013 // the unknown-id path. They were three separate `if`s here and a predicate there, kept in
3014 // step by a comment, and a comment cannot hold an invariant across two functions.
3015 //
3016 // WHAT THAT COSTS WHEN IT DRIFTS — and the history matters, because 0.9.2 changed WHERE
3017 // this is paid and an auditor should not read the danger below as a hole 0.9.1 shipped.
3018 // `authenticate_client` enters this function on `cred.client_assertion.is_some()` ALONE,
3019 // so a request carrying an assertion AND a `client_secret` arrives here and is refused by
3020 // the RFC 6749 section 2.3 check below in nanoseconds. The UNKNOWN id sending identical
3021 // bytes takes the not-found arm, which charges `dummy_verify(cred.client_secret)`
3022 // unconditionally, and with a secret present that runs the host's `SecretVerifier` scheme:
3023 // argon2id milliseconds against nanoseconds. The assertion dummy balances nothing there,
3024 // because `assertion_could_be_verified` is false when a secret is present. One request per
3025 // candidate id, never repeated, sorts registered ids from unregistered ones, which is the
3026 // enumeration the whole mechanism exists to close.
3027 //
3028 // THAT STATE EXISTED DURING 0.9.1'S AUDIT ROUNDS AND WAS NOT RELEASED IN ONE. Released
3029 // 0.9.1 closed it HERE, with a `self.dummy_verify(cred.client_secret)` immediately before
3030 // the `Malformed` return below, so the known and unknown ids paid the same call for the
3031 // same request. (0.9.0 is a different shape again: it had no dummy verification anywhere,
3032 // so there was no asymmetry of this kind to have — the whole balancing mechanism arrived
3033 // in 0.9.1.) What 0.9.2 changed is that the charge is no longer made at this site, or at
3034 // the second site further down: see below.
3035 //
3036 // The three conditions, and why each refuses before anything is read:
3037 //
3038 // - An assertion has to be present at all.
3039 // - RFC 7521 section 4.2: the type is what says which assertion format this is, and this
3040 // server implements exactly one. An absent or unrecognised type is refused rather than
3041 // assumed, because assuming would mean verifying a credential in a format nobody
3042 // declared.
3043 // - RFC 6749 section 2.3: "The client MUST NOT use more than one authentication method in
3044 // each request." A request carrying both a secret and an assertion has not said which
3045 // credential it means, and a server that picks one behaves differently from the next
3046 // server, which is exactly the ambiguity an intermediary would exploit.
3047 //
3048 // All three answered `AssertionFailure::Malformed` before and all three answer it now:
3049 // there is no more specific variant for "this server will not read the string it was
3050 // handed", and the wire answer was one bare `invalid_client` for every one of them anyway.
3051 // So collapsing them changes what is SPENT and nothing else, on the wire or in the audit
3052 // channel.
3053 //
3054 // NOTHING IS CHARGED HERE ANY MORE, by either half of this function: `paid` is untouched on
3055 // every refusal below that did no verification, and the single exit in
3056 // `authenticate_client` spends what such a request would have spent. That is the 0.9.2
3057 // change, and it is a change of PLACE rather than of amount: 0.9.1 made two charges from
3058 // inside this function — a `dummy_verify` at the `Malformed` return just below, and a
3059 // `dummy_assertion_verify` in the `WrongPrincipal` arm after it — and each got its own
3060 // case right while neither could see what the other, or `authenticate_client`, had already
3061 // spent. `CredentialCost` is what a single exit needs in order to know.
3062 if !Self::assertion_could_be_verified(cred) {
3063 return Err(AssertionFailure::Malformed);
3064 }
3065 let assertion = cred.client_assertion.ok_or(AssertionFailure::Malformed)?;
3066
3067 // THE REGISTRATION DECIDES, and this is where that starts. A client registered for
3068 // `client_secret_basic` cannot promote itself to `private_key_jwt` by sending an assertion,
3069 // because there is no key here that anybody vouched for on its behalf.
3070 let keys = match &client.auth {
3071 crate::client::ClientAuth::ConfidentialAssertion { keys } => keys,
3072 // The registration does not authenticate this way at all, so there is no key any
3073 // assertion could have been signed with. `WrongPrincipal` is the closest true
3074 // statement: the party this credential claims to be is not the party it names.
3075 // NOTHING WAS VERIFIED, so `paid.assertion` stays false and the exit charges
3076 // `dummy_assertion_verify` for it. A registered id that does not use assertions
3077 // answering in nanoseconds, while an unknown id sending identical bytes paid a full
3078 // verification, is the registration KIND readable off the clock — and the kind is
3079 // exactly what such a probe is after, so the known path has to pay too.
3080 _ => return Err(AssertionFailure::WrongPrincipal),
3081 };
3082
3083 // RFC 7523 section 3 (3) admits either the token endpoint URL or, by long-established
3084 // practice (OpenID Connect Core section 9), the issuer identifier.
3085 //
3086 // The verifier is resolved and PASSED ALONG rather than required here, because only one of
3087 // the two methods needs one. `private_key_jwt` is ES256 and `verify_assertion` refuses it
3088 // on a `None` (an unchecked credential has authenticated nobody). `client_secret_jwt` is
3089 // an HS256 HMAC over the registered secret and touches no curve at all, so requiring a
3090 // backend on that path refused a valid credential for a reason no RFC gives. Which one
3091 // this registration is, is `client.auth`'s to say, and `AssertionKeys` is what says it.
3092 // CHARGED AS PAID AT THE CALL, not at its result. Every outcome from here on is one the
3093 // unknown-id path prices with a single `dummy_assertion_verify`, so charging a second one on
3094 // top would make a KNOWN id the slower of the two — which is how round 8 broke round 7. The
3095 // one gap this leaves is the refusals `verify_assertion` makes before it reaches a
3096 // signature; see the FOURTH residual on `authenticate_client`, and `CredentialCost`.
3097 paid.assertion = true;
3098 let verified = verify_assertion(
3099 self.es256_verifier(),
3100 keys,
3101 assertion,
3102 client.client_id.as_str(),
3103 &[self.token_endpoint(), self.issuer_identifier()],
3104 self.clock.now(),
3105 )?;
3106
3107 // RFC 7523 section 3: the `jti` is single use within the assertion's validity. THIS is the
3108 // check that makes an observed request unrepeatable, and it is the whole difference between
3109 // an authentication mechanism and a bearer credential that happens to be signed. It is
3110 // namespaced by client id so that two clients choosing the same `jti` (a counter, a
3111 // timestamp) cannot lock each other out.
3112 let claimed = self
3113 .store
3114 .claim_replay_id(
3115 &replay_key("ca", client.client_id.as_str(), &verified.jti),
3116 verified.expires_at,
3117 )
3118 .await
3119 // FAILING CLOSED. A claim that could not be recorded is a claim that did not happen,
3120 // and treating a storage outage as "probably fine" would turn every assertion into a
3121 // replayable one for the duration of the outage.
3122 //
3123 // REPORTED AS ITS OWN REASON, and not as `Replayed`, which is what it was through
3124 // 0.9.0. The wire answer is identical either way (`invalid_client`), so this is
3125 // entirely a question about the audit channel, and there the two are opposites:
3126 // `Replayed` is documented as a captured-and-replayed assertion, which
3127 // `crate::events` calls "a different incident and a much worse one", while this is the
3128 // store being unreachable. An outage fails EVERY `private_key_jwt` client at once, so
3129 // the mislabel fired a burst of this crate's worst-incident signal at the exact moment
3130 // an operator was reading the channel to find out what had broken. The DPoP twin below
3131 // propagates the same failure with `map_err(storage_error)`.
3132 .map_err(|_| AssertionFailure::ReplayCheckUnavailable)?;
3133 if !claimed {
3134 return Err(AssertionFailure::Replayed);
3135 }
3136 Ok(())
3137 }
3138
3139 /// The token endpoint URL this server answers on, which is what RFC 7523 section 3 (3) and RFC
3140 /// 9449 section 4.3 (7) compare against.
3141 ///
3142 /// Derived the same way `AuthorizationServerMetadata::from_config` derives it, and it MUST stay
3143 /// that way: the document tells a client where to send its request and what to put in `aud` and
3144 /// `htu`, so a server whose own idea of its token endpoint differs from the one it published
3145 /// refuses every conforming client.
3146 ///
3147 /// PRECOMPUTED at construction (see [`AuthorizationServer::with_clock`]) and borrowed here.
3148 /// The value is fixed for the life of the server, and this is called once per RFC 9449 proof
3149 /// verification and once per RFC 7523 assertion verification, so a `private_key_jwt` client
3150 /// sending DPoP paid two `format!`s of a constant on every token request. The crate already
3151 /// precomputes the metadata document, the JWKS and the JOSE header for exactly this reason.
3152 #[cfg(any(feature = "client-assertion", feature = "dpop"))]
3153 fn token_endpoint(&self) -> &str {
3154 &self.token_endpoint
3155 }
3156
3157 /// RFC 9449 section 4.3, and the single-use claim on the proof's `jti`.
3158 ///
3159 /// The proof is checked BEFORE the grant is looked at, because it binds to the REQUEST rather
3160 /// than to the grant: a proof that does not verify means this request is refused whatever it
3161 /// asked for, and spending its `jti` here means a replayed proof costs the attacker a lookup
3162 /// and gains them nothing.
3163 ///
3164 /// `htm` is `POST` because RFC 6749 section 3.2 makes the token endpoint POST-only, and `htu`
3165 /// is this server's own token endpoint rather than something the host passes in. That is
3166 /// deliberate: the value a conforming client puts in `htu` is the one it read from the RFC 8414
3167 /// document, which is exactly what `token_endpoint` returns, so taking it from the host would
3168 /// add a seam whose only possible use is to get it wrong.
3169 #[cfg(feature = "dpop")]
3170 async fn verify_dpop(&self, proof: Option<&str>) -> Result<Option<Box<str>>, ErrorResponse> {
3171 let proof = match proof {
3172 Some(proof) => proof,
3173 None if self.config.require_dpop => {
3174 return Err(ErrorResponse::new(ErrorCode::InvalidDpopProof)
3175 .with_description("this server requires a DPoP proof on every token request"))
3176 }
3177 None => return Ok(None),
3178 };
3179 // Resolved BEFORE the proof is parsed: with no backend there is nothing that could make
3180 // this proof acceptable, so an unauthenticated caller does not get to spend a base64 decode
3181 // and a JSON parse finding that out.
3182 let verifier = self.es256_verifier().ok_or_else(|| {
3183 // EMITTED, and this is the refusal it matters most to emit. `jwt` carries the verifier
3184 // seam and `jwt-p256` carries the arithmetic, so a build with `dpop` and neither an
3185 // installed verifier nor that backend refuses EVERY proof: the deployment is
3186 // misconfigured, not the client, and through 0.9.0 this was the one refusal the audit
3187 // channel never heard about at all. `UnsupportedAlgorithm` is the honest reading of
3188 // `DpopFailure`'s existing vocabulary — with no backend, ES256 is not an algorithm
3189 // this build accepts, whatever `dpop_signing_alg_values_supported` advertises.
3190 self.hooks.emit(|| Event::DpopProofRefused {
3191 failure: crate::dpop::DpopFailure::UnsupportedAlgorithm,
3192 });
3193 // BARE, like the nine section 4.3 checks below. The event above carries the reason,
3194 // which is where `dpop.rs` says the distinction belongs; the description would have
3195 // put it on the wire, and `verify_dpop` runs BEFORE any client authentication, so an
3196 // anonymous caller could read a deployment misconfiguration off a refusal.
3197 ErrorResponse::new(ErrorCode::InvalidDpopProof)
3198 })?;
3199 let verified = verify_proof(
3200 verifier,
3201 proof,
3202 "POST",
3203 self.token_endpoint(),
3204 self.clock.now(),
3205 )
3206 // THE REASON GOES TO THE AUDIT CHANNEL, and until 0.9.1 it went nowhere: this arm was
3207 // `map_err(|_| ..)` and no event was emitted at all, against `dpop.rs`'s statement that
3208 // "the distinction here is for the host's audit channel, not for the wire". A deployment
3209 // could therefore not tell a client with a skewed clock (`StaleProof`) from one whose
3210 // proofs are being captured and replayed (`Replayed`), which are a configuration problem
3211 // and an incident.
3212 .map_err(|failure| {
3213 self.hooks.emit(|| Event::DpopProofRefused { failure });
3214 ErrorResponse::new(ErrorCode::InvalidDpopProof)
3215 })?;
3216 // Namespaced by THUMBPRINT rather than by client id: a proof is bound to a key, not to a
3217 // registration (a public client's proof arrives before anything has authenticated), so the
3218 // key is the only identity available at this point that an attacker cannot choose freely.
3219 let claimed = self
3220 .store
3221 .claim_replay_id(
3222 &replay_key("dpop", &verified.jkt, &verified.jti),
3223 verified.replay_until,
3224 )
3225 .await
3226 .map_err(storage_error)?;
3227 if !claimed {
3228 // The single-use check is one of RFC 9449 section 4.3's, so its failure is reported
3229 // through the same channel as the nine `verify_proof` performs. It was the one
3230 // `DpopFailure::Replayed` nothing ever produced.
3231 self.hooks.emit(|| Event::DpopProofRefused {
3232 failure: crate::dpop::DpopFailure::Replayed,
3233 });
3234 // BARE, for the reason the no-verifier arm above gives and one more of its own. This
3235 // path is reached without authentication, and "already used" told an anonymous caller
3236 // that the proof they presented is in the replay cache, which confirms that its
3237 // signature, `htu`, `htm` and `iat` all PASSED: a captured proof could be tested for
3238 // freshness against the server that would otherwise have accepted it. The event
3239 // carries `DpopFailure::Replayed` to the host, which is the whole distinction.
3240 return Err(ErrorResponse::new(ErrorCode::InvalidDpopProof));
3241 }
3242 Ok(Some(verified.jkt.into_boxed_str()))
3243 }
3244
3245 /// The registered default, TRIMMED to what the registration also says may ever be granted.
3246 ///
3247 /// The trim is a fix for a defect the 0.9.1 rotation ceiling created rather than a
3248 /// belt-and-braces check. Nothing had ever compared `default_scopes` with `allowed_scopes`:
3249 /// both are plain public fields on [`crate::client::Client`], and a registration can reach a
3250 /// host's store without passing through [`AuthorizationServer::register_client`] at all. So a
3251 /// registration whose default exceeded its own allowance granted the default, and then
3252 /// [`AuthorizationServer::refresh_token`]'s ceiling refused the first rotation with
3253 /// `invalid_scope` and did NOT put the record back, destroying the chain permanently. That
3254 /// ceiling's premise is that "the client asked to continue a grant this server is no longer
3255 /// willing to honour", and the premise is false here: an identical fresh authorization request
3256 /// naming no scope mints the same grant again. Trimming is the reading that makes the two
3257 /// halves agree, and it is the fail-closed one: what is granted is exactly what the
3258 /// registration says may ever be granted.
3259 ///
3260 /// Written as a guarded clone so the OVERWHELMINGLY common case is byte for byte what this
3261 /// always did. THE SECOND ARM IS REACHABLE AND IS NOT DEAD CODE: `register_client` does NOT
3262 /// refuse a registration whose `default_scopes` exceed its `allowed_scopes`, deliberately, and
3263 /// says why in its own body -- narrowing `allowed_scopes` alone is exactly how an operator
3264 /// corrects an over-broad registration, and refusing that would turn a security control into
3265 /// an error message. This trim is what makes that safe, so deleting it as unreachable
3266 /// reintroduces the defect the paragraph above describes.
3267 ///
3268 /// Called from BOTH places the RFC 6749 section 3.3 default is applied, which is the other
3269 /// half of the fix: the authorization endpoint had its own copy of "absent means the registered
3270 /// default", and a rule with two implementations is a rule that drifts.
3271 fn granted_default_scope(client: &Client) -> ScopeSet {
3272 if client.default_scopes.is_subset(&client.allowed_scopes) {
3273 return client.default_scopes.clone();
3274 }
3275 ScopeSet::from_tokens(
3276 client
3277 .default_scopes
3278 .iter()
3279 .filter(|s| client.allowed_scopes.contains(s.as_str()))
3280 .map(|s| s.as_str()),
3281 )
3282 // Unreachable: every token here came out of a `ScopeSet` and so parsed once already.
3283 .unwrap_or_else(|_| ScopeSet::empty())
3284 }
3285
3286 /// Resolve the scope a request will be granted: the client default when the request names
3287 /// none, otherwise the request, which must sit inside the registration's allowed set.
3288 fn resolve_scope(
3289 client: &Client,
3290 requested: Option<&ScopeSet>,
3291 ) -> Result<ScopeSet, ErrorResponse> {
3292 match requested {
3293 // INTERSECTED, not taken whole, and that is a fix for a defect the 0.9.1 rotation
3294 // ceiling created rather than a belt-and-braces check. Nothing had ever compared
3295 // `default_scopes` with `allowed_scopes`: both are plain public fields on
3296 // `crate::client::Client`, and a registration can reach a host's store without passing
3297 // through `register_client` at all. So a registration whose default exceeded its own
3298 // allowance granted the default, and then `refresh_token`'s ceiling refused the first
3299 // rotation with `invalid_scope` and did NOT put the record back, destroying a chain
3300 // permanently. That ceiling's premise is that "the client asked to continue a grant
3301 // this server is no longer willing to honour", and that premise is false here: an
3302 // identical fresh authorization request naming no scope mints the same grant again.
3303 // Intersecting is the only reading that makes the two halves agree, and it is the
3304 // fail-closed one: what is granted is exactly what the registration says may ever be
3305 // granted. `register_client` does NOT refuse the disagreement -- see its body for why
3306 // not, and `granted_default_scope` for why this trim is therefore load bearing rather
3307 // than defensive.
3308 None => Ok(Self::granted_default_scope(client)),
3309 Some(s) if s.is_subset(&client.allowed_scopes) => Ok(s.clone()),
3310 // NB: descriptions must stay inside the RFC 6749 section 5.2 charset (no double
3311 // quote, no backslash), which scope tokens themselves already satisfy.
3312 // BORROWED, not built. This refusal is reachable by an UNAUTHENTICATED caller at the
3313 // authorization endpoint, at whatever rate they choose, and the `format!` this replaced
3314 // allocated a String and echoed the caller's own scope string into it. `tests/
3315 // allocation.rs` states the rule on `refused_token_request_allocation_bound`: a refusal
3316 // is work the attacker buys, so it does not get to buy a heap allocation. Roughly fifty
3317 // other refusal sites in this crate already hand back a `&'static str`; this one was
3318 // the exception. The developer who sent it still learns which parameter was wrong,
3319 // which is all RFC 6749 section 5.2 asks of `error_description`, and not echoing the
3320 // value back keeps it out of the host's logs as well.
3321 Some(_) => Err(ErrorResponse::new(ErrorCode::InvalidScope)
3322 .with_description("requested scope exceeds the client registration")),
3323 }
3324 }
3325
3326 /// RFC 8628 section 3.1/3.2: start a device authorization.
3327 pub async fn device_authorization(
3328 &self,
3329 client_id: &ClientId,
3330 client_secret: Option<&str>,
3331 requested_scope: Option<&ScopeSet>,
3332 ) -> Result<DeviceAuthorizationResponse, ErrorResponse> {
3333 self.device_authorization_with_credential(
3334 client_id,
3335 &ClientCredential::secret(client_secret),
3336 requested_scope,
3337 )
3338 .await
3339 }
3340
3341 /// RFC 8628 section 3.1/3.2 for a client authenticating with any credential this server
3342 /// accepts, including an RFC 7523 assertion.
3343 ///
3344 /// Added ALONGSIDE [`AuthorizationServer::device_authorization`] rather than replacing it: the
3345 /// three-argument form is what every existing host already calls and a shared secret remains
3346 /// the commonest credential. Both go through the same `authenticate_client`, so there is one
3347 /// authentication path and not two.
3348 pub async fn device_authorization_with_credential(
3349 &self,
3350 client_id: &ClientId,
3351 cred: &ClientCredential<'_>,
3352 requested_scope: Option<&ScopeSet>,
3353 ) -> Result<DeviceAuthorizationResponse, ErrorResponse> {
3354 // STAMPED BEFORE THE REGISTRATION IS READ, for the reason `client_credentials_token`
3355 // states at length: `created_at` is the instant a revocation barrier compares this grant
3356 // against at redemption, so it must predate the read the write is derived from. Taking it
3357 // after `authenticate_client` would date the grant later than a `delete_client` landing in
3358 // that window, and the comparison would then admit a token for a registration deleted
3359 // before the grant was even written.
3360 //
3361 // The device flow makes that worse than elsewhere, which is why it is worth the extra
3362 // line: `put_device_grant` consults no barrier at all, the approval that follows is a
3363 // compare-and-swap against a record that is present, and a device client is typically
3364 // PUBLIC — so a host re-provisioning the same `client_id` needs no secret for the polling
3365 // device to redeem it.
3366 let created_at = self.clock.now();
3367
3368 let client = self.authenticate_client(client_id, cred).await?;
3369 if !client.allows_grant(GrantType::DeviceCode) {
3370 return Err(ErrorResponse::new(ErrorCode::UnauthorizedClient)
3371 .with_description("client registration does not include the device_code grant"));
3372 }
3373 let scope = Self::resolve_scope(&client, requested_scope)?;
3374
3375 let now = self.clock.now();
3376 // `?` rather than a panic: this is an unauthenticated-shaped request path like any
3377 // other, and an OS that will not hand over 32 bytes is a `server_error`, not a reason to
3378 // abort the host's process. See `try_random_hex`.
3379 let device_code = try_random_hex(32).ok_or_else(randomness_error)?;
3380 let user_code = self.unique_user_code().await?;
3381 let grant = DeviceGrant {
3382 device_code: device_code.clone(),
3383 user_code: user_code.clone(),
3384 client_id: client.client_id.clone(),
3385 scope,
3386 state: DeviceGrantState::Pending,
3387 // Read at request ENTRY, above, not here. `expires_at` below stays measured from the
3388 // write, because the TTL is a promise about how long the user has to type the code.
3389 created_at,
3390 expires_at: saturating_deadline(now, self.config.device_code_ttl),
3391 interval: self.config.poll_interval,
3392 last_poll_at: None,
3393 };
3394 self.store
3395 .put_device_grant(grant)
3396 .await
3397 .map_err(storage_error)?;
3398
3399 let verification_uri_complete = self.config.include_verification_uri_complete.then(|| {
3400 // RFC 8628 s3.3.1: this is a DEEP LINK whose only job is to prefill the code, so
3401 // a `?` appended to a `verification_uri` that already carries a query does not
3402 // merely look wrong — it folds the code into the previous parameter's value and
3403 // the page prefills nothing. The same helper every authorization-response URL in
3404 // this crate uses, rather than a second answer to the same question.
3405 format!(
3406 "{}{}user_code={}",
3407 self.config.verification_uri,
3408 crate::authorization::query_separator(&self.config.verification_uri),
3409 user_code
3410 )
3411 });
3412 Ok(DeviceAuthorizationResponse {
3413 device_code,
3414 user_code,
3415 verification_uri: self.config.verification_uri.clone(),
3416 verification_uri_complete,
3417 expires_in: self.config.device_code_ttl.as_secs(),
3418 interval: self.config.poll_interval.as_secs(),
3419 })
3420 }
3421
3422 /// Draw a user code that no live grant already answers to.
3423 ///
3424 /// RFC 8628 section 6.1 sizes the user code for a human to type, which is exactly why it is
3425 /// short enough to collide: the birthday bound at the floor length is in the low hundreds of
3426 /// thousands of concurrent live grants. An accepted collision is not a cosmetic problem, it is
3427 /// two devices sharing one credential, and it corrupts the store's index for both.
3428 ///
3429 /// The draw is checked, not assumed. The check is advisory (another grant can be written
3430 /// between the lookup and the put), which is why [`Storage::put_device_grant`] is REQUIRED to
3431 /// refuse a collision outright: this loop keeps the common case cheap, the store keeps it
3432 /// correct.
3433 async fn unique_user_code(&self) -> Result<String, ErrorResponse> {
3434 // Clamped, not honoured: see `ServerConfig::user_code_length`.
3435 let len = self.config.user_code_length.max(MIN_USER_CODE_LENGTH);
3436 for _ in 0..USER_CODE_GENERATION_ATTEMPTS {
3437 // `None` is the OS refusing randomness, which is the same `server_error` the
3438 // storage failures in this loop become rather than a panic on a request path.
3439 let raw = random_user_code(len).ok_or_else(randomness_error)?;
3440 // The store indexes NORMALIZED codes, and `raw` is already the normalized form (the
3441 // alphabet is upper case and carries no hyphen), so this needs no second pass.
3442 if self
3443 .store
3444 .find_device_grant_by_user_code(&raw)
3445 .await
3446 .map_err(storage_error)?
3447 .is_none()
3448 {
3449 return Ok(display_user_code(&raw));
3450 }
3451 }
3452 Err(ErrorResponse::new(ErrorCode::ServerError)
3453 .with_description("could not allocate an unused user code"))
3454 }
3455
3456 /// Fetch a still-live pending grant by entered user code, for the verification UI actions,
3457 /// with the RFC 8628 section 5.1 throttle around it.
3458 ///
3459 /// `check` runs BEFORE the lookup, so a refused attempt learns nothing, and the outcome is
3460 /// reported back afterwards because a guessing attack is visible in FAILURES, not in traffic.
3461 /// Every rejection this can produce (unknown code, expired, already used) is counted the same
3462 /// way: an attacker enumerating codes does not care which one they get.
3463 async fn pending_grant_by_user_code(
3464 &self,
3465 entered_user_code: &str,
3466 ) -> Result<DeviceGrant, DeviceApprovalError> {
3467 let attempt = Attempt::DeviceUserCodeEntry;
3468 if self.hooks.check(attempt) == RateLimitDecision::Deny {
3469 return Err(DeviceApprovalError::RateLimited);
3470 }
3471 let outcome = self
3472 .lookup_pending_grant_by_user_code(entered_user_code)
3473 .await;
3474 self.hooks.record(
3475 attempt,
3476 if outcome.is_ok() {
3477 AttemptOutcome::Succeeded
3478 } else {
3479 AttemptOutcome::Failed
3480 },
3481 );
3482 outcome
3483 }
3484
3485 /// The lookup itself, split out so the throttle above wraps every exit from it.
3486 async fn lookup_pending_grant_by_user_code(
3487 &self,
3488 entered_user_code: &str,
3489 ) -> Result<DeviceGrant, DeviceApprovalError> {
3490 let normalized = normalize_user_code(entered_user_code);
3491 let grant = self
3492 .store
3493 .find_device_grant_by_user_code(&normalized)
3494 .await
3495 .map_err(DeviceApprovalError::Storage)?
3496 .ok_or(DeviceApprovalError::UnknownUserCode)?;
3497 if self.clock.now() >= grant.expires_at {
3498 // Expired: remove it so the user-facing answer and the poll path agree the code is
3499 // gone; the device's next poll will already find nothing (invalid_grant), which is
3500 // indistinguishable from a spent code and fine either way.
3501 let _ = self.store.take_device_grant(&grant.device_code).await;
3502 return Err(DeviceApprovalError::Expired);
3503 }
3504 if grant.state != DeviceGrantState::Pending {
3505 return Err(DeviceApprovalError::NotPending);
3506 }
3507 Ok(grant)
3508 }
3509
3510 /// The host's verification UI approves a grant for `subject` (the authenticated user).
3511 ///
3512 /// # The host MUST rate limit calls to this
3513 ///
3514 /// RFC 8628 section 5.1 is explicit that the user code's entropy is sufficient only IN
3515 /// COMBINATION WITH rate limiting: the code is short because a human types it, and an
3516 /// unthrottled verification endpoint turns "short enough to type" into "short enough to
3517 /// enumerate".
3518 ///
3519 /// This crate CAN throttle this call, and does: the first thing this method does is
3520 /// `pending_grant_by_user_code`, which asks the installed [`RateLimiter`] about an
3521 /// [`crate::events::Attempt::DeviceUserCodeEntry`] BEFORE the code is looked up, and reports
3522 /// the outcome back afterwards. [`DeviceApprovalError::RateLimited`] is what a refusal looks
3523 /// like from here. A limiter is shipped ([`crate::rate_limit::FixedWindowRateLimiter`]), and
3524 /// the crate's own `http` service installs nothing by default, so a host that installs none
3525 /// gets none. This paragraph said the opposite through 0.9.1 — "performs NO rate limiting and
3526 /// cannot" — which was a promise of absence beside code that consults the throttle on its
3527 /// first statement, and is the kind of doc that gets a host to build a second throttle or,
3528 /// worse, to conclude the risk is unavoidable.
3529 ///
3530 /// What remains the HOST's, and it is the important half: this crate has no notion of a
3531 /// caller, an IP, a session or a user, so it can only key the throttle on what it is given.
3532 /// An attacker who spreads guesses across the whole code space from many sources is visible
3533 /// to the host and not to this crate. Without a limiter installed, [`MIN_USER_CODE_LENGTH`]
3534 /// symbols is a guessing exercise, not a credential.
3535 pub async fn approve_device(
3536 &self,
3537 entered_user_code: &str,
3538 subject: impl Into<String>,
3539 ) -> Result<(), DeviceApprovalError> {
3540 let mut grant = self.pending_grant_by_user_code(entered_user_code).await?;
3541 let subject = subject.into();
3542 // Cloned ONLY when a sink is installed: `grant` is consumed by the put below, so the
3543 // event's fields have to be captured first, and an unobserved host must not pay for that.
3544 let audit = self
3545 .hooks
3546 .is_observed()
3547 .then(|| (grant.client_id.clone(), subject.clone()));
3548 grant.state = DeviceGrantState::Approved { subject };
3549 // COMPARE-AND-SWAP against `Pending`, never a blind put. The read above and this write are
3550 // separated by however long the host's store takes, and this is not the only writer: a
3551 // DENIAL from a second verification-UI action, or a poll, can land in between. A blind put
3552 // resolves that by whoever writes last, which for two decisions on one user code is
3553 // arbitrary, and the arbitrary direction that matters is an approval overwriting a refusal
3554 // the user already gave. FIRST DECISION WINS instead.
3555 //
3556 // `NotPending` rather than a new variant: it is the answer this call would have given had
3557 // it arrived a moment later and read the decided grant, so the host's verification UI needs
3558 // no new case to handle a race it could already reach by being slow.
3559 if !self
3560 .store
3561 .compare_and_swap_device_grant(&DeviceGrantState::Pending, grant)
3562 .await
3563 .map_err(DeviceApprovalError::Storage)?
3564 {
3565 return Err(DeviceApprovalError::NotPending);
3566 }
3567 // Emitted AFTER the write: an approval that failed to persist did not happen, and an audit
3568 // log that says otherwise is worse than none.
3569 if let Some((client_id, subject)) = &audit {
3570 self.hooks.emit(|| Event::DeviceGrantApproved {
3571 client_id: client_id.as_str(),
3572 subject,
3573 });
3574 }
3575 Ok(())
3576 }
3577
3578 /// The host's verification UI records the user's refusal.
3579 ///
3580 /// The same RFC 8628 section 5.1 obligation as [`AuthorizationServer::approve_device`] applies:
3581 /// this path also tells a caller whether a code exists, so the HOST must rate limit it too. An
3582 /// attacker enumerating codes does not care which of the two endpoints answers.
3583 pub async fn deny_device(&self, entered_user_code: &str) -> Result<(), DeviceApprovalError> {
3584 let mut grant = self.pending_grant_by_user_code(entered_user_code).await?;
3585 let audit = self.hooks.is_observed().then(|| grant.client_id.clone());
3586 grant.state = DeviceGrantState::Denied;
3587 // Same compare-and-swap, same reason: see `approve_device`. First decision wins in both
3588 // directions, so a refusal cannot overwrite an approval either.
3589 if !self
3590 .store
3591 .compare_and_swap_device_grant(&DeviceGrantState::Pending, grant)
3592 .await
3593 .map_err(DeviceApprovalError::Storage)?
3594 {
3595 return Err(DeviceApprovalError::NotPending);
3596 }
3597 if let Some(client_id) = &audit {
3598 self.hooks.emit(|| Event::DeviceGrantDenied {
3599 client_id: client_id.as_str(),
3600 });
3601 }
3602 Ok(())
3603 }
3604
3605 /// The token endpoint (RFC 6749 section 3.2; device grant per RFC 8628 section 3.4/3.5), for a
3606 /// request that names no RFC 8707 resource indicator.
3607 ///
3608 /// Equivalent to [`AuthorizationServer::token_with_resources`] with an empty list, which is
3609 /// what a token request carrying no `resource` parameter means: no NARROWING is asked for, so
3610 /// the issued token inherits whatever the grant already carries.
3611 ///
3612 /// # THIS FUTURE IS NOT CANCELLATION SAFE, and what a drop costs
3613 ///
3614 /// Applies equally to [`AuthorizationServer::token_with_resources`] and
3615 /// [`AuthorizationServer::token_with_context`], which are the same future.
3616 ///
3617 /// A Rust future stops at whatever `await` it is suspended in when it is dropped, and it never
3618 /// resumes. Two grants reached through here are TAKE-THEN-WRITE sequences, meaning they remove
3619 /// a single-use credential from the store and then persist what that credential became. A drop
3620 /// between the two leaves the first half done and nothing to finish it, and the crate cannot
3621 /// make a dropped future complete: there is no destructor that can run an `async` store call.
3622 /// So the CONTRACT is stated here rather than silently relied on, because until 0.9.1 a host
3623 /// had no way to learn it.
3624 ///
3625 /// Named exactly, because the cost differs:
3626 ///
3627 /// - `authorization_code`. The code is TAKEN (RFC 6749 s4.1.2's one-time use), then a CONSUMED
3628 /// record is written, then the tokens are issued, then that record is updated with what they
3629 /// were. A drop between the take and the consumed write is the most expensive one in the
3630 /// crate: the code is gone, so it cannot be redeemed twice, but RFC 9700 s4.1.1 replay
3631 /// DETECTION works by recognising a code that was already redeemed, and this leaves no record
3632 /// to recognise. A later replay of a code that leaked into a log, a `Referer` header or
3633 /// browser history reads as an unknown string, permanently and silently, for that grant. The
3634 /// write order was chosen to make a store FAILURE fall this way round rather than the other,
3635 /// and a drop lands in the same window that ordering shrank; it cannot close it.
3636 /// - `refresh_token`. The presented token is TAKEN, then a SPENT record is written, then the
3637 /// rotated chain is issued. A drop between the take and the spent write destroys the client's
3638 /// chain (the string it holds is gone and no replacement was persisted, so the user
3639 /// authenticates again) and loses the RFC 9700 s4.14.2 reuse marker for it, which is the same
3640 /// loss as above: a later presentation of that token is an unknown string rather than
3641 /// evidence of compromise, so it revokes no family.
3642 /// - The device grant's successful poll takes its grant record before issuing, with the same
3643 /// shape and the same cost as the code path.
3644 ///
3645 /// WHAT A HOST MUST DO. Drive this from a task the connection cannot cancel, and await THAT:
3646 /// spawn the call and await the join handle, so a disconnecting client aborts the response and
3647 /// not the work. This crate's own axum adapter does exactly that; see [`crate::http`]. A host
3648 /// that instead selects this future against a timeout, a shutdown signal, or a connection
3649 /// watcher is choosing every cost listed above, and choosing it at whatever rate its clients
3650 /// disconnect.
3651 pub fn token(
3652 &self,
3653 request: TokenRequest,
3654 ) -> impl std::future::Future<Output = Result<TokenResponse, ErrorResponse>> + '_ {
3655 self.token_with_resources(request, &[])
3656 }
3657
3658 /// The token endpoint with the RFC 8707 `resource` parameter.
3659 ///
3660 /// `resources` is the (possibly repeated) `resource` parameter from the token request, in wire
3661 /// order. It is a separate argument rather than a field on every [`TokenRequest`] variant on
3662 /// purpose: RFC 8707 section 2 defines `resource` as a parameter of the token REQUEST,
3663 /// independent of `grant_type`, so putting it on each variant would state the same thing four
3664 /// times, grow the enum every host copies around, and make every future grant type repeat it
3665 /// again.
3666 ///
3667 /// What it does depends on the grant, and section 2 is what decides:
3668 ///
3669 /// - `authorization_code` and `refresh_token` may NARROW to a subset of what the authorization
3670 /// request obtained, and never widen it;
3671 /// - `client_credentials` has no prior authorization request, so its resources are simply
3672 /// validated and used;
3673 /// - `urn:ietf:params:oauth:grant-type:device_code` refuses any resource with `invalid_target`.
3674 /// The device authorization request (RFC 8628 section 3.1) does not accept `resource` in this
3675 /// crate yet, so there is nothing granted for a poll to narrow to, and inventing an audience
3676 /// at the token endpoint that the user never approved is exactly what the narrowing rule
3677 /// exists to prevent.
3678 pub fn token_with_resources<'a>(
3679 &'a self,
3680 request: TokenRequest,
3681 resources: &'a [String],
3682 ) -> impl std::future::Future<Output = Result<TokenResponse, ErrorResponse>> + 'a {
3683 self.token_with_context(
3684 request,
3685 TokenRequestContext {
3686 resources,
3687 ..Default::default()
3688 },
3689 )
3690 }
3691
3692 /// The token endpoint with everything about the request that does not belong inside
3693 /// [`TokenRequest`]: the RFC 8707 resource indicators, the RFC 7523 client assertion, and the
3694 /// RFC 9449 DPoP proof.
3695 ///
3696 /// [`AuthorizationServer::token`] and [`AuthorizationServer::token_with_resources`] are this
3697 /// with an emptier context, so there is one implementation of the token endpoint and not three.
3698 /// Both of them are plain functions returning THIS future rather than `async fn`s that await
3699 /// it, and that is a measurement rather than a style: an `async fn` wrapper is a second
3700 /// generator frame holding its own copy of the 120-byte [`TokenRequest`] while the inner future
3701 /// holds another, and adding one pushed the token future over tokio's 2048-byte debug boxing
3702 /// threshold. `tests/allocation.rs` caught it.
3703 ///
3704 /// # Why THIS one is a plain function too, and not an `async fn`
3705 ///
3706 /// The same measurement, one level down, and it is the largest single saving on this path.
3707 /// An `async fn` stores its parameters TWICE: once as the coroutine's upvars, which is where
3708 /// they live before the first poll, and again as the locals they are moved into on that first
3709 /// poll. rustc does not overlay the two, so `request` (120 bytes) and `context` (104 bytes)
3710 /// were each counted twice for the whole life of the future. A plain function returning an
3711 /// `async move` block captures each ONCE, as an upvar the body reads directly.
3712 ///
3713 /// Measured on the RFC 6749 s4.1.3 arm, which is the widest: 2056 bytes as an `async fn`
3714 /// against 1824 as a block, both `--all-features`. The first of those is past tokio's
3715 /// threshold and costs a 2 KB heap allocation on every single token request.
3716 ///
3717 /// The client secret may be presented EITHER on the [`TokenRequest`] variant (where it has
3718 /// always lived) or on [`TokenRequestContext::credential`]; the context wins when both are set,
3719 /// and neither is silently dropped.
3720 // `manual_async_fn` is exactly the simplification this function must NOT take: see the
3721 // measurement in the doc comment above. An `async fn` here stores `request` and `context`
3722 // twice over and puts the token future past tokio's debug boxing threshold.
3723 #[allow(clippy::manual_async_fn)]
3724 pub fn token_with_context<'a>(
3725 &'a self,
3726 request: TokenRequest,
3727 context: TokenRequestContext<'a>,
3728 ) -> impl std::future::Future<Output = Result<TokenResponse, ErrorResponse>> + 'a {
3729 async move {
3730 let requested_resources =
3731 self.validate_resources(context.resources.iter().map(|r| r.as_str()))?;
3732 // RFC 9396 s5 and s6, parsed and type-checked ONCE here for the same reason the
3733 // resource indicators are validated once here: it is a parameter of the token
3734 // request itself, not of any one grant. The s5 type check has to run at THIS
3735 // endpoint too and not only at the authorization endpoint, because
3736 // `client_credentials` reaches issuance without ever passing the other one.
3737 #[cfg(feature = "rar")]
3738 let requested_details = match context.authorization_details {
3739 None => GrantedDetails::default(),
3740 Some(raw) => {
3741 let parsed = crate::rar::AuthorizationDetails::parse(raw)?;
3742 parsed.require_supported_types(
3743 self.config.authorization_details_types_supported.as_deref(),
3744 )?;
3745 GrantedDetails::of(&parsed)
3746 }
3747 };
3748 // And the build that supports NO type refuses the parameter outright, which is the
3749 // same s5 rule with the type list empty. Checked before the grant is looked up, so
3750 // the client hears about the parameter it sent rather than about the code it sent:
3751 // an `invalid_grant` for a request whose real defect is `authorization_details`
3752 // sends the client's author to the wrong half of the request.
3753 #[cfg(not(feature = "rar"))]
3754 if context.authorization_details.is_some() {
3755 return Err(ErrorResponse::new(ErrorCode::InvalidAuthorizationDetails)
3756 .with_description("this server does not support authorization_details"));
3757 }
3758 #[cfg(not(feature = "rar"))]
3759 let requested_details = GrantedDetails::default();
3760 // RFC 9449 s4.3, before anything else touches the store: see `verify_dpop`.
3761 //
3762 // NOT boxed, and that is a measurement. It was `Box::pin(..)` through 0.9.0, to keep
3763 // the proof-check state out of the token future, and by then the restructuring
3764 // recorded above (an `async move` block rather than an `async fn`, and matching
3765 // `request` by reference) had already bought back more than the box was saving.
3766 // Measured both ways on four feature sets, the future is byte for byte identical:
3767 // 1136 under `dpop` alone, 1248 with `rar`, 1280 with `mtls,consent,rar,par`, 1344
3768 // `--all-features`. So the box bought nothing and cost one 168-byte allocation on
3769 // EVERY token request under this feature, including every refusal, which is traffic an
3770 // attacker sets the rate of. `refused_token_request_allocation_bound` had been
3771 // carrying that as a named exception; it now asserts zero on every feature set.
3772 //
3773 // THIS ENDPOINT IS EXPENSIVE UNDER `dpop`, AND A HOST HAS TO SIZE ITS RATE LIMITER FOR
3774 // IT. A DPoP proof carrying a WRONG signature costs a full P-256 verification, MEASURED
3775 // at 133.18 us on the machine `benches/README.md` names, and it is indistinguishable
3776 // from a valid one until that verification finishes; a merely MALFORMED proof costs
3777 // 34 ns. So roughly 7,500 requests per second of well-formed garbage saturates a core,
3778 // and the caller need hold no credential to send them.
3779 //
3780 // The ORDERING was examined and deliberately left alone. Moving the proof check after
3781 // `authenticate_client` would put a cheap credential test first, but it cannot be done
3782 // in this function (each grant helper authenticates its own client, with its own
3783 // credential shape), and doing it inside each helper would CHANGE THE WIRE ANSWER: a
3784 // request with both a bad secret and a bad proof would answer `invalid_client` where it
3785 // now answers `invalid_dpop_proof`. That is error semantics, visible to every
3786 // conforming client, traded for a mitigation that is partial anyway, since a caller who
3787 // has any valid client credential (a public client id is one) pays nothing to get past
3788 // the reorder. RFC 9449 offers nothing cheaper to filter on either: the proof is signed
3789 // by a key the AS learns FROM the proof, so there is nothing to check before checking
3790 // the signature.
3791 //
3792 // The honest answer is therefore the rate limiter, not the ordering. See
3793 // `crate::rate_limit`.
3794 #[cfg(feature = "dpop")]
3795 let jkt = self.verify_dpop(context.dpop_proof).await?;
3796 // Matched by REFERENCE, and that is the second half of the same measurement the doc
3797 // comment above records. Moving the fields out of the enum does not free the enum's
3798 // own slot in the coroutine: `request` is an upvar, so its 120 bytes are reserved for
3799 // the whole life of the future either way, and the moved-out owned fields were then a
3800 // second 120 bytes of the same data live across the grant helper's await. Borrowing
3801 // costs the helpers nothing, because every one of them already takes `&str` /
3802 // `&ClientId` / `Option<&ScopeSet>`. Measured at 128 bytes of the all-features token
3803 // future, 1952 down to 1824.
3804 match &request {
3805 TokenRequest::AuthorizationCode {
3806 client_id,
3807 client_secret,
3808 code,
3809 redirect_uri,
3810 code_verifier,
3811 } => {
3812 let bound = Bound {
3813 cred: context.credential.or_secret(client_secret.as_deref()),
3814 #[cfg(feature = "dpop")]
3815 jkt: jkt.as_deref(),
3816 };
3817 let outcome = self
3818 .authorization_code_token(
3819 client_id,
3820 &bound,
3821 code,
3822 redirect_uri.as_deref(),
3823 code_verifier.as_deref(),
3824 &requested_resources,
3825 requested_details,
3826 )
3827 .await;
3828 self.emit_refusal(client_id, GrantType::AuthorizationCode, &outcome);
3829 outcome
3830 }
3831 TokenRequest::ClientCredentials {
3832 client_id,
3833 client_secret,
3834 scope,
3835 } => {
3836 let bound = Bound {
3837 cred: context.credential.or_secret(client_secret.as_deref()),
3838 #[cfg(feature = "dpop")]
3839 jkt: jkt.as_deref(),
3840 };
3841 let outcome = self
3842 .client_credentials_token(
3843 client_id,
3844 &bound,
3845 scope.as_ref(),
3846 requested_resources,
3847 requested_details,
3848 )
3849 .await;
3850 self.emit_refusal(client_id, GrantType::ClientCredentials, &outcome);
3851 outcome
3852 }
3853 TokenRequest::DeviceCode {
3854 client_id,
3855 client_secret,
3856 device_code,
3857 } => {
3858 // RFC 8707 s2: nothing was granted to narrow to, so a resource here would be an
3859 // audience the user never approved. See `token_with_resources`.
3860 if !requested_resources.is_empty() {
3861 return Err(
3862 ErrorResponse::new(ErrorCode::InvalidTarget).with_description(
3863 "the device authorization request granted no resource to narrow to",
3864 ),
3865 );
3866 }
3867 // RFC 9396 s6, and the same argument: the device authorization request
3868 // cannot carry authorization_details in this crate, so there is nothing
3869 // granted for this poll to narrow to, and minting detail here would be
3870 // authorizing something the user never saw.
3871 #[cfg(feature = "rar")]
3872 if !requested_details.is_empty() {
3873 return Err(ErrorResponse::new(ErrorCode::InvalidAuthorizationDetails)
3874 .with_description(
3875 "the device authorization request granted no authorization_details",
3876 ));
3877 }
3878 let bound = Bound {
3879 cred: context.credential.or_secret(client_secret.as_deref()),
3880 #[cfg(feature = "dpop")]
3881 jkt: jkt.as_deref(),
3882 };
3883 let outcome = self.device_token(client_id, &bound, device_code).await;
3884 self.emit_refusal(client_id, GrantType::DeviceCode, &outcome);
3885 outcome
3886 }
3887 TokenRequest::RefreshToken {
3888 client_id,
3889 client_secret,
3890 refresh_token,
3891 scope,
3892 } => {
3893 let bound = Bound {
3894 cred: context.credential.or_secret(client_secret.as_deref()),
3895 #[cfg(feature = "dpop")]
3896 jkt: jkt.as_deref(),
3897 };
3898 let outcome = self
3899 .refresh_token(
3900 client_id,
3901 &bound,
3902 refresh_token,
3903 scope.as_ref(),
3904 &requested_resources,
3905 requested_details,
3906 )
3907 .await;
3908 self.emit_refusal(client_id, GrantType::RefreshToken, &outcome);
3909 outcome
3910 }
3911 }
3912 }
3913 }
3914
3915 /// Emit [`Event::GrantRefused`] when a token-endpoint answer was an error.
3916 ///
3917 /// A non-async helper called from the arms of [`AuthorizationServer::token_with_resources`],
3918 /// where the client id is already an owned local. Deliberately NOT a wrapper around the whole
3919 /// endpoint: see the note in the 0.2.0 hook patch, and `tests/allocation.rs`.
3920 fn emit_refusal(
3921 &self,
3922 client_id: &ClientId,
3923 grant_type: GrantType,
3924 outcome: &Result<TokenResponse, ErrorResponse>,
3925 ) {
3926 if let Err(error) = outcome {
3927 self.hooks.emit(|| Event::GrantRefused {
3928 client_id: client_id.as_str(),
3929 grant_type,
3930 error: error.error,
3931 });
3932 }
3933 }
3934
3935 /// Validate an authorization request (RFC 6749 section 4.1.1) before any user interaction.
3936 ///
3937 /// The order of checks is dictated by RFC 6749 section 4.1.2.1 and is a security boundary,
3938 /// not a style choice: the client and the redirect URI are validated FIRST, because until
3939 /// they are, there is no address the server may safely send an error to. Everything checked
3940 /// afterwards is reported by redirecting to the (now validated) URI.
3941 ///
3942 /// On success the host shows its consent UI and then calls
3943 /// [`AuthorizationServer::issue_authorization_code`], or reports
3944 /// [`ValidatedAuthorizationRequest::denied`] if the user refuses.
3945 pub async fn validate_authorization_request(
3946 &self,
3947 request: &AuthorizationRequest<'_>,
3948 ) -> Result<ValidatedAuthorizationRequest, AuthorizationError> {
3949 // The POLICY gate, ahead of the validation itself: a deployment may declare that
3950 // parameters in the query are not an acceptable way to ask for authorization at all.
3951 //
3952 // RFC 9126 section 4 lets a server require PAR globally, and RFC 9101 section 10.5
3953 // requires the equivalent for signed request objects, both for the same reason: an
3954 // attacker who can rewrite the browser's URL will simply strip the protection and send a
3955 // plain RFC 6749 request unless the server refuses one. This is the only entry point that
3956 // takes query parameters, so refusing here is what makes the policy hold; `par.rs` reaches
3957 // the validation below directly, having already established that the request was pushed or
3958 // signed.
3959 #[cfg(feature = "par")]
3960 if matches!(&self.config.par, Some(par) if par.require_pushed_authorization_requests) {
3961 return Err(AuthorizationError::Direct(
3962 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
3963 "this server accepts authorization request data only via PAR (RFC 9126 s4)",
3964 ),
3965 ));
3966 }
3967 #[cfg(feature = "jar")]
3968 if matches!(&self.config.jar, Some(jar) if jar.require_signed_request_object) {
3969 return Err(AuthorizationError::Direct(
3970 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
3971 "this server requires a signed request object (RFC 9101 s10.5)",
3972 ),
3973 ));
3974 }
3975 self.validate_direct_authorization_request(request).await
3976 }
3977
3978 /// The validation itself, with no policy gate in front of it.
3979 ///
3980 /// Split out for RFC 9126 / RFC 9101: a pushed or signed request has ALREADY satisfied the
3981 /// policy the wrapper above enforces, and it arrives as parameters rather than as a query, so
3982 /// it needs this and not the wrapper. Everything else about it is unchanged, which is the
3983 /// point: the PAR endpoint validates a pushed request by calling exactly the function the
3984 /// authorization endpoint calls, so the two cannot drift.
3985 pub(crate) async fn validate_direct_authorization_request(
3986 &self,
3987 request: &AuthorizationRequest<'_>,
3988 ) -> Result<ValidatedAuthorizationRequest, AuthorizationError> {
3989 // THE THROTTLE, and this endpoint had none. RFC 9700 section 4.13 is about credential
3990 // stuffing at the token endpoint; this one takes NO CREDENTIAL, which is precisely why it
3991 // needs a bound of its own rather than sharing `Attempt::ClientAuthentication`'s. What a
3992 // deployment is bounding here is work and storage: every request costs a `get_client`, and
3993 // an approved one goes on to WRITE an authorization code record that nothing but
3994 // `Storage::sweep_expired` ever reclaims.
3995 //
3996 // Asked FIRST, before the store is touched, for the same reason `authenticate_client` asks
3997 // first: a refused attempt must cost nothing and reveal nothing.
3998 //
3999 // `temporarily_unavailable` (RFC 6749 section 4.1.2.1) rather than `invalid_request`,
4000 // because nothing about the request was wrong and a client that retries later will
4001 // succeed. It is DIRECT rather than a redirect: at this point neither the client nor the
4002 // redirect URI has been validated, so there is no address this server may safely send
4003 // anything to.
4004 let attempt = Attempt::AuthorizationRequest {
4005 client_id: request.client_id.as_deref().unwrap_or(""),
4006 };
4007 if self.hooks.check(attempt) == RateLimitDecision::Deny {
4008 return Err(AuthorizationError::Direct(
4009 ErrorResponse::new(ErrorCode::TemporarilyUnavailable)
4010 .with_description("too many authorization requests; retry later"),
4011 ));
4012 }
4013 let outcome = self
4014 .validate_direct_authorization_request_inner(request)
4015 .await;
4016 // Reported back so a limiter can count FAILURES rather than traffic, exactly as the token
4017 // plane does. A refused authorization request is the signal worth counting here: a caller
4018 // walking client ids, or replaying a malformed request, produces nothing else.
4019 self.hooks.record(
4020 attempt,
4021 match &outcome {
4022 Ok(_) => AttemptOutcome::Succeeded,
4023 Err(_) => AttemptOutcome::Failed,
4024 },
4025 );
4026 outcome
4027 }
4028
4029 /// The validation itself, with the throttle above already satisfied.
4030 ///
4031 /// Split out so that every `?` below reports its refusal to the limiter through ONE place. The
4032 /// alternative, threading `hooks.record` through a dozen early returns, is the shape that ends
4033 /// with one path that forgot to.
4034 async fn validate_direct_authorization_request_inner(
4035 &self,
4036 request: &AuthorizationRequest<'_>,
4037 ) -> Result<ValidatedAuthorizationRequest, AuthorizationError> {
4038 // `&'static str`: every description below is a constant naming a condition, never a
4039 // value out of the request, so the refusal borrows it rather than copying it. The
4040 // authorization endpoint is unauthenticated, so its refusal rate is the attacker's to
4041 // choose.
4042 let direct = |code: ErrorCode, why: &'static str| {
4043 AuthorizationError::Direct(ErrorResponse::new(code).with_description(why))
4044 };
4045
4046 // 1. The client. An unknown client_id and a malformed one collapse into one answer: the
4047 // user agent is untrusted here, and telling it which client ids exist helps nobody.
4048 let client_id = request
4049 .client_id
4050 .as_deref()
4051 .filter(|s| !s.is_empty())
4052 .ok_or_else(|| direct(ErrorCode::InvalidRequest, "missing client_id"))?;
4053 let client = self
4054 .store
4055 .get_client(&ClientId::new(client_id))
4056 .await
4057 .map_err(|_| direct(ErrorCode::ServerError, "storage unavailable"))?
4058 .ok_or_else(|| direct(ErrorCode::InvalidRequest, "unknown client_id"))?;
4059
4060 // 2. The redirect URI. OAuth 2.1 section 4.1.3 requires exact string comparison: no
4061 // prefix matching, no ignoring a trailing slash, no normalising case. Every relaxation
4062 // of this rule has a published attack behind it.
4063 //
4064 // WHETHER IT WAS SENT is recorded as well as what it resolved to, because RFC 6749
4065 // section 4.1.3 makes the token endpoint's copy of this parameter conditional on the
4066 // authorization request having carried one. The resolved value below cannot answer that
4067 // question: the section 3.1.2.3 omission path fills it in from the registration, so both
4068 // cases arrive at the token endpoint looking identical.
4069 let redirect_uri_was_explicit = request.redirect_uri.is_some();
4070 let redirect_uri = match request.redirect_uri.as_deref() {
4071 Some(requested) => client
4072 .redirect_uris
4073 .iter()
4074 .find(|registered| registered.as_str() == requested)
4075 .cloned()
4076 .ok_or_else(|| {
4077 direct(
4078 ErrorCode::InvalidRequest,
4079 "redirect_uri does not exactly match a registered URI",
4080 )
4081 })?,
4082 // RFC 6749 section 3.1.2.3: the request may omit it only when there is exactly one
4083 // registration to mean. With several, the server would be guessing where to send a
4084 // credential, and a wrong guess is the whole attack.
4085 //
4086 // ZERO AND SEVERAL ARE ONE ANSWER, and that is the same rule step 1 above states.
4087 // Separate descriptions ("client has no registered redirect_uri" against "redirect_uri
4088 // is required when several are registered") sorted every client id into three buckets
4089 // by how many URIs it has registered, from one UNAUTHENTICATED request carrying
4090 // nothing but a `client_id`. Combined with the unknown-id refusal that is four
4091 // distinguishable answers about a registration the caller has proved no relationship
4092 // to, which defeats the collapse the comment on step 1 promises. Neither case can be
4093 // repaired by the client anyway: both are answered by SENDING a `redirect_uri`, and
4094 // the string below says so.
4095 None => match client.redirect_uris.as_slice() {
4096 [only] => only.clone(),
4097 _ => {
4098 return Err(direct(
4099 ErrorCode::InvalidRequest,
4100 "redirect_uri is required for this client",
4101 ))
4102 }
4103 },
4104 };
4105
4106 // From here the redirect URI is trusted, so errors go back to the client (section
4107 // 4.1.2.1) carrying the state that lets it correlate them.
4108 let state = request.state.as_deref().map(str::to_string);
4109 let redirect = |code: ErrorCode, why: &'static str| {
4110 AuthorizationError::Redirect(AuthorizationErrorRedirect {
4111 redirect_uri: redirect_uri.clone(),
4112 error: ErrorResponse::new(code).with_description(why),
4113 state: state.clone(),
4114 // RFC 9207 section 2: every authorization response, including this one, names the
4115 // server that produced it.
4116 iss: self.issuer_identifier().to_string(),
4117 })
4118 };
4119
4120 // 3. response_type. OAuth 2.1 removes the implicit grant, so `token` is not merely
4121 // unsupported by this server, it is gone from the protocol.
4122 match request.response_type.as_deref() {
4123 Some("code") => {}
4124 None => return Err(redirect(ErrorCode::InvalidRequest, "missing response_type")),
4125 Some(_) => {
4126 return Err(redirect(
4127 ErrorCode::UnsupportedResponseType,
4128 "this server issues authorization codes only",
4129 ))
4130 }
4131 }
4132
4133 if !client.allows_grant(GrantType::AuthorizationCode) {
4134 return Err(redirect(
4135 ErrorCode::UnauthorizedClient,
4136 "client registration does not include the authorization_code grant",
4137 ));
4138 }
4139
4140 // 4. PKCE. OAuth 2.1 requires it for every authorization code request. RFC 7636 section
4141 // 4.3 defaults an absent code_challenge_method to `plain`, which this server does not
4142 // implement and does not advertise, so an absent method is refused rather than
4143 // silently downgraded.
4144 match request.code_challenge_method.as_deref() {
4145 Some("S256") => {}
4146 None => {
4147 return Err(redirect(
4148 ErrorCode::InvalidRequest,
4149 "code_challenge_method=S256 is required",
4150 ))
4151 }
4152 Some(_) => {
4153 return Err(redirect(
4154 ErrorCode::InvalidRequest,
4155 "only code_challenge_method=S256 is supported",
4156 ))
4157 }
4158 }
4159 let code_challenge = request.code_challenge.as_deref().unwrap_or_default();
4160 if !challenge_is_well_formed(code_challenge) {
4161 // A malformed challenge can never match any verifier, so accepting it would issue a
4162 // code that is guaranteed to fail redemption later, with a misleading error.
4163 return Err(redirect(
4164 ErrorCode::InvalidRequest,
4165 "code_challenge must be the base64url SHA-256 form of RFC 7636 section 4.2",
4166 ));
4167 }
4168
4169 // 5. Scope. RFC 6749 section 3.3: absent means the registered default, trimmed to what the
4170 // registration allows. See `granted_default_scope`, which is shared with `resolve_scope`
4171 // precisely so this endpoint and the token endpoint cannot answer differently.
4172 let scope = match request.scope.as_deref() {
4173 None => Self::granted_default_scope(&client),
4174 Some(s) => {
4175 let requested = ScopeSet::parse(s)
4176 .map_err(|_| redirect(ErrorCode::InvalidScope, "malformed scope"))?;
4177 if !requested.is_subset(&client.allowed_scopes) {
4178 return Err(redirect(
4179 ErrorCode::InvalidScope,
4180 "requested scope exceeds the client registration",
4181 ));
4182 }
4183 requested
4184 }
4185 };
4186
4187 // 6. RFC 8707 resource indicators. Checked LAST of the redirectable checks because it is
4188 // the newest and least load-bearing of them: a request that is also missing PKCE should
4189 // hear about PKCE. A malformed indicator is `invalid_target` (section 2), reported to
4190 // the client rather than to the user, since by here the redirect URI is trusted.
4191 let resource = self
4192 .validate_resources(request.resource.iter().map(|r| r.as_ref()))
4193 .map_err(|e| {
4194 AuthorizationError::Redirect(AuthorizationErrorRedirect {
4195 redirect_uri: redirect_uri.clone(),
4196 error: e,
4197 state: state.clone(),
4198 iss: self.issuer_identifier().to_string(),
4199 })
4200 })?;
4201
4202 // RFC 9396 authorization_details, checked last among the redirectable checks for
4203 // the same reason `resource` is checked late: it is the newest of them, and a
4204 // request that is also missing PKCE should hear about PKCE. Reported to the client
4205 // rather than to the user, since by here the redirect URI is trusted, and REFUSED
4206 // rather than ignored (section 5), because a client whose authorization detail was
4207 // silently dropped would obtain a token it believes says something it does not.
4208 //
4209 // THE BUILD WITHOUT `rar` REFUSES EVERY ONE OF THEM, which is the stronger case and not
4210 // an absent one: it supports no authorization detail type whatsoever, so section 5's
4211 // condition holds for any value at all and there is nothing to parse before answering.
4212 // Same posture as the RFC 9101 request object path in `crate::par`, which already said
4213 // this; this is the plain query request, and the RFC 9126 push, which reaches here too.
4214 #[cfg(not(feature = "rar"))]
4215 if request.authorization_details.is_some() {
4216 return Err(AuthorizationError::Redirect(AuthorizationErrorRedirect {
4217 redirect_uri: redirect_uri.clone(),
4218 error: ErrorResponse::new(ErrorCode::InvalidAuthorizationDetails)
4219 .with_description("this server does not support authorization_details"),
4220 state: state.clone(),
4221 iss: self.issuer_identifier().to_string(),
4222 }));
4223 }
4224
4225 #[cfg(feature = "rar")]
4226 let details = {
4227 let to_redirect = |error: ErrorResponse| {
4228 AuthorizationError::Redirect(AuthorizationErrorRedirect {
4229 redirect_uri: redirect_uri.clone(),
4230 error,
4231 state: state.clone(),
4232 iss: self.issuer_identifier().to_string(),
4233 })
4234 };
4235 match request.authorization_details.as_deref() {
4236 None => crate::rar::AuthorizationDetails::none(),
4237 Some(raw) => {
4238 let parsed =
4239 crate::rar::AuthorizationDetails::parse(raw).map_err(to_redirect)?;
4240 parsed
4241 .require_supported_types(
4242 self.config.authorization_details_types_supported.as_deref(),
4243 )
4244 .map_err(to_redirect)?;
4245 parsed
4246 }
4247 }
4248 };
4249
4250 // RFC 9470 s4's `acr_values` and `max_age`, parsed from THIS request rather than from the
4251 // query the user agent arrived with. That distinction is the whole point: for an RFC 9126
4252 // pushed request `request` is the stored record, and for an RFC 9101 signed one it is the
4253 // verified claim set, so the two parameters now survive both (they were dropped entirely
4254 // before, which disabled step-up for every PAR and JAR deployment) and the query cannot
4255 // supply them for either (RFC 9126 s4, RFC 9101 s6.3, which says the server MUST use only
4256 // the object's parameters even when the query repeats them).
4257 //
4258 // Checked last among the redirectable checks, so a request that is ALSO missing PKCE still
4259 // hears about PKCE first; that is the order the endpoint reported before this moved here.
4260 #[cfg(feature = "consent")]
4261 let requirement = crate::consent::AuthenticationRequirement::from_request(request)
4262 .map_err(|error| {
4263 AuthorizationError::Redirect(AuthorizationErrorRedirect {
4264 redirect_uri: redirect_uri.clone(),
4265 error,
4266 state: state.clone(),
4267 iss: self.issuer_identifier().to_string(),
4268 })
4269 })?;
4270
4271 // `mut` plus a setter rather than a ninth constructor argument: see
4272 // `ValidatedAuthorizationRequest::set_authorization_details`.
4273 #[allow(unused_mut)]
4274 let mut validated = ValidatedAuthorizationRequest::new(
4275 // Cloned rather than moved: `client` is an `Arc<Client>` shared with the store since
4276 // `Storage::get_client` stopped deep-copying the registration, so the id has to be
4277 // copied out. One allocation on the AUTHORIZATION endpoint, against the eight the
4278 // shared read saved on it.
4279 client.client_id.clone(),
4280 redirect_uri,
4281 redirect_uri_was_explicit,
4282 scope,
4283 state,
4284 code_challenge.to_string(),
4285 CodeChallengeMethod::S256,
4286 self.issuer_identifier().to_string(),
4287 resource,
4288 );
4289 #[cfg(feature = "rar")]
4290 validated.set_authorization_details(details);
4291 #[cfg(feature = "consent")]
4292 validated.set_authentication_requirement(requirement);
4293 Ok(validated)
4294 }
4295
4296 /// Mint an authorization code for a request the user has approved (RFC 6749 section 4.1.2).
4297 ///
4298 /// RFC 6749 SECTION 10.12 IS THE REASON THIS TAKES A [`UserApproval`] AND NOT A SUBJECT.
4299 /// Knowing WHO the user is does not establish that they agreed to anything. An authorization
4300 /// endpoint that mints a code as soon as it can name the logged-in user issues one on any
4301 /// cross-site top-level navigation that user's browser can be made to follow, which is exactly
4302 /// the cross-site request forgery section 10.12 describes: the attacker's client, the victim's
4303 /// session, a code delivered to the attacker's registered redirect URI. Nothing in this crate
4304 /// can see a user, so nothing here can detect that; the only defence a library has is to
4305 /// require the host to SAY that a resource owner approved this request, and to be unbuildable
4306 /// without it.
4307 ///
4308 /// Taking a [`ValidatedAuthorizationRequest`] (through the approval) rather than a raw request
4309 /// is deliberate for the same reason one level down: an unvalidated request cannot reach code
4310 /// issuance, because it cannot be spelled.
4311 pub async fn issue_authorization_code(
4312 &self,
4313 approval: UserApproval<'_>,
4314 ) -> Result<AuthorizationResponse, AuthorizationError> {
4315 // RFC 9470 IS ENFORCED HERE TOO, and it was not until the 0.9.1 audit.
4316 // `validate_authorization_request` parses `acr_values` and `max_age` onto the validated
4317 // request, and this entry point threw the result away: it minted a code for a request that
4318 // demanded a step-up without evaluating the demand, and `GrantedAuthentication::default()`
4319 // then recorded no authentication, so introspection reported no `auth_time` and no `acr`
4320 // for every token the code produced. The sibling below calls that enforcement "a library
4321 // job rather than a host job on purpose: a `max_age` the host is trusted to check for
4322 // itself is a `max_age` that gets checked in whichever code path somebody remembered", and
4323 // this was the path nobody remembered. `crate::http` uses the sibling, so what shipped
4324 // unenforced was the DIRECT API, which is the path `UserApproval` documents as the one
4325 // this crate's default build invites.
4326 //
4327 // Routed through the sibling with NO report, rather than duplicating the check: this entry
4328 // point has no argument through which a host could report an authentication, so `None` is
4329 // the only honest value and `satisfied_by` answers `Ok(())` for an empty requirement. A
4330 // request carrying neither parameter is therefore completely unaffected, and one carrying
4331 // either is refused with RFC 9470 section 3 `insufficient_user_authentication` rather than
4332 // granted, which is the fail-closed direction and the same answer the sibling gives an
4333 // unreported requirement.
4334 #[cfg(feature = "consent")]
4335 {
4336 let requirement = approval.request().authentication_requirement.clone();
4337 return self
4338 .issue_authorization_code_with_authentication(approval, &requirement, None)
4339 .await;
4340 }
4341 #[cfg(not(feature = "consent"))]
4342 self.issue_authorization_code_inner(approval, GrantedAuthentication::default())
4343 .await
4344 }
4345
4346 /// Mint an authorization code for a request the user has approved, holding the request's RFC
4347 /// 9470 step-up requirement to the authentication the HOST reports it performed.
4348 ///
4349 /// This is the enforcement half of RFC 9470, and it is a library job rather than a host job on
4350 /// purpose: a `max_age` the host is trusted to check for itself is a `max_age` that gets checked
4351 /// in whichever code path somebody remembered. The host still owns the authentication itself,
4352 /// and `authentication` is its REPORT of one; this crate cannot verify that report and does not
4353 /// pretend to. See the [`crate::consent`] module docs for the whole boundary.
4354 ///
4355 /// A requirement the report does not satisfy is refused with RFC 9470 section 3's
4356 /// `insufficient_user_authentication`, delivered as a REDIRECT (RFC 6749 section 4.1.2.1):
4357 /// by this point the redirect URI has been validated, and the client is both the party that
4358 /// asked the question and the party that has to decide whether to send the user back to log in.
4359 /// Nothing is minted and no consent is touched.
4360 /// The approval means the same thing here as it does on
4361 /// [`AuthorizationServer::issue_authorization_code`], and is required for the same RFC 6749
4362 /// section 10.12 reason: a satisfied `acr_values` says the user authenticated STRONGLY, never
4363 /// that they agreed.
4364 #[cfg(feature = "consent")]
4365 pub async fn issue_authorization_code_with_authentication(
4366 &self,
4367 approval: UserApproval<'_>,
4368 requirement: &crate::consent::AuthenticationRequirement,
4369 authentication: Option<&crate::consent::Authentication>,
4370 ) -> Result<AuthorizationResponse, AuthorizationError> {
4371 if let Err(failure) = requirement.satisfied_by(authentication, self.clock.now()) {
4372 let request = approval.request();
4373 return Err(AuthorizationError::Redirect(AuthorizationErrorRedirect {
4374 redirect_uri: request.redirect_uri.clone(),
4375 error: failure.error_response(),
4376 state: request.state.clone(),
4377 iss: request.issuer.clone(),
4378 }));
4379 }
4380 self.issue_authorization_code_inner(
4381 approval,
4382 GrantedAuthentication {
4383 authentication: authentication.cloned().map(Box::new),
4384 },
4385 )
4386 .await
4387 }
4388
4389 /// The issuance itself, shared by both entry points above so that they cannot drift.
4390 async fn issue_authorization_code_inner(
4391 &self,
4392 approval: UserApproval<'_>,
4393 authentication: GrantedAuthentication,
4394 ) -> Result<AuthorizationResponse, AuthorizationError> {
4395 #[cfg(not(feature = "consent"))]
4396 let _ = authentication;
4397 // Destructured rather than borrowed through the approval: `subject` is MOVED into the
4398 // record below, which is the same one allocation the previous `impl Into<String>` argument
4399 // produced. The approval adds no allocation of its own; it is a borrow plus that String.
4400 let UserApproval {
4401 request,
4402 subject,
4403 decided_at,
4404 } = approval;
4405 // THE SECOND CHARGE, and it is a separate one on purpose. `validate_authorization_request`
4406 // is charged for a READ; this is where the authorization code record is WRITTEN, and
4407 // nothing but `Storage::sweep_expired` reclaims one. Charging the write to the validation
4408 // would let a caller who validated once go on issuing for free, which is the half that
4409 // actually grows the store.
4410 //
4411 // A REDIRECT rather than a direct refusal, unlike the validation's: by this point the
4412 // redirect URI has been validated, so RFC 6749 section 4.1.2.1 sends the error back to the
4413 // client, carrying the state that lets it correlate. `temporarily_unavailable` because
4414 // nothing about the request was wrong and a retry later will succeed. Nothing is minted
4415 // and no consent is touched, which is the same posture the step-up refusal above takes.
4416 //
4417 // NOTHING IS RECORDED on the deny, and that is the same rule every other classification
4418 // site in this crate follows (`authenticate_client`, the validation above,
4419 // `pending_grant_by_user_code`, `register_dynamic_client`). `RateLimiter::record` is
4420 // documented as reporting how an ALLOWED attempt turned out, and a denial never became an
4421 // attempt: reporting it as `Failed` would drive the failure count with the very traffic
4422 // the limiter refused, and a host alerting on failure rate — which this crate tells hosts
4423 // to do — would read a client that merely exceeded its ceiling as a caller walking the
4424 // redirect-URI space. This site reported it through the 0.9.1 audit's second charge and
4425 // was the one exception.
4426 let attempt = Attempt::AuthorizationRequest {
4427 client_id: request.client_id.as_str(),
4428 };
4429 if self.hooks.check(attempt) == RateLimitDecision::Deny {
4430 return Err(AuthorizationError::Redirect(AuthorizationErrorRedirect {
4431 redirect_uri: request.redirect_uri.clone(),
4432 error: ErrorResponse::new(ErrorCode::TemporarilyUnavailable)
4433 .with_description("too many authorization requests; retry later"),
4434 state: request.state.clone(),
4435 iss: request.issuer.clone(),
4436 }));
4437 }
4438 let now = self.clock.now();
4439 // `?` rather than a panic, for the reason `try_random_hex` gives. The refusal is a
4440 // REDIRECT because by this point the redirect URI has been validated (RFC 6749 s4.1.2.1),
4441 // so it goes back to the client the same way the storage failure below does.
4442 let code = try_random_hex(32).ok_or_else(|| {
4443 self.hooks.record(attempt, AttemptOutcome::Failed);
4444 AuthorizationError::Redirect(AuthorizationErrorRedirect {
4445 redirect_uri: request.redirect_uri.clone(),
4446 error: ErrorResponse::new(ErrorCode::ServerError),
4447 state: request.state.clone(),
4448 iss: request.issuer.clone(),
4449 })
4450 })?;
4451 let record = AuthorizationCodeRecord {
4452 // WHEN THE USER'S DECISION HAPPENED, which is not always now. Redemption carries this
4453 // into the issued token so a revocation can tell this code from one minted after it,
4454 // and a barrier refuses a grant that PREDATES it — so dating the decision later than
4455 // it happened is what lets a code outrank a withdrawal recorded in between.
4456 //
4457 // `None` means the host prompted during this request and the two instants are the
4458 // same. A host acting on a standing approval must say so with
4459 // [`UserApproval::granted_at`]; this crate's own `http` service does, passing the
4460 // instant the authorization request was received. See that method for the ordering
4461 // this closes.
4462 issued_at: decided_at.unwrap_or(now),
4463 code: code.clone(),
4464 client_id: request.client_id.clone(),
4465 redirect_uri: request.redirect_uri.clone(),
4466 // RFC 6749 s4.1.3: carried so the token endpoint can require the parameter exactly
4467 // when the authorization request sent one. See the field.
4468 redirect_uri_was_explicit: request.redirect_uri_was_explicit,
4469 scope: request.scope.clone(),
4470 subject,
4471 code_challenge: request.code_challenge.clone(),
4472 code_challenge_method: request.code_challenge_method,
4473 // RFC 8707 s2: what the token this code redeems into may be audience-restricted to.
4474 resource: request.resource.clone(),
4475 // RFC 9396 s7: the details as granted, which is what the redeeming token request
4476 // may narrow and what the issued token will carry.
4477 #[cfg(feature = "rar")]
4478 authorization_details: request.authorization_details.clone(),
4479 expires_at: saturating_deadline(now, self.config.authorization_code_ttl),
4480 state: AuthorizationCodeState::Issued,
4481 // RFC 9470 s6.2: recorded here because the token endpoint has no user in front of it and
4482 // could not ask. See `AuthorizationCodeRecord::authentication`.
4483 #[cfg(feature = "consent")]
4484 authentication: authentication.authentication,
4485 };
4486 self.store
4487 .put_authorization_code(record)
4488 .await
4489 .map_err(|_| {
4490 self.hooks.record(attempt, AttemptOutcome::Failed);
4491 AuthorizationError::Redirect(AuthorizationErrorRedirect {
4492 redirect_uri: request.redirect_uri.clone(),
4493 error: ErrorResponse::new(ErrorCode::ServerError),
4494 state: request.state.clone(),
4495 iss: request.issuer.clone(),
4496 })
4497 })?;
4498 // The record is written, so the charge is settled as a success. Reported at all because a
4499 // limiter that counts failures needs to be told about the ones that were not.
4500 self.hooks.record(attempt, AttemptOutcome::Succeeded);
4501 Ok(AuthorizationResponse {
4502 code,
4503 state: request.state.clone(),
4504 // RFC 9207 s2. Taken from the validated request rather than re-read from config so the
4505 // success and error halves of one authorization request cannot disagree about who
4506 // answered it.
4507 iss: request.issuer.clone(),
4508 })
4509 }
4510
4511 /// RFC 6749 section 4.1.3 with the OAuth 2.1 PKCE requirement: redeem an authorization code.
4512 // Eight arguments rather than seven, for the reason `issue` carries the same allow: the
4513 // alternative is a parameter struct nobody else would ever construct, wrapping values
4514 // that are already named at this function's one call site.
4515 #[allow(clippy::too_many_arguments)]
4516 async fn authorization_code_token(
4517 &self,
4518 client_id: &ClientId,
4519 bound: &Bound<'_>,
4520 code: &str,
4521 redirect_uri: Option<&str>,
4522 code_verifier: Option<&str>,
4523 requested_resources: &[String],
4524 requested_details: GrantedDetails,
4525 ) -> Result<TokenResponse, ErrorResponse> {
4526 let client = self.authenticate_client(client_id, &bound.cred).await?;
4527 if !client.allows_grant(GrantType::AuthorizationCode) {
4528 return Err(ErrorResponse::new(ErrorCode::UnauthorizedClient));
4529 }
4530
4531 // Single use is enforced by the atomic take: concurrent redemptions of the same code
4532 // cannot both succeed, because only one of them receives the record.
4533 let record = self
4534 .store
4535 .take_authorization_code(code)
4536 .await
4537 .map_err(storage_error)?
4538 .ok_or_else(|| ErrorResponse::new(ErrorCode::InvalidGrant))?;
4539
4540 // A code belongs to the client it was issued to, and this is checked FIRST, before the
4541 // replay branch below, because that branch is DESTRUCTIVE. Ordering it the other way makes
4542 // "revoke the tokens this code minted" reachable by whoever presents the code, and a code
4543 // is a value that leaks: into logs, into `Referer` headers, into browser history. The
4544 // record goes BACK rather than being burned, for the same reason: the legitimate client
4545 // must still be able to complete its flow, and letting a third party destroy a live code
4546 // is a denial of service for free.
4547 //
4548 // Being honest about what this check is and is not. For a CONFIDENTIAL client it is an
4549 // authentication gate, because `authenticate_client` above proved the caller holds the
4550 // secret. For a PUBLIC client it is not: RFC 6749 section 4.1.2 notes that a public client
4551 // id is not a secret and anyone may claim one, so a leaked code still lets an attacker
4552 // reach this branch as the client the code WAS issued to, and still ends that client's
4553 // tokens. That residual is inherent to public clients and PKCE does not close it, since
4554 // the revocation happens before any verifier is checked. What the ordering above does buy
4555 // is that the residual stops at the client whose code actually leaked, instead of being
4556 // handed to every registered client in the deployment.
4557 if record.client_id != client.client_id {
4558 // NOT fire-and-forget. If this write fails, a LIVE code belonging to an honest client
4559 // has just been destroyed by a stranger's request, and answering `invalid_grant` would
4560 // report that as the ordinary refusal it is not: the honest client would come back a
4561 // moment later, be told `invalid_grant` as well, and nobody would ever connect the two.
4562 // `server_error` is the truthful answer and it is the only place this failure can
4563 // surface, because the party in front of us is not the one who was harmed.
4564 //
4565 // It reveals nothing: reaching this branch at all requires a real code, and the
4566 // difference between the two answers is a store failure the caller cannot provoke.
4567 self.store
4568 .put_authorization_code(record)
4569 .await
4570 .map_err(storage_error)?;
4571 return Err(ErrorResponse::new(ErrorCode::InvalidGrant));
4572 }
4573
4574 // A code presented twice is evidence it leaked, so RFC 6749 section 4.1.2 and RFC 9700
4575 // section 4.1.1 want the tokens it already minted revoked, not just the replay refused.
4576 // Refusing the replay alone would leave the attacker's stolen access token live.
4577 // `Replayed` counts too: a THIRD presentation is still a replay, and the record still
4578 // names what to revoke. Reading only `Consumed` here would make every presentation after
4579 // the second one look like an unknown code, which is the answer a typo gets.
4580 if let Some((access_token, refresh_token)) = record.state.minted() {
4581 let (access_token, refresh_token) = (
4582 access_token.map(str::to_string),
4583 refresh_token.map(str::to_string),
4584 );
4585 let (access_token, refresh_token) = (&access_token, &refresh_token);
4586 // Revoking by FAMILY rather than by the two recorded strings, so that a chain the
4587 // client has legitimately rotated since redemption dies too: the compromise is of the
4588 // grant, not of one token from it (RFC 9700 section 4.14.2).
4589 let mut revoked_family = false;
4590 // EVERY STEP BELOW CAN FAIL, AND THE WIRE CANNOT CARRY THE NEWS. The answer to a
4591 // replayed code is `invalid_grant` whatever happens here, because the party being
4592 // answered is whoever holds a leaked code and there is nothing to tell them. So the
4593 // AUDIT EVENT is the only signal a deployment gets, which makes an event that claims
4594 // containment it did not achieve strictly worse than no event: it is what an operator
4595 // reads while deciding not to investigate. This flag is what stops it lying.
4596 let mut containment_failed = false;
4597 // MOVED out of the record rather than cloned: `rec` is a value this scope already owns
4598 // and drops, so carrying its family id to the audit event below costs nothing.
4599 let mut revoked_family_id: Option<String> = None;
4600 if let Some(rt) = refresh_token {
4601 // The `Err` arm is NOT the same thing as `Ok(None)` and must not be folded into it.
4602 // `Ok(None)` means there is no chain to revoke, which is a clean outcome. `Err`
4603 // means the store could not say, so the family revocation was never even ATTEMPTED
4604 // and the attacker's chain may be live. Reading both as "no chain" is exactly the
4605 // overstated containment `containment_failed` exists to prevent.
4606 match self.store.get_refresh_token(rt).await {
4607 Ok(Some(rec)) => {
4608 // Cloned out of the shared snapshot `get_refresh_token` handed back. One
4609 // allocation, on the detected-compromise path only, against the seven that
4610 // read now costs nothing on the paths that run per request.
4611 revoked_family_id = Some(rec.family_id.clone());
4612 // `revoked_family` now means what its name says. It was set unconditionally
4613 // here, with the `Result` discarded, so a store that failed at the one
4614 // moment this server was responding to a detected compromise reported a
4615 // clean containment: the attacker's chain still live, the audit log saying
4616 // it was killed.
4617 match self
4618 .store
4619 .revoke_token_family(&rec.family_id, self.revocation_window())
4620 .await
4621 {
4622 Ok(_) => revoked_family = true,
4623 Err(_) => containment_failed = true,
4624 }
4625 }
4626 Ok(None) => {}
4627 Err(_) => containment_failed = true,
4628 }
4629 }
4630 if !revoked_family {
4631 // No refresh chain to reach the family through (or it is already swept, or the
4632 // revocation above failed): the access token this code minted is still nameable
4633 // directly, so this is a genuine fallback and not merely a tidy-up. Dropping its
4634 // failure left the compromised access token live for its whole TTL, silently.
4635 //
4636 // `None` means the redemption that consumed this code never got as far as issuing
4637 // anything (see `AuthorizationCodeState::Consumed::access_token`), so there is
4638 // genuinely nothing to revoke and nothing failed. The replay is still real and is
4639 // still reported.
4640 if let Some(at) = access_token {
4641 if self.store.delete_token(at).await.is_err() {
4642 containment_failed = true;
4643 }
4644 }
4645 }
4646 // The record goes BACK, and it goes back as `Replayed` rather than as what was read.
4647 // `src/authorization.rs` and `src/store.rs` both promise it is retained until its own
4648 // expiry, and that promise is what makes replay detection work more than once: taking
4649 // it here would make the NEXT replay read as an unknown code. Losing this write
4650 // therefore loses the EVIDENCE, not just a record, which is why it counts as a
4651 // containment failure too.
4652 //
4653 // `Replayed` is the DURABLE TRACE, and it is the half of this that a concurrent
4654 // redemption can see. Putting `Consumed` back would be byte for byte what a redemption
4655 // suspended on the host's signer wrote before it suspended, so that redemption would
4656 // wake, record what it minted, and hand out an access token and a refresh chain this
4657 // very replay was supposed to have contained. See `AuthorizationCodeState::Replayed`.
4658 let replayed = AuthorizationCodeRecord {
4659 state: AuthorizationCodeState::Replayed {
4660 access_token: access_token.clone(),
4661 refresh_token: refresh_token.clone(),
4662 },
4663 ..record
4664 };
4665 if self.store.put_authorization_code(replayed).await.is_err() {
4666 containment_failed = true;
4667 }
4668 // EVIDENCE OF COMPROMISE (RFC 6749 section 4.1.2, RFC 9700 section 4.1.1). The
4669 // revocation above is silent without this: a host cannot investigate a stolen code it
4670 // was never told about.
4671 self.hooks.emit(|| Event::AuthorizationCodeReplayDetected {
4672 client_id: client.client_id.as_str(),
4673 family_id: revoked_family_id.as_deref(),
4674 tokens_revoked: revoked_family,
4675 containment_failed,
4676 });
4677 return Err(ErrorResponse::new(ErrorCode::InvalidGrant));
4678 }
4679
4680 if self.clock.now() >= record.expires_at {
4681 // Expired codes are not put back: they can never become valid again.
4682 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
4683 .with_description("authorization code expired"));
4684 }
4685
4686 // RFC 6749 section 4.1.3: the redirect URI presented here must be the one the code was
4687 // issued against, which is what stops a code obtained for one registered URI being
4688 // redeemed as if it had been issued for another.
4689 //
4690 // THE PARAMETER IS CONDITIONAL, and conditional in both directions. Section 4.1.3 makes it
4691 // REQUIRED "if the `redirect_uri` parameter was included in the authorization request", and
4692 // section 3.1.2.3 entitles a client with exactly one registered URI to omit it there. This
4693 // endpoint required it unconditionally through 0.9.1, so that ordinary and conforming
4694 // client was refused here — and refused with a message blaming a mismatch that had not
4695 // happened, which is the answer that sends its developer looking at its registration.
4696 //
4697 // The check is not weakened for the request that DID send one: `Some` still has to equal
4698 // the recorded URI, and `None` is accepted only against a record that says the
4699 // authorization request named nothing. Sending one where none was sent is still refused,
4700 // because a code minted for a registration-derived URI must not be redeemable as if the
4701 // client had chosen the address itself.
4702 let redirect_uri_matches = match redirect_uri {
4703 Some(u) => u == record.redirect_uri,
4704 None => !record.redirect_uri_was_explicit,
4705 };
4706 if !redirect_uri_matches {
4707 let _ = self.store.put_authorization_code(record).await;
4708 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
4709 .with_description("redirect_uri does not match the authorization request"));
4710 }
4711
4712 // RFC 7636 section 4.6. A missing verifier is the exact downgrade PKCE exists to stop, so
4713 // it is a failure, never a skipped check.
4714 //
4715 // The LENGTH AND ALPHABET of the verifier are checked here too (section 4.1: 43 to 128
4716 // characters from the unreserved set), and until the 0.9.1 audit they were checked
4717 // nowhere — `pkce::verifier_is_valid` existed and had no caller outside the tests. The
4718 // asymmetry mattered: this crate pins the challenge it MINTS at 43 base64url characters
4719 // and refuses `plain`, so it validated everything the server produced and nothing the
4720 // client presented. A client deriving its challenge from a six-character verifier gets a
4721 // grant an attacker can finish, because section 7.1 puts the `code_challenge` in the
4722 // authorization request — browser history, `Referer`, proxy logs — and six characters is
4723 // not a search. Refusing costs a conforming client nothing.
4724 let verified = match (code_verifier, record.code_challenge_method) {
4725 (Some(v), CodeChallengeMethod::S256) => {
4726 crate::pkce::verifier_is_valid(v)
4727 && crate::pkce::verify_s256(v, &record.code_challenge)
4728 }
4729 (None, _) => false,
4730 };
4731 if !verified {
4732 let _ = self.store.put_authorization_code(record).await;
4733 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
4734 .with_description("code_verifier does not match the recorded code_challenge"));
4735 }
4736
4737 // RFC 8707 s2: the token request may narrow the audience the authorization request
4738 // obtained, never widen it. The code is put BACK on refusal, exactly as the scope and
4739 // redirect_uri mismatches above do: asking for the wrong resource is a client bug, and
4740 // burning a live code for a bug the client can fix on retry is a denial of service the
4741 // attacker gets for free.
4742 // RFC 8707 s2 and RFC 9396 s6 are the SAME rule applied to two parameters: the token
4743 // request may narrow what the authorization request obtained, and never widen it.
4744 // Both are computed as one fallible expression with ONE error path, rather than two
4745 // `match` blocks each holding an `ErrorResponse` across a `put_authorization_code`
4746 // await of its own: this function IS the token future, and `tests/allocation.rs`
4747 // holds that future under tokio's 2048-byte debug boxing threshold, past which every
4748 // single token request pays a 2 KB allocation.
4749 //
4750 // The code goes BACK on refusal, exactly as the redirect_uri and PKCE mismatches
4751 // above do: asking for the wrong thing is a client bug, and burning a live code for
4752 // a bug the client can fix on retry is a denial of service the attacker gets free.
4753 let narrowed = self
4754 .narrow_and_permit(&record.resource, requested_resources)
4755 .and_then(|r| {
4756 GrantedDetails::of_code(&record)
4757 .narrow(&requested_details)
4758 .map(|d| (r, d))
4759 });
4760 let (resource, details) = match narrowed {
4761 Ok(narrowed) => narrowed,
4762 Err(e) => {
4763 let _ = self.store.put_authorization_code(record).await;
4764 return Err(e);
4765 }
4766 };
4767
4768 // Retain the spent code until its own expiry, recording what it minted, so a later replay
4769 // is recognisable as a replay rather than as an unknown code.
4770 //
4771 // THE CONSUMED RECORD IS WRITTEN BEFORE ISSUANCE, and word for word the same argument as
4772 // the refresh rotation in `refresh_token` below: `take_authorization_code` above has
4773 // already removed this code, `Storage` has no transaction so the take and this write cannot
4774 // be one operation, and all that can be chosen is which way the pair fails.
4775 //
4776 // Issuing first fails OPEN: a store that dies mid-issuance leaves the code gone with no
4777 // consumed record, so a replay of a code that leaked into a log, a `Referer` header or
4778 // browser history reads as a typo, and RFC 6749 section 4.1.2 / RFC 9700 section 4.1.1
4779 // replay detection is off for that grant permanently and silently. Writing it first fails
4780 // CLOSED: the client is refused and starts a new authorization request, which it can do
4781 // without help, and the alarm stays armed.
4782 //
4783 // The price is the SECOND write below, because what the code minted is not knowable until
4784 // it has been minted. That is a much smaller loss if it fails than this one is: the alarm
4785 // is already armed by then.
4786 let subject = record.subject.clone();
4787 let scope = record.scope.clone();
4788 let authentication = GrantedAuthentication::from_code(&record);
4789 let mut consumed = AuthorizationCodeRecord {
4790 state: AuthorizationCodeState::Consumed {
4791 access_token: None,
4792 refresh_token: None,
4793 },
4794 ..record
4795 };
4796 self.store
4797 .put_authorization_code(consumed.clone())
4798 .await
4799 .map_err(storage_error)?;
4800
4801 let issued = self
4802 .issue_boxed(
4803 &client,
4804 bound,
4805 GrantType::AuthorizationCode,
4806 // The user's decision, taken from the code being redeemed. NOT `now`: a code
4807 // minted before a revocation must still be refused when it is redeemed after one.
4808 record.issued_at,
4809 Some(subject),
4810 scope,
4811 resource,
4812 details,
4813 None,
4814 true,
4815 authentication,
4816 // No actor: this grant delegates nothing (RFC 8693 s4.1).
4817 GrantedActor::default(),
4818 // No ceiling: an authorization code's redemption is the START of a grant's
4819 // lifetime, so the ordinary `access_token_ttl` is the whole rule.
4820 None,
4821 )
4822 .await?;
4823
4824 // Now the record can name what it minted, which is what lets a replay REVOKE rather than
4825 // merely be refused.
4826 //
4827 // A COMPARE-AND-SWAP against the record this function wrote itself, not a blind put, and
4828 // the expectation is exactly the `Consumed { None, None }` written before issuance. Two
4829 // things can have happened during the issuance above, which may have suspended on the
4830 // host's `Es256Signer` for a network round trip:
4831 //
4832 // - A REPLAY was detected and marked the record `Replayed`. The swap fails, and it must:
4833 // the replay path already decided this grant is compromised and found nothing to revoke
4834 // because nothing had been issued yet. Writing here would hand out the very tokens it
4835 // was trying to contain.
4836 // - The code was cascaded away by `delete_client` or `revoke_consent`. Absent refuses too,
4837 // so a withdrawn consent is not undone by a redemption that started before it.
4838 //
4839 // Either way the issuance is undone below rather than reported as a storage failure.
4840 let expected_before_issuance = AuthorizationCodeState::Consumed {
4841 access_token: None,
4842 refresh_token: None,
4843 };
4844 consumed.state = AuthorizationCodeState::Consumed {
4845 access_token: Some(issued.access_token.clone()),
4846 refresh_token: issued.refresh_token.clone(),
4847 };
4848 let recorded = self
4849 .store
4850 .compare_and_swap_authorization_code(&expected_before_issuance, consumed)
4851 .await;
4852 if !matches!(recorded, Ok(true)) {
4853 // The client is answered with an error, so it never receives the tokens that were just
4854 // minted and they become orphans: live records nobody was ever handed. Best effort
4855 // cleanup, and the `Result` is deliberately discarded rather than reported, because
4856 // there is nothing useful left to say. These are 32 bytes of OS randomness that were
4857 // never transmitted to anyone, so a failed cleanup costs storage the host's
4858 // `Storage::sweep_expired` reclaims at the token's own expiry, and nothing else. The
4859 // Both artifacts are nameable directly here, which is cheaper and more precise than
4860 // asking the store for the family they belong to.
4861 self.undo_issuance(&issued.access_token).await;
4862 if let Some(rt) = &issued.refresh_token {
4863 let _ = self.store.take_refresh_token(rt).await;
4864 }
4865 // The two outcomes are answered DIFFERENTLY, because they are different facts about
4866 // the deployment and the wire code is what a host's dashboards count.
4867 //
4868 // `Ok(false)` is not a failure of this server: the swap did exactly its job. The grant
4869 // was replayed or revoked while this redemption was in flight, so `invalid_grant` is
4870 // the true answer and a `server_error` would send an operator looking for a storage
4871 // fault that never happened.
4872 return Err(match recorded {
4873 Ok(_) => ErrorResponse::new(ErrorCode::InvalidGrant).with_description(
4874 "this authorization code was replayed or revoked during redemption",
4875 ),
4876 Err(_) => ErrorResponse::new(ErrorCode::ServerError)
4877 .with_description("could not record the redemption"),
4878 });
4879 }
4880
4881 Ok(issued)
4882 }
4883
4884 /// RFC 6749 section 4.4: the client acts on its own behalf, with no resource owner.
4885 async fn client_credentials_token(
4886 &self,
4887 client_id: &ClientId,
4888 bound: &Bound<'_>,
4889 requested_scope: Option<&ScopeSet>,
4890 resource: Vec<String>,
4891 details: GrantedDetails,
4892 ) -> Result<TokenResponse, ErrorResponse> {
4893 // STAMPED BEFORE THE REGISTRATION IS READ, and that ordering is the whole point.
4894 //
4895 // This grant has no resource owner, so the client's own authentication IS the decision and
4896 // this is the instant it dates from. Taking it AFTER `authenticate_client` would date the
4897 // grant later than the read it derives from, and a `delete_client` landing in between —
4898 // one `get_client` round trip, plus a secret verification, plus on the assertion path a
4899 // JWT verify and a replay claim — would then record a barrier EARLIER than the grant it is
4900 // supposed to refuse. The write would be applied and a live token minted for a
4901 // registration that no longer exists.
4902 //
4903 // That is the exact resurrection the barrier exists to stop, and it was introduced by the
4904 // 0.9.1 audit fix that made barriers compare instants at all: refusing on identity alone
4905 // had closed it for free. Found by auditing that fix rather than by any test.
4906 let grant_established_at = self.clock.now();
4907 let client = self.authenticate_client(client_id, &bound.cred).await?;
4908 // RFC 6749 section 4.4: this grant is for confidential clients. A public client has no
4909 // secret, so "the client itself" is not an identity anyone has proven.
4910 //
4911 // BARE, for the reason spelled out at the introspection twin of this check: a description
4912 // here would be the only response on the endpoint that distinguishes a registered public
4913 // client from an unknown id, and the credential path just above collapses exactly that
4914 // distinction. Both sites were changed together because the introspection comment cites
4915 // this one as "the same refusal, for the same reason", and a fix applied to one of a pair
4916 // that call each other authority is how a rule ends up living in two places with two
4917 // answers. The operator's sentence is `ClientAuthFailure::NotConfidential`.
4918 if matches!(client.auth, crate::client::ClientAuth::Public) {
4919 self.hooks.emit(|| Event::ClientAuthenticationFailed {
4920 client_id: client_id.as_str(),
4921 failure: ClientAuthFailure::NotConfidential,
4922 });
4923 return Err(ErrorResponse::new(ErrorCode::InvalidClient));
4924 }
4925 if !client.allows_grant(GrantType::ClientCredentials) {
4926 return Err(ErrorResponse::new(ErrorCode::UnauthorizedClient));
4927 }
4928 let scope = Self::resolve_scope(&client, requested_scope)?;
4929 // Section 4.4.3: a refresh token SHOULD NOT be included. The client holds its own
4930 // credentials and can mint another token whenever it likes, so a refresh token would be a
4931 // second long-lived secret bought for nothing.
4932 // RFC 8707 s2: there is no prior authorization request here, so there is nothing to
4933 // narrow AGAINST. The client authenticated as itself and is naming the resource server it
4934 // means to call, which is the whole of what the parameter says in this grant.
4935 self.issue_boxed(
4936 &client,
4937 bound,
4938 GrantType::ClientCredentials,
4939 // Read at request ENTRY, above, not here. See the comment there.
4940 grant_established_at,
4941 None,
4942 scope,
4943 resource,
4944 // RFC 9396 s6: there is no prior authorization request here, so there is nothing
4945 // to narrow AGAINST. The client authenticated as itself and is naming what it
4946 // means to do, which is the whole of what the parameter says in this grant; the
4947 // s5 type check has already run at the endpoint.
4948 details,
4949 None,
4950 false,
4951 // RFC 6749 s4.4 has no resource owner, so there is no user authentication to report.
4952 GrantedAuthentication::default(),
4953 // No actor: this grant delegates nothing (RFC 8693 s4.1).
4954 GrantedActor::default(),
4955 // No ceiling: this grant derives from the client's own live credentials and from no
4956 // earlier token, so there is nothing for its lifetime to be capped against.
4957 None,
4958 )
4959 .await
4960 }
4961
4962 /// RFC 8628 sections 3.4/3.5: one device-token poll.
4963 async fn device_token(
4964 &self,
4965 client_id: &ClientId,
4966 bound: &Bound<'_>,
4967 device_code: &str,
4968 ) -> Result<TokenResponse, ErrorResponse> {
4969 let client = self.authenticate_client(client_id, &bound.cred).await?;
4970 if !client.allows_grant(GrantType::DeviceCode) {
4971 return Err(ErrorResponse::new(ErrorCode::UnauthorizedClient));
4972 }
4973
4974 let mut grant = self
4975 .store
4976 .get_device_grant(device_code)
4977 .await
4978 .map_err(storage_error)?
4979 .ok_or_else(|| ErrorResponse::new(ErrorCode::InvalidGrant))?;
4980
4981 // A device_code was issued to exactly one client; anyone else presenting it holds a grant
4982 // that was not made to them (RFC 6749 section 5.2 `invalid_grant`). The grant is NOT
4983 // consumed: a stray or malicious cross-client poll must not break the real device.
4984 if grant.client_id != client.client_id {
4985 return Err(ErrorResponse::new(ErrorCode::InvalidGrant));
4986 }
4987
4988 let now = self.clock.now();
4989
4990 // Expiry first (RFC 8628 section 3.5 `expired_token`), and the grant is removed: the code
4991 // can never become valid again, and later polls report plain `invalid_grant`.
4992 if now >= grant.expires_at {
4993 let _ = self.store.take_device_grant(device_code).await;
4994 return Err(ErrorResponse::new(ErrorCode::ExpiredToken));
4995 }
4996
4997 // Poll pacing. Too-fast polls get `slow_down`, and the REQUIRED spacing grows by the
4998 // configured increment (the RFC directs the client to add 5 seconds; the server tracks the
4999 // same number so it can hold the client to it). The window also restarts at this poll:
5000 // hammering does not drain the wait.
5001 //
5002 // BOTH WRITES BELOW ARE COMPARE-AND-SWAPS, and this is the argued part. A poll and the
5003 // user's decision at the verification UI are two different actors on one record, and the
5004 // poll's write is derived from a read that happened one or more storage round trips ago.
5005 // Blind-putting that snapshot back reverts an approval the user really gave, or, worse, a
5006 // DENIAL: the verification UI has already told the user their refusal was recorded, the
5007 // grant is `Pending` again, and nothing anywhere reports an error.
5008 //
5009 // The trade is deliberate and it is not symmetric. A poll writes exactly two fields,
5010 // `interval` and `last_poll_at`, and losing them costs at most one extra `slow_down` on the
5011 // next poll. Losing a DECISION is losing something a human did. So a missed swap here is
5012 // NOT retried and NOT an error: the poll simply declines to write, the decision stands, and
5013 // the device reads it on its next poll a few seconds later. That is the whole design rule,
5014 // and it is why this is a compare-and-swap rather than a re-read-and-merge: re-reading
5015 // narrows the window, it does not close it, and the decision must not be losable at all.
5016 if let Some(last) = grant.last_poll_at {
5017 // `checked_add`, not `+`: `SystemTime + Duration` PANICS on overflow, and
5018 // `grant.interval` is grown by the poll rate below against a host-set increment that
5019 // nothing validates. `None` means the deadline is past any representable instant, so
5020 // the client is unconditionally too early.
5021 // `map_or(true, ..)` rather than `is_none_or`, which is stable only since 1.82 while
5022 // this crate's MSRV is 1.75. The clippy lint that prefers `is_none_or` does not know
5023 // that, so it is silenced here rather than obeyed.
5024 #[allow(clippy::unnecessary_map_or)]
5025 if last
5026 .checked_add(grant.interval)
5027 .map_or(true, |next| now < next)
5028 {
5029 let expected = grant.state.clone();
5030 // `saturating_add`: `Duration: AddAssign` panics on overflow, and this
5031 // accumulator is paced by the client's own polling against a host-set increment.
5032 // A saturated interval is a client that must wait longer than the grant lives,
5033 // which is the same refusal by a different route.
5034 grant.interval = grant
5035 .interval
5036 .saturating_add(self.config.slow_down_increment);
5037 grant.last_poll_at = Some(now);
5038 // A genuine storage FAILURE is still fatal, exactly as it was before: only losing
5039 // the race is tolerated. The `slow_down` answer stands either way, because the
5040 // pacing verdict was computed from a real read of a real record.
5041 self.store
5042 .compare_and_swap_device_grant(&expected, grant)
5043 .await
5044 .map_err(storage_error)?;
5045 return Err(ErrorResponse::new(ErrorCode::SlowDown));
5046 }
5047 }
5048
5049 // Cloned before `grant` is moved, and no more often than the `match grant.state.clone()`
5050 // this replaced: the branch arms need the state and the swap needs it as the expectation.
5051 let state = grant.state.clone();
5052 grant.last_poll_at = Some(now);
5053
5054 match state {
5055 DeviceGrantState::Pending => {
5056 self.store
5057 .compare_and_swap_device_grant(&DeviceGrantState::Pending, grant)
5058 .await
5059 .map_err(storage_error)?;
5060 Err(ErrorResponse::new(ErrorCode::AuthorizationPending))
5061 }
5062 DeviceGrantState::Denied => {
5063 // Terminal answer, delivered once; the grant is consumed with it.
5064 let _ = self.store.take_device_grant(device_code).await;
5065 Err(ErrorResponse::new(ErrorCode::AccessDenied))
5066 }
5067 DeviceGrantState::Approved { subject } => {
5068 // Single use: redemption goes through the atomic take, so a concurrent double
5069 // poll can only mint one token; the loser sees `invalid_grant`.
5070 let taken = self
5071 .store
5072 .take_device_grant(device_code)
5073 .await
5074 .map_err(storage_error)?
5075 .ok_or_else(|| ErrorResponse::new(ErrorCode::InvalidGrant))?;
5076 // No resource: the device authorization request does not carry one (see
5077 // `token_with_resources`), and the poll above refuses any the client sends.
5078 self.issue_boxed(
5079 &client,
5080 bound,
5081 GrantType::DeviceCode,
5082 // The instant the DEVICE ASKED, which is NOT the instant the user approved.
5083 // RFC 8628 s3.3 approval happens at the host's own verification UI and
5084 // `DeviceGrantState::Approved` records only the subject, so the approval
5085 // instant is never persisted and `created_at` is the closest thing that
5086 // exists. It is EARLIER than the decision, never later, so a barrier recorded
5087 // in between refuses this grant rather than admitting it: fail-closed, at the
5088 // cost of a user who re-approves after withdrawing consent having to restart
5089 // the device flow. See `IssuedToken::grant_established_at`, which states the
5090 // window in full.
5091 taken.created_at,
5092 Some(subject),
5093 taken.scope,
5094 Vec::new(),
5095 // No details: the device authorization request does not carry them and
5096 // the poll above refuses any the client sends.
5097 GrantedDetails::default(),
5098 None,
5099 true,
5100 // The device grant carries no authentication report: RFC 8628 s3.3 approval
5101 // happens at the host's own verification UI, which is where the report would
5102 // have to be taken, and inventing one here would be this server asserting
5103 // something it never witnessed.
5104 GrantedAuthentication::default(),
5105 // No actor: this grant delegates nothing (RFC 8693 s4.1).
5106 GrantedActor::default(),
5107 // No ceiling: RFC 8628 approval starts a grant, exactly as a code redemption
5108 // does, so the ordinary `access_token_ttl` is the whole rule.
5109 None,
5110 )
5111 .await
5112 }
5113 }
5114 }
5115
5116 /// RFC 6749 section 6 with OAuth 2.1 rotation: redeem a refresh token, single use.
5117 async fn refresh_token(
5118 &self,
5119 client_id: &ClientId,
5120 bound: &Bound<'_>,
5121 refresh_token: &str,
5122 requested_scope: Option<&ScopeSet>,
5123 requested_resources: &[String],
5124 requested_details: GrantedDetails,
5125 ) -> Result<TokenResponse, ErrorResponse> {
5126 let client = self.authenticate_client(client_id, &bound.cred).await?;
5127 if !client.allows_grant(GrantType::RefreshToken) {
5128 return Err(ErrorResponse::new(ErrorCode::UnauthorizedClient));
5129 }
5130
5131 // Consume first (atomic): that is what makes redemption single use under concurrency.
5132 // Judging comes after, and every judgement below either puts the record back or has a
5133 // stated reason not to.
5134 let record = self
5135 .store
5136 .take_refresh_token(refresh_token)
5137 .await
5138 .map_err(storage_error)?
5139 .ok_or_else(|| ErrorResponse::new(ErrorCode::InvalidGrant))?;
5140
5141 // Presented by a client it was not issued to. The record goes BACK: the presenter proved
5142 // only that they hold a string, and destroying a live credential on that basis locks out
5143 // the client that legitimately holds it while costing the attacker nothing. Same reasoning
5144 // as the authorization code path, which has always put the record back on a mismatch.
5145 if record.client_id != client.client_id {
5146 self.restore_refresh_token(record).await?;
5147 return Err(ErrorResponse::new(ErrorCode::InvalidGrant));
5148 }
5149
5150 // REUSE. This token was already rotated away, so two parties hold it, and the AS has just
5151 // been handed unambiguous evidence of that. OAuth 2.1 draft section 6.1 and RFC 9700
5152 // section 4.14.2: invalidate the presented token AND revoke the tokens issued for that
5153 // authorization grant. Refusing the presentation alone would be the defence inverted,
5154 // because the party who presents the superseded token is by definition the one who did NOT
5155 // redeem it first, which in a theft is the victim.
5156 //
5157 // When the revocation SUCCEEDS it removes every record carrying this id, including this
5158 // one, so there is nothing to put back. When it fails there is, and that is the whole of
5159 // the paragraph below.
5160 if record.state == RefreshTokenState::Spent {
5161 // THE STORE FAILING HERE MUST NOT PROPAGATE, and the reason is the same one the
5162 // authorization-code replay path states above: the wire cannot carry this news. The
5163 // answer to a reused refresh token is `invalid_grant` however badly the store is
5164 // behaving, so the AUDIT EVENT is the only signal a deployment gets. Returning the
5165 // storage error instead lost all three halves of the response at once — the family
5166 // was not revoked, so the thief's rotated chain stayed live; the `Spent` record had
5167 // ALREADY been removed by `take_refresh_token` above and was never put back, so RFC
5168 // 9700 section 4.14.2 reuse detection for that family was off from then on and a later
5169 // presentation of the same string read as an unknown token; and no event fired at all,
5170 // so the host's only audit channel was never told any of it.
5171 let mut containment_failed = false;
5172 // MOVED out of the record before the `Err` arm below can move the record itself back
5173 // into the store. One allocation, on the detected-compromise path only.
5174 let family_id = record.family_id.clone();
5175 let mut records_revoked = 0;
5176 match self
5177 .store
5178 .revoke_token_family(&family_id, self.revocation_window())
5179 .await
5180 {
5181 Ok(revoked) => records_revoked = revoked,
5182 Err(_) => {
5183 containment_failed = true;
5184 // THE ALARM STAYS ARMED. The record goes back exactly as it was read, still
5185 // `Spent`, because it is the evidence: it is what makes the NEXT presentation
5186 // of this string detectable as reuse rather than as an unknown token. Its own
5187 // outcome is not inspected because `containment_failed` is already true —
5188 // the revocation failing is the compromise this event has to report, and a
5189 // refused or failed restore only adds to a story that is already the bad one.
5190 let _ = self.store.put_refresh_token(record).await;
5191 }
5192 }
5193 // EVIDENCE OF COMPROMISE, and the event most likely to be asked about after the fact:
5194 // the family revocation also logs out the legitimate client. It is emitted on BOTH
5195 // outcomes. A reuse that could not be contained is the more urgent of the two, not the
5196 // one worth staying quiet about, and `containment_failed` is what stops the event
5197 // claiming a containment that did not happen.
5198 self.hooks.emit(|| Event::RefreshTokenReuseDetected {
5199 client_id: client.client_id.as_str(),
5200 family_id: &family_id,
5201 records_revoked,
5202 containment_failed,
5203 });
5204 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
5205 .with_description("refresh token reuse detected; the grant has been revoked"));
5206 }
5207
5208 // RFC 9449 s5: a refresh chain issued to a DPoP-bound grant stays bound to the SAME key,
5209 // and a rotation has to prove possession of it. Without this the binding would be
5210 // decorative past the first access token: a stolen refresh token could simply be re-bound
5211 // to the thief's key on the next rotation, leaving the attacker holding a token they can
5212 // prove possession for while the victim's key is the one refused. The record goes BACK, as
5213 // for every other judgement here that is not evidence of compromise.
5214 #[cfg(feature = "dpop")]
5215 if record.jkt.as_deref() != bound.jkt {
5216 self.restore_refresh_token(record).await?;
5217 return Err(ErrorResponse::new(ErrorCode::InvalidDpopProof)
5218 .with_description("this refresh token is bound to a different DPoP key"));
5219 }
5220
5221 // RFC 8705 s3, and word for word the same argument as the DPoP check above: a chain
5222 // issued over a client certificate stays bound to THAT certificate, and a rotation
5223 // has to present it again. Without this the binding is decorative past the first
5224 // access token, because a stolen refresh token could be re-bound to the thief's own
5225 // certificate on the next rotation. Section 3 makes it a MUST for public clients
5226 // specifically; applying it to every bound chain costs a confidential mutual-TLS
5227 // client nothing, since it presents that certificate on every request anyway.
5228 #[cfg(feature = "mtls")]
5229 if record.x5t_s256.as_deref() != bound.cred.certificate.map(|c| c.thumbprint()) {
5230 self.restore_refresh_token(record).await?;
5231 return Err(
5232 ErrorResponse::new(ErrorCode::InvalidGrant).with_description(
5233 "this refresh token is bound to a different client certificate",
5234 ),
5235 );
5236 }
5237
5238 if let Some(expires_at) = record.expires_at {
5239 if self.clock.now() >= expires_at {
5240 // Not put back: an expired chain can never become valid again, and keeping it
5241 // would only be storage the host has to sweep.
5242 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
5243 .with_description("refresh token chain expired"));
5244 }
5245 }
5246
5247 // THE REGISTRATION'S CEILING, RE-APPLIED AT EVERY ROTATION.
5248 //
5249 // `Client::allowed_scopes` is documented as "the scopes this client may ever be granted",
5250 // and until the 0.9.1 audit a refresh chain was the one place that promise was not kept:
5251 // the only ceiling here was `record.scope`, so an operator who discovered a client should
5252 // never have held `payments:write` and narrowed the registration kept minting it on every
5253 // rotation. With `refresh_token_ttl` defaulting to `None` — no expiry at all — "may ever
5254 // be granted" was, by default, "may be granted forever regardless of what the registration
5255 // now says". Only `delete_client` or a per-family revocation the operator has no list of
5256 // actually stopped it.
5257 //
5258 // A code or a device grant has the same gap for its own lifetime, but those are 60 and 600
5259 // seconds and self-closing; a chain is not, which is why the check lands here.
5260 //
5261 // Refusing rather than silently intersecting is the fail-closed direction and the honest
5262 // one: the client asked to continue a grant this server is no longer willing to honour, and
5263 // quietly handing back a narrower token would look to the client like the grant it had. The
5264 // record is NOT put back — unlike the widening attempt below, this is not a retryable client
5265 // mistake, and leaving the chain alive would mean answering `invalid_scope` forever while
5266 // the credential stays valid.
5267 if !record.scope.is_subset(&client.allowed_scopes) {
5268 return Err(
5269 ErrorResponse::new(ErrorCode::InvalidScope).with_description(
5270 "this grant carries scopes the client's registration no longer allows",
5271 ),
5272 );
5273 }
5274
5275 // Narrowing only (RFC 6749 section 6: scope must not include any scope not originally
5276 // granted). A widening attempt is a client bug, not a compromise: put the record back so
5277 // the mistake is retryable.
5278 let scope = match requested_scope {
5279 None => record.scope.clone(),
5280 Some(s) if s.is_subset(&record.scope) => s.clone(),
5281 Some(_) => {
5282 self.restore_refresh_token(record).await?;
5283 return Err(ErrorResponse::new(ErrorCode::InvalidScope)
5284 .with_description("refresh may narrow scope, never widen it"));
5285 }
5286 };
5287
5288 // RFC 8707 s2, the same narrowing rule as the scope rule immediately above and refused the
5289 // same way: the record goes back so a client that asked for the wrong resource can retry.
5290 // RFC 8707 s2 and RFC 9396 s6, one fallible expression and one error path, for the
5291 // reason the authorization code grant gives above. The chain carries what the
5292 // PREVIOUS leg narrowed to, so a client that narrowed once cannot climb back on the
5293 // next rotation; the record goes back either way, because a widening attempt here is
5294 // a client bug and not evidence of compromise.
5295 let narrowed = self
5296 .narrow_and_permit(&record.resource, requested_resources)
5297 .and_then(|r| {
5298 GrantedDetails::of_refresh(&record)
5299 .narrow(&requested_details)
5300 .map(|d| (r, d))
5301 });
5302 let (resource, details) = match narrowed {
5303 Ok(narrowed) => narrowed,
5304 Err(e) => {
5305 self.restore_refresh_token(record).await?;
5306 return Err(e);
5307 }
5308 };
5309
5310 // Retain the rotated token, marked spent, exactly as the authorization code path retains a
5311 // consumed code and for the same reason: a deleted token makes a later presentation
5312 // indistinguishable from an unknown string, and reuse detection is then impossible. A
5313 // chain with no absolute expiry gets a retention deadline here, so the record is
5314 // reclaimable by `Storage::sweep_expired` rather than immortal.
5315 //
5316 // THIS WRITE HAPPENS BEFORE ISSUANCE, AND THE ORDER IS A SECURITY PROPERTY RATHER THAN A
5317 // STYLE CHOICE. `Storage` deliberately has no transaction (see the trait's own docs: a host
5318 // may be backing this with anything from a HashMap to a sharded KV store, and requiring
5319 // cross-key atomicity would exclude most of them), so the atomic take above and this write
5320 // CANNOT be made one operation. All that can be chosen is which way the pair fails.
5321 //
5322 // Issuing first and marking spent afterwards fails OPEN. The take has already removed this
5323 // token; if anything in issuance or in this write then fails, the token is gone with NO
5324 // spent record, so a later presentation of it reads as an unknown string rather than as
5325 // reuse. RFC 9700 section 4.14.2 detection is then off for this family, permanently and
5326 // silently, at exactly the moment the deployment's storage is misbehaving, which is when a
5327 // compromise is most likely to go unnoticed. The freshly minted tokens meanwhile stay live
5328 // and orphaned, because the caller is answered with an error and never sees them.
5329 //
5330 // Marking spent first fails CLOSED, and that is the right trade. If this write fails,
5331 // nothing has been minted and the client re-authenticates. If it succeeds and issuance then
5332 // fails, the client is locked out of this chain and re-authenticates, and the alarm is
5333 // ARMED: the next presentation of this token is recognised as reuse and revokes the family.
5334 // Locking a client out is an inconvenience it can recover from without help; a
5335 // compromise-detection capability going offline is not.
5336 let chain_expires_at = record.expires_at;
5337 // Read off the record BEFORE it is moved into the spent value below. These are the same
5338 // three clones the issuance took when it ran first, so the ordering change costs nothing.
5339 let subject = record.subject.clone();
5340 let family_id = record.family_id.clone();
5341 let authentication = GrantedAuthentication::from_refresh(&record);
5342 // Read BEFORE `record` is consumed into `spent` below.
5343 let grant_established_at = record.grant_established_at;
5344 let spent = RefreshTokenRecord {
5345 state: RefreshTokenState::Spent,
5346 // `saturating_deadline` rather than `+`: `refresh_reuse_window` is a plain public
5347 // field with no validating constructor, and this branch is the DEFAULT
5348 // configuration's (`refresh_token_ttl: None` means the chain has no absolute expiry),
5349 // so every rotation in such a deployment performs this addition on a request path.
5350 expires_at: chain_expires_at.or_else(|| {
5351 Some(saturating_deadline(
5352 self.clock.now(),
5353 self.config.refresh_reuse_window,
5354 ))
5355 }),
5356 ..record
5357 };
5358 // NOT `restore_refresh_token`: this write is not a restoration and a refusal here is not
5359 // benign. The spent marker is what arms reuse detection for the token about to be minted,
5360 // so if a revocation has reached this family the rotation must STOP rather than continue
5361 // to issuance. Continuing would mint a chain from a grant that was revoked a moment ago,
5362 // and would do it with the alarm disarmed.
5363 if self
5364 .store
5365 .put_refresh_token(spent)
5366 .await
5367 .map_err(storage_error)?
5368 .is_refused()
5369 {
5370 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
5371 .with_description("the grant was revoked while this token was being refreshed"));
5372 }
5373
5374 self.issue_boxed(
5375 &client,
5376 bound,
5377 GrantType::RefreshToken,
5378 // CARRIED from the chain, never restamped. Restamping here would let a chain walk
5379 // forward past the revocation that killed the decision it descends from.
5380 grant_established_at,
5381 subject,
5382 scope,
5383 resource,
5384 details,
5385 Some(RefreshChain {
5386 family_id,
5387 expires_at: chain_expires_at,
5388 }),
5389 true,
5390 authentication,
5391 // A rotation carries no actor of its own: nothing was delegated by refreshing.
5392 GrantedActor::default(),
5393 // No ceiling from the access token being replaced: the CHAIN's own absolute expiry is
5394 // what bounds a refresh grant's total lifetime, and it is applied to the rotated
5395 // refresh token above rather than to the access token here.
5396 None,
5397 )
5398 .await
5399 }
5400
5401 /// Mint and persist an access token (and, when configured, a rotated refresh token).
5402 ///
5403 /// `chain`: `None` starts a NEW family (a fresh grant); `Some(_)` continues an existing one,
5404 /// keeping both its family id and its absolute lifetime, which is what makes rotation a chain
5405 /// rather than a sequence of unrelated tokens.
5406 // Eight arguments rather than seven, because the audit event has to name the grant that
5407 // produced the token and `issue` is the only place that sees the whole issuance. Bundling them
5408 // into a struct would be churn for its own sake on a private function with four call sites.
5409 #[allow(clippy::too_many_arguments)]
5410 /// [`AuthorizationServer::issue`] behind one heap allocation.
5411 ///
5412 /// Every caller of `issue` goes through here, and the reason is a measurement rather than a
5413 /// preference. `issue` is the widest frame on the token path: it holds a whole `IssuedToken`
5414 /// and a whole `RefreshTokenRecord` across its storage awaits, and because a generator's size
5415 /// is the MAXIMUM over all of its states, that width is paid by every token request including
5416 /// the polls and refusals that never issue anything. Inlined, it puts the token future over
5417 /// tokio's 2048-byte debug boxing threshold as soon as `dpop` adds a binding to both records,
5418 /// and tokio's answer to that is to box the WHOLE token future on every single request.
5419 ///
5420 /// So: one allocation, paid only when a token is actually issued, instead of one allocation the
5421 /// size of the entire token future paid on every request that reaches this endpoint. The
5422 /// allocation gates in `tests/allocation.rs` are what settled this, and they measure both.
5423 ///
5424 /// # RE-MEASURED after 0.9.0, and KEPT
5425 ///
5426 /// The two other `Box::pin`s on this path (the RFC 9449 proof check and the RFC 7523 assertion
5427 /// check) were both removed at that point, because measuring them showed the future was byte
5428 /// for byte identical with and without them: they had stopped buying anything. This one had
5429 /// not. Inlined, the `--all-features` token future goes from 1344 bytes to 1608, leaving 440
5430 /// bytes of headroom against tokio's 2048 rather than 704.
5431 ///
5432 /// That trade is REFUSED, and the number is why. What removing it would save is one allocation
5433 /// per token ISSUED, out of the 39 a code redemption already costs. What it would spend is 264
5434 /// bytes of the margin against a threshold this crate has crossed twice already, once for 120
5435 /// bytes and once for 344; a future feature the size of `rar` would put it over, and the
5436 /// failure mode on the other side of that line is a 2 KB heap allocation on every single token
5437 /// request, refusals included. Two and a half percent off the issuance path is not worth
5438 /// spending a third of the headroom that keeps the whole endpoint off tokio's slow path.
5439 #[allow(clippy::too_many_arguments)]
5440 fn issue_boxed<'a>(
5441 &'a self,
5442 client: &'a Client,
5443 bound: &'a Bound<'_>,
5444 grant_type: GrantType,
5445 // The instant the GRANT behind this issuance was authorized: the code's mint, the device
5446 // approval, or the instant carried forward from the chain. NOT `now`. See
5447 // `crate::token::IssuedToken::grant_established_at`.
5448 grant_established_at: SystemTime,
5449 subject: Option<String>,
5450 scope: ScopeSet,
5451 resource: Vec<String>,
5452 details: GrantedDetails,
5453 chain: Option<RefreshChain>,
5454 allow_refresh: bool,
5455 authentication: GrantedAuthentication,
5456 actor: GrantedActor,
5457 lifetime_ceiling: Option<SystemTime>,
5458 ) -> std::pin::Pin<
5459 Box<dyn std::future::Future<Output = Result<TokenResponse, ErrorResponse>> + Send + 'a>,
5460 > {
5461 Box::pin(self.issue(
5462 client,
5463 bound,
5464 grant_type,
5465 grant_established_at,
5466 subject,
5467 scope,
5468 resource,
5469 details,
5470 chain,
5471 allow_refresh,
5472 authentication,
5473 actor,
5474 lifetime_ceiling,
5475 ))
5476 }
5477
5478 #[allow(clippy::too_many_arguments)]
5479 pub(crate) async fn issue(
5480 &self,
5481 client: &Client,
5482 bound: &Bound<'_>,
5483 grant_type: GrantType,
5484 // The instant the GRANT behind this issuance was authorized: the code's mint, the device
5485 // approval, or the instant carried forward from the chain. NOT `now`. See
5486 // `crate::token::IssuedToken::grant_established_at`.
5487 grant_established_at: SystemTime,
5488 subject: Option<String>,
5489 scope: ScopeSet,
5490 resource: Vec<String>,
5491 details: GrantedDetails,
5492 chain: Option<RefreshChain>,
5493 allow_refresh: bool,
5494 authentication: GrantedAuthentication,
5495 actor: GrantedActor,
5496 // An ABSOLUTE CEILING on the issued access token's expiry, or `None` for the ordinary
5497 // `access_token_ttl` from now. `Some` exists for RFC 8693 token exchange, where the issued
5498 // token derives its authority from a subject token that is itself expiring: without a
5499 // ceiling the exchanged token is an ordinary access token, therefore an acceptable SUBJECT
5500 // token, therefore re-exchangeable just before each expiry for a fresh full TTL, forever.
5501 // That is a grant renewing its own lifetime without limit, which is exactly what this
5502 // grant refuses to issue a refresh token for.
5503 lifetime_ceiling: Option<SystemTime>,
5504 ) -> Result<TokenResponse, ErrorResponse> {
5505 // `bound` carries only the RFC 9449 key binding, so with that feature off it is genuinely
5506 // unused HERE. It stays in the signature regardless, so the four call sites do not have to
5507 // differ by feature: a call site that differs by feature is a call site that gets it wrong
5508 // under the configuration nobody builds locally.
5509 #[cfg(not(feature = "dpop"))]
5510 let _ = bound;
5511 // Same for the RFC 9470 authentication report: without `consent` the wrapper is empty and
5512 // genuinely unused here, and an unused parameter is a warning rather than a signature that
5513 // differs by feature.
5514 #[cfg(not(feature = "consent"))]
5515 let _ = authentication;
5516 // And the RFC 8693 actor, which only a delegation exchange ever fills.
5517 #[cfg(not(feature = "token-exchange"))]
5518 let _ = actor;
5519 // Likewise: without `rar` the details are a zero sized value nothing reads.
5520 #[cfg(not(feature = "rar"))]
5521 let _ = details;
5522 let now = self.clock.now();
5523 // ONE expiry instant, computed ONCE and used by all three places that state it: the
5524 // persisted record, the RFC 9068 `exp` claim, and the RFC 6749 s5.1 `expires_in` member. A
5525 // token whose signed `exp` disagrees with the expiry this server enforces on introspection
5526 // is worse than either bound on its own, because the two halves of the deployment then
5527 // disagree about when the token died.
5528 //
5529 // The ceiling can only ever SHORTEN the lifetime: `min` of the ordinary deadline and
5530 // whatever the caller capped it at.
5531 let expires_at = {
5532 let ordinary = saturating_deadline(now, self.config.access_token_ttl);
5533 match lifetime_ceiling {
5534 Some(ceiling) => ordinary.min(ceiling),
5535 None => ordinary,
5536 }
5537 };
5538
5539 let issues_refresh = allow_refresh
5540 && self.config.issue_refresh_tokens
5541 && client.allows_grant(GrantType::RefreshToken);
5542
5543 // ONE DRAW FOR ALL THREE ARTIFACTS. `getrandom::fill` is a syscall whose cost is almost
5544 // entirely per CALL rather than per byte (see `hex_encode`), and an issuance needed up to
5545 // three of them: a family id, an access token, and a refresh token. MEASURED at roughly
5546 // 1 us saved per issuance, which is about half of `client_credentials_issue` and a fifth of
5547 // an authorization code redemption.
5548 //
5549 // Scoped to a block with NO await inside it, deliberately. The buffer must not survive
5550 // across a suspension point or its 80 bytes join the coroutine's state, and this future is
5551 // held under tokio's 2048-byte debug boxing threshold by `tests/allocation.rs`. The refresh
5552 // token is therefore encoded HERE, before the buffer dies, and carried as an `Option<String>`
5553 // to its use below rather than being drawn there.
5554 //
5555 // Drawing 80 bytes when only 32 will be used is free: the syscall is the cost, and the
5556 // alternative (branching on `issues_refresh` before the draw) would buy nothing and add a
5557 // path where the unused half is not overwritten.
5558 let (family_id, access_token, pending_refresh) = {
5559 let mut entropy = [0u8; 80];
5560 // `ok_or_else` rather than `expect`: this is the token endpoint, every other
5561 // fallible step of which answers `server_error`, and a panic here aborts the host
5562 // in a host built with `panic = "abort"`. See `try_random_hex`.
5563 getrandom::fill(&mut entropy).map_err(|_| randomness_error())?;
5564 // The family id is minted (or inherited) BEFORE the access token, because the access
5565 // token has to carry it: RFC 9700 section 4.14.2 revokes the tokens of the whole grant
5566 // on detected reuse, and an access token with no family is unreachable from that event.
5567 // A grant that issues no refresh chain has no family, and allocates nothing for one:
5568 // there is no chain to reuse, so there is nothing a reuse detection could revoke.
5569 let family_id = match (&chain, issues_refresh) {
5570 (Some(c), _) => Some(c.family_id.clone()),
5571 (None, true) => Some(hex_encode(&entropy[..16])),
5572 (None, false) => None,
5573 };
5574 (
5575 family_id,
5576 hex_encode(&entropy[16..48]),
5577 issues_refresh.then(|| hex_encode(&entropy[48..])),
5578 )
5579 };
5580
5581 // Cloned ONLY when a sink is installed: both values are consumed by the records written
5582 // below, and an unobserved host must not pay two clones per issued token. BOXED because
5583 // this local lives across every await in the issuance, and the token future is 1936 bytes
5584 // against tokio's 2048-byte debug boxing threshold; 8 bytes here rather than 48 is 40
5585 // bytes of headroom for every caller, bought with one small allocation paid only by a
5586 // host that asked to be told about issuance.
5587 let audit = self
5588 .hooks
5589 .is_observed()
5590 .then(|| Box::new((subject.clone(), family_id.clone())));
5591
5592 // RFC 9068, when the host configured it: the WIRE token becomes a signed JWT and the
5593 // random string above becomes its `jti`. The record below is persisted either way, keyed
5594 // by whatever the client will actually present, so RFC 7662 introspection and RFC 7009
5595 // revocation keep working and a revoked JWT is genuinely dead at this AS rather than
5596 // merely deprecated.
5597 // Bound before it is matched rather than matched directly, because the signing input is
5598 // then visibly the LAST thing computed before the suspension. MEASURED: this makes no
5599 // difference to the frame either way (1704 bytes on `--all-features` both ways); it is
5600 // written for the reader, and the measurement is recorded so nobody has to repeat it.
5601 //
5602 // WHAT IS LIVE ACROSS THE AWAIT IS THE WHOLE ISSUANCE, and an earlier version of this
5603 // comment claimed otherwise. `now`, `issues_refresh`, `family_id`, `pending_refresh`,
5604 // `audit`, `subject`, `scope`, `resource`, `details`, `authentication`, `client` and
5605 // `bound` are all built above and read below, so they are all in the frame. The SIZE
5606 // claim is unaffected and is gated by `tests/allocation.rs`; the description was simply
5607 // wrong, and it matters because that await is the host's `Es256Signer` and is unbounded.
5608 #[cfg(feature = "jwt")]
5609 let prepared = self.access_token_signing_input(
5610 client,
5611 subject.as_deref(),
5612 &scope,
5613 &resource,
5614 &details,
5615 now,
5616 expires_at,
5617 access_token,
5618 bound,
5619 &actor,
5620 &authentication,
5621 )?;
5622 #[cfg(feature = "jwt")]
5623 let access_token = match prepared {
5624 Err(opaque) => opaque,
5625 // The ONE await the RFC 9068 path adds, and the only thing live across it is this
5626 // `String` and a borrow of the configuration. A signer that cannot sign mints NOTHING:
5627 // there is no fallback to an opaque token and no unsigned token, because an access
5628 // token this server could not sign is one it must not issue.
5629 Ok((jwt, input)) => jwt.finish_signing(input).await.map_err(|e| {
5630 let _ = e;
5631 ErrorResponse::new(ErrorCode::ServerError)
5632 })?,
5633 };
5634 let refused = self
5635 .store
5636 .put_token(IssuedToken {
5637 // RFC 9449 s6: the binding is recorded on the AS side too, not only in the token,
5638 // so that RFC 7662 introspection can report it and a resource server can check it
5639 // without having to parse a token this server may have issued as opaque.
5640 #[cfg(feature = "dpop")]
5641 jkt: bound.jkt.map(Box::from),
5642 // The record was written this way before there was anybody to read it, because the
5643 // record is the half that cannot be added later; a registered resource server
5644 // reads it now (`ServerConfig::resource_servers`).
5645 //
5646 // RFC 8705 s3, and the same argument as `jkt` immediately above: an opaque
5647 // token carries its binding nowhere else, so s3.2 introspection could not
5648 // report it if it were not written down here.
5649 #[cfg(feature = "mtls")]
5650 x5t_s256: bound.cred.certificate.map(|c| Box::new(*c.thumbprint())),
5651 access_token: access_token.clone(),
5652 client_id: client.client_id.clone(),
5653 subject: subject.clone(),
5654 scope: scope.clone(),
5655 resource: resource.clone(),
5656 // RFC 9396 s7: the details as granted, assigned to this access token. This
5657 // is what introspection (s9.2) reports and what the s9.1 JWT claim carries.
5658 #[cfg(feature = "rar")]
5659 authorization_details: details.clone().into_details(),
5660 issued_at: now,
5661 // The GRANT's instant, not this issuance's: a barrier is compared against it, and
5662 // a rotation writing at `now` must not thereby outlive the revocation that killed
5663 // the decision it descends from.
5664 grant_established_at,
5665 // Computed at the top of this function, so the record, the signed `exp` and the
5666 // `expires_in` below are one instant stated three times rather than three.
5667 expires_at,
5668 family_id: family_id.clone(),
5669 // RFC 8693 s4.1: the token records who authority was delegated TO, so that RFC
5670 // 7662 introspection can report it. An opaque token carries it nowhere else.
5671 #[cfg(feature = "token-exchange")]
5672 act: actor.act.clone(),
5673 // RFC 9470 s6.2: the token reports the authentication behind it, so introspection can
5674 // answer the question the resource server's challenge asked.
5675 #[cfg(feature = "consent")]
5676 authentication: authentication.authentication.clone(),
5677 })
5678 .await
5679 .map_err(storage_error)?;
5680 // The grant was revoked while this issuance was in flight, most likely across the signing
5681 // await immediately above, which is a network round trip when the host's `Es256Signer`
5682 // fronts a KMS. Nothing was written, so there is nothing to undo; the client is told its
5683 // grant is invalid, which by now it is.
5684 if refused.is_refused() {
5685 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
5686 .with_description("the grant was revoked while this token was being issued"));
5687 }
5688
5689 let refresh_token = if issues_refresh {
5690 let expires_at = match &chain {
5691 Some(c) => c.expires_at,
5692 None => self
5693 .config
5694 .refresh_token_ttl
5695 .map(|ttl| saturating_deadline(now, ttl)),
5696 };
5697 // Drawn in the single `getrandom` call at the top of this function; `issues_refresh`
5698 // is what decided both that draw and this branch, so the value is always present.
5699 let rt = pending_refresh.expect("issues_refresh decided both");
5700 let refused = self
5701 .store
5702 .put_refresh_token(RefreshTokenRecord {
5703 // RFC 9449 s5: the chain remembers the key it was issued to, and rotation
5704 // checks it. See the check in `refresh_token`.
5705 #[cfg(feature = "dpop")]
5706 jkt: bound.jkt.map(Box::from),
5707 // RFC 8705 s3: the chain remembers the certificate it was issued to, and
5708 // rotation checks it. See the check in `refresh_token`.
5709 #[cfg(feature = "mtls")]
5710 x5t_s256: bound.cred.certificate.map(|c| Box::new(*c.thumbprint())),
5711 refresh_token: rt.clone(),
5712 client_id: client.client_id.clone(),
5713 subject,
5714 scope: scope.clone(),
5715 // The chain remembers what it may narrow from on the next rotation.
5716 resource,
5717 #[cfg(feature = "rar")]
5718 authorization_details: details.clone().into_details(),
5719 expires_at,
5720 // CARRIED, never restamped: see `RefreshTokenRecord::grant_established_at`.
5721 grant_established_at,
5722 // Present whenever a refresh token is: `issues_refresh` is what decided both.
5723 family_id: family_id.unwrap_or_default(),
5724 state: RefreshTokenState::Active,
5725 // Carried, never restamped: see `RefreshTokenRecord::authentication`.
5726 #[cfg(feature = "consent")]
5727 authentication: authentication.authentication,
5728 })
5729 .await
5730 .map_err(storage_error)?;
5731 // Revoked BETWEEN the two writes. This is the case that needs undoing rather than
5732 // merely refusing: the access token a few lines above is already in the store, and
5733 // leaving it there would hand the caller a live credential minted from a grant that no
5734 // longer exists, which is the resurrection defect wearing a different hat.
5735 if refused.is_refused() {
5736 self.undo_issuance(&access_token).await;
5737 return Err(ErrorResponse::new(ErrorCode::InvalidGrant)
5738 .with_description("the grant was revoked while this token was being issued"));
5739 }
5740 Some(rt)
5741 } else {
5742 None
5743 };
5744
5745 // Emitted after BOTH records are persisted, so the event describes a token that exists.
5746 if let Some(audit) = &audit {
5747 self.hooks.emit(|| Event::TokenIssued {
5748 client_id: client.client_id.as_str(),
5749 grant_type,
5750 subject: audit.0.as_deref(),
5751 scope: &scope,
5752 family_id: audit.1.as_deref(),
5753 refresh_issued: refresh_token.is_some(),
5754 });
5755 }
5756
5757 Ok(TokenResponse {
5758 access_token,
5759 // RFC 9449 s5: a token bound to a proof key is a `DPoP` token and not a `Bearer` one,
5760 // and the difference is exactly what tells the client, and any resource server reading
5761 // the response, that the token must be presented with a proof.
5762 #[cfg(feature = "dpop")]
5763 token_type: match bound.jkt {
5764 Some(_) => TokenType::Dpop,
5765 None => TokenType::Bearer,
5766 },
5767 #[cfg(not(feature = "dpop"))]
5768 token_type: TokenType::Bearer,
5769 // RFC 6749 s5.1: the lifetime IN SECONDS FROM NOW of the token just issued, derived
5770 // from the same `expires_at` the record and the signed `exp` carry. Not
5771 // `access_token_ttl`, which is only the same number when nothing capped the lifetime;
5772 // reporting the uncapped TTL for a capped token would have the client keep using a
5773 // token this server has already stopped honouring.
5774 //
5775 // `unwrap_or_default` is the fail-closed direction: a ceiling already in the past
5776 // yields zero rather than an underflow, and zero tells the client the token is spent.
5777 expires_in: expires_at.duration_since(now).unwrap_or_default().as_secs(),
5778 refresh_token,
5779 scope: (!scope.is_empty()).then(|| scope.to_string()),
5780 // RFC 9396 s7: what was GRANTED, from the same value that reaches the stored record
5781 // and the signed token, so the three cannot disagree about what this token authorizes.
5782 #[cfg(feature = "rar")]
5783 authorization_details: details.into_details(),
5784 })
5785 }
5786
5787 /// Opaque-token introspection: `Ok(Some(_))` only for a known, unexpired token.
5788 ///
5789 /// This is the host-facing form, which hands back the whole record. The RFC 7662 WIRE form is
5790 /// [`AuthorizationServer::introspection_response`], which answers the reduced, caller-scoped
5791 /// document the RFC defines.
5792 pub async fn introspect(
5793 &self,
5794 access_token: &str,
5795 ) -> Result<Option<std::sync::Arc<IssuedToken>>, StorageError> {
5796 Ok(self
5797 .store
5798 .get_token(access_token)
5799 .await?
5800 .filter(|t| self.clock.now() < t.expires_at))
5801 }
5802
5803 /// RFC 7662 token introspection, as the protected endpoint the RFC describes.
5804 ///
5805 /// The caller must authenticate (section 2.1), and a token the caller has no relationship to
5806 /// reads as inactive rather than as a description of somebody else's grant: section 2.2 says
5807 /// the response for an invalid token is simply `active: false`, and section 4 warns that this
5808 /// endpoint otherwise becomes an oracle for probing tokens a caller does not hold.
5809 ///
5810 /// # The two callers
5811 ///
5812 /// Section 1 names a protected resource as the primary consumer, and section 2.1 permits a
5813 /// client to introspect its own token. Both are served:
5814 ///
5815 /// - the token's OWN CLIENT, which sees the whole record; and
5816 /// - a RESOURCE SERVER declared in [`ServerConfig::resource_servers`], which sees a token only
5817 /// when the token's RFC 8707 [`IssuedToken::resource`] set names one of the identifiers that
5818 /// resource server is registered for.
5819 ///
5820 /// Everything else is `{"active": false}`, including a live token belonging to another client
5821 /// and addressed to another resource server. A deployment that registers no resource servers
5822 /// answers the token's own client and nobody else, which is what this server did through
5823 /// 0.9.1.
5824 ///
5825 /// The resource server is not a new kind of principal and gets no new credential: it registers
5826 /// as an ordinary confidential client and authenticates here exactly as any client does. See
5827 /// [`ServerConfig::resource_servers`] for why the authorization half is not optional -- and
5828 /// for what a resource server's traffic costs the client-authentication rate limit, which is
5829 /// the one thing about this endpoint that a host has to size rather than accept. A resource
5830 /// server calls it once per request at the protected resource, and the default budget was
5831 /// derived from a client's token traffic.
5832 ///
5833 /// # What a resource server is not told
5834 ///
5835 /// RFC 7662 section 5: "omitting privacy-sensitive information from an introspection response
5836 /// is the simplest way of minimizing privacy issues". The sensitive thing here is not only the
5837 /// user's identity, which the resource server needs and gets. It is the SHAPE OF THE GRANT:
5838 /// which OTHER services this user's token is good at. Two members carry that fact and both are
5839 /// narrowed to the asking resource server:
5840 ///
5841 /// - `aud`, to the RFC 8707 identifiers that resource server is registered for; and
5842 /// - `authorization_details`, to the RFC 9396 section 2.2 elements whose `locations` name one
5843 /// of those identifiers, or that carry no `locations` at all. A kept element has its own
5844 /// `locations` narrowed too, so an element addressed to two resource servers does not smuggle
5845 /// the second one's URI past the filter. Section 9.2 asks for precisely this ("filtered and
5846 /// extended for the RS making the introspection request"), and section 9.1 says the same of
5847 /// the JWT form.
5848 ///
5849 /// An earlier 0.9.2 draft narrowed `aud` and shipped `authorization_details` whole, which meant
5850 /// the disclosure
5851 /// the first refused was re-made verbatim by the second, with the actions and privileges
5852 /// granted elsewhere attached. That is fixed rather than accepted, and the alternative
5853 /// resolution -- STOP NARROWING `aud`, and treat a registered resource server as a semi-trusted
5854 /// party that sees the grant as granted -- was rejected. A resource server is registered for
5855 /// the identifiers it answers for and nothing wider; a deployment adding a second protected
5856 /// resource would otherwise be silently telling the first one about it, and the party who pays
5857 /// for that is the user, who is not present and cannot be asked. Consistency in the other
5858 /// direction is cheaper to buy and costs somebody else.
5859 ///
5860 /// Two members are NOT narrowed, and the omission is a decision rather than an oversight:
5861 ///
5862 /// - `scope` is the whole grant's scope set. Nothing in this crate maps a scope to a resource
5863 /// server -- there is no per-resource catalogue to filter against -- so any narrowing would
5864 /// be a guess at which strings "belong" to the asker, and a resource server that silently
5865 /// loses a scope refuses access the resource owner granted. A scope is also a token in the
5866 /// deployment's own vocabulary; unlike a `locations` URI it does not NAME another service.
5867 /// - `act` (RFC 8693 section 4.1) describes who is acting in the call this resource server is
5868 /// handling, not where else the grant reaches.
5869 ///
5870 /// The cost of the narrowing, stated plainly: a resource server given a filtered
5871 /// `authorization_details` cannot distinguish "not granted" from "not for you". That is the
5872 /// same indistinguishability the `aud` narrowing already imposes, and it is the harmless
5873 /// direction -- both readings oblige the resource server to refuse, because an element it is
5874 /// not named in is one it must not act on either way. The disclosure direction has no such
5875 /// symmetry. A caller that needs the unfiltered record is the token's OWN CLIENT, and it still
5876 /// gets it; so does a host, through [`AuthorizationServer::introspect`].
5877 pub async fn introspection_response(
5878 &self,
5879 client_id: &ClientId,
5880 client_secret: Option<&str>,
5881 token: &str,
5882 ) -> Result<IntrospectionResponse, ErrorResponse> {
5883 self.introspection_response_with_credential(
5884 client_id,
5885 &ClientCredential::secret(client_secret),
5886 token,
5887 )
5888 .await
5889 }
5890
5891 /// RFC 7662 introspection for a caller authenticating with any credential this server accepts,
5892 /// including an RFC 7523 assertion. See
5893 /// [`AuthorizationServer::device_authorization_with_credential`] on why this is an addition
5894 /// rather than a replacement.
5895 pub async fn introspection_response_with_credential(
5896 &self,
5897 client_id: &ClientId,
5898 cred: &ClientCredential<'_>,
5899 token: &str,
5900 ) -> Result<IntrospectionResponse, ErrorResponse> {
5901 let client = self.authenticate_client(client_id, cred).await?;
5902 // RFC 7662 section 2.1 requires the endpoint to be protected, and section 4 says it MUST
5903 // NOT be publicly available, because it otherwise describes any token an attacker has
5904 // merely obtained a copy of. A PUBLIC client has no secret to verify, so "authenticated as
5905 // a public client" is a sentence true of every caller on the internet: naming a client id
5906 // is not authentication, and an ownership check made against an identity anyone may claim
5907 // is not an access control. Same refusal as `client_credentials_token`, for the same
5908 // reason.
5909 //
5910 // BARE, AND THE REASON GOES TO THE AUDIT CHANNEL. It used to carry the description
5911 // "introspection requires a confidential client", which was a client-existence oracle:
5912 // an unknown client id and a confidential client with the wrong secret both leave
5913 // `authenticate_client` as a BARE `invalid_client` (see "THE ONE EXIT"), so a description
5914 // here was the one answer that meant "this id is registered, and it is public". That is
5915 // precisely the distinction the whole credential path collapses, rebuilt one endpoint
5916 // downstream of it. 0.9.2 also turns this endpoint into an advertised resource-server-
5917 // facing surface, so it is now a probe an attacker is invited to make.
5918 //
5919 // The operator still gets the sentence — `ClientAuthFailure::NotConfidential` — because
5920 // the usual cause is a misregistered resource server rather than an attack, and a bare
5921 // refusal with nothing in the log would be unactionable. The rate limiter is NOT charged
5922 // again: `authenticate_client` already recorded this attempt's outcome, and recording a
5923 // second one for a single request would make this endpoint count double.
5924 if matches!(client.auth, crate::client::ClientAuth::Public) {
5925 self.hooks.emit(|| Event::ClientAuthenticationFailed {
5926 client_id: client_id.as_str(),
5927 failure: ClientAuthFailure::NotConfidential,
5928 });
5929 return Err(ErrorResponse::new(ErrorCode::InvalidClient));
5930 }
5931 let record = self.introspect(token).await.map_err(storage_error)?;
5932 // WHOSE QUESTION IS THIS. RFC 7662 has two legitimate callers and they are answered from
5933 // the same arm but not with the same document, so the viewpoint is decided once, here,
5934 // before anything is copied out of the record.
5935 //
5936 // `None` covers unknown, expired, somebody else's, and addressed-to-some-other-resource-
5937 // server. All four are `{"active": false}` on purpose: section 2.2 gives exactly one answer
5938 // for a token the caller has not proven a relationship to, and distinguishing "no such
5939 // token" from "a live token that is not yours" would rebuild the oracle section 4 warns
5940 // about out of the error channel instead of the response body.
5941 let view = record
5942 .as_ref()
5943 .and_then(|t| self.introspection_view(&client, t));
5944 Ok(match (record, view) {
5945 (Some(t), Some(view)) => IntrospectionResponse {
5946 active: true,
5947 // RFC 7662 s2.2, THE WHOLE GRANT'S SCOPE SET, to both viewpoints, and deliberately
5948 // so: see "What a resource server is not told" on this method for why this member
5949 // is not narrowed the way `aud` and `authorization_details` are.
5950 scope: (!t.scope.is_empty()).then(|| t.scope.to_string()),
5951 client_id: Some(t.client_id.as_str().to_string()),
5952 sub: t.subject.clone(),
5953 #[cfg(feature = "dpop")]
5954 token_type: Some(match t.jkt {
5955 Some(_) => TokenType::Dpop,
5956 None => TokenType::Bearer,
5957 }),
5958 #[cfg(not(feature = "dpop"))]
5959 token_type: Some(TokenType::Bearer),
5960 exp: unix_seconds(t.expires_at),
5961 iat: unix_seconds(t.issued_at),
5962 iss: Some(self.issuer_identifier().to_string()),
5963 // RFC 7662 s2.2: `aud` is OPTIONAL, and this server has one to report exactly when
5964 // the grant carried RFC 8707 resource indicators. Omitted rather than empty when it
5965 // does not: see `IntrospectionResponse::aud`.
5966 //
5967 // THIS IS THE ONE MEMBER THE TWO VIEWPOINTS DO NOT SHARE. The token's own client
5968 // sees the whole set, because it asked for it and already holds it. A RESOURCE
5969 // SERVER sees only the identifiers it is itself registered for, because the rest of
5970 // the set is a list of the OTHER resource servers this user's token is good at, and
5971 // section 5's privacy considerations do not stop at the user's identity: telling
5972 // api.example that this token also works at payroll.example discloses the shape of
5973 // somebody's account to a third party that has no part in it. Narrowing here rather
5974 // than at the record keeps `aud` a true statement in both documents; it is the same
5975 // claim, answered to the extent the asker is entitled to it.
5976 aud: match &view {
5977 IntrospectionView::OwningClient => {
5978 (!t.resource.is_empty()).then(|| t.resource.clone())
5979 }
5980 IntrospectionView::ResourceServer(mine) => Some(mine.clone()),
5981 },
5982 // RFC 9470 s6.2. A resource server that sent a step-up challenge has to be able to
5983 // see whether the token it now holds satisfies it; without these two it would have
5984 // to take the client's word for that, which is the whole thing the challenge exists
5985 // to avoid.
5986 #[cfg(feature = "consent")]
5987 auth_time: t
5988 .authentication
5989 .as_ref()
5990 .and_then(|a| unix_seconds(a.auth_time)),
5991 #[cfg(feature = "consent")]
5992 acr: t
5993 .authentication
5994 .as_ref()
5995 .and_then(|a| a.acr.as_deref().map(str::to_string)),
5996 // RFC 9396 s9.2: the details as a top-level member of the introspection
5997 // response. For an OPAQUE token this is the ONLY way a resource server can
5998 // learn what the token actually authorizes, which is the whole point of the
5999 // parameter, and a resource server registered under
6000 // `ServerConfig::resource_servers` is now the caller that receives it.
6001 //
6002 // FILTERED FOR THE ASKER, for the reason `aud` above is. An earlier 0.9.2 draft
6003 // left it unfiltered until the audit noticed the two members carry the same fact. A
6004 // section 2.2 element has `locations`, which NAMES RESOURCE SERVERS BY URI, so
6005 // handing api.example the whole array says "this token also works at
6006 // payroll.example" in the very breath `aud` refuses to say it, and adds the
6007 // actions and privileges granted there. Section 9.2 asks for exactly this:
6008 // "filtered and extended for the RS making the introspection request".
6009 #[cfg(feature = "rar")]
6010 authorization_details: match &view {
6011 IntrospectionView::OwningClient => t.authorization_details.clone(),
6012 IntrospectionView::ResourceServer(mine) => {
6013 details_for_resource_server(&t.authorization_details, mine)
6014 }
6015 },
6016 // RFC 9449 s6.1 and RFC 8705 s3.2, in ONE RFC 7800 s3.1 object. Both mechanisms
6017 // register a member of `cnf` and a token can carry both, so this is built from
6018 // every binding the record has rather than from whichever one happens to be
6019 // checked first. Omitted entirely when there is none.
6020 //
6021 // A caller that introspects must be able to confirm the binding, or the binding
6022 // stops at this server and the caller is back to trusting a bearer string.
6023 // That caller is the token's own client or, since 0.9.2, the resource server the
6024 // token is addressed to; both need to confirm the binding for the same reason.
6025 #[cfg(any(feature = "dpop", feature = "mtls"))]
6026 cnf: {
6027 let cnf = crate::token::Confirmation {
6028 #[cfg(feature = "dpop")]
6029 jkt: t.jkt.as_deref().map(str::to_string),
6030 #[cfg(feature = "mtls")]
6031 x5t_s256: t.x5t_s256.as_deref().copied(),
6032 };
6033 (!cnf.is_empty()).then_some(cnf)
6034 },
6035 // RFC 8693 s4.1, and the reason it is on the record at all: for an OPAQUE token
6036 // this is the only channel a resource server has for learning that what it holds
6037 // is a DELEGATION rather than the subject acting directly.
6038 //
6039 // Not narrowed, and unlike `scope` there is nothing here that could be: `act`
6040 // describes WHO IS ACTING in the call this resource server is being made, not
6041 // where else the grant reaches. It names no other resource server, so it does not
6042 // carry the fact `aud` withholds, and withholding it would leave the RS unable to
6043 // tell a delegated call from a direct one -- which is the one thing section 4.1
6044 // exists to tell it.
6045 #[cfg(feature = "token-exchange")]
6046 act: t.act.as_deref().cloned(),
6047 },
6048 // Unknown, expired, somebody else's, or addressed to a different resource server.
6049 // All four are one answer on purpose; see `view` above.
6050 _ => IntrospectionResponse::inactive(),
6051 })
6052 }
6053
6054 /// Which RFC 7662 viewpoint `client` holds on `token`, or `None` for the callers section 2.2
6055 /// answers `{"active": false}`.
6056 ///
6057 /// Ownership is checked FIRST and wins outright. A client that is both the token's own client
6058 /// and a registered resource server is answered as the owner, which is the wider of the two
6059 /// documents; being told about your own token is not a privilege that a second, narrower role
6060 /// should be able to take away.
6061 fn introspection_view(
6062 &self,
6063 client: &Client,
6064 token: &IssuedToken,
6065 ) -> Option<IntrospectionView> {
6066 if token.client_id == client.client_id {
6067 return Some(IntrospectionView::OwningClient);
6068 }
6069 // The resource-server channel. A registration matches only by NAMING an identifier the
6070 // token actually carries, so a token whose grant requested no resource indicator at all
6071 // (`token.resource` empty) matches nothing here and is refused to every resource server.
6072 // That is deliberate and it is the whole defence: see `ServerConfig::resource_servers`.
6073 let mine: Vec<String> = self
6074 .config
6075 .resource_servers
6076 .as_deref()
6077 .unwrap_or(&[])
6078 .iter()
6079 .filter(|rs| rs.client_id == client.client_id)
6080 .flat_map(|rs| rs.resources.iter())
6081 .filter(|id| token.resource.iter().any(|r| r == *id))
6082 .map(|id| id.to_string())
6083 .fold(Vec::new(), |mut acc, id| {
6084 // Deduped because the same identifier may legitimately appear in two registrations
6085 // for one client (see `ResourceServerRegistration`), and `aud` repeating it would
6086 // be a malformed-looking document produced by a configuration that is not wrong.
6087 if !acc.contains(&id) {
6088 acc.push(id);
6089 }
6090 acc
6091 });
6092 (!mine.is_empty()).then_some(IntrospectionView::ResourceServer(mine))
6093 }
6094
6095 /// Record that a resource owner has consented to a client acting for them.
6096 ///
6097 /// One live consent per (client, subject) pair: an existing record is WIDENED in place, keeping
6098 /// its identifier and its original `granted_at`, so a user who approves one more scope next
6099 /// month still sees one entry rather than two and withdrawing it withdraws the whole
6100 /// relationship. See [`crate::consent::ConsentRecord::extend`].
6101 ///
6102 /// The library NEVER calls this for itself. Recording consent is a statement that a user agreed
6103 /// to something, and this crate has no way to know that: it never sees a user. The host calls
6104 /// it once its own consent step has actually been answered.
6105 ///
6106 /// # Concurrency, and what this used to concede
6107 ///
6108 /// This is a read-modify-write: it looks for an existing record, widens it, and writes it
6109 /// back. It is now a COMPARE-AND-SWAP against what it read
6110 /// ([`Storage::compare_and_swap_consent`]), retried once, and both halves of that matter.
6111 ///
6112 /// The half this doc used to argue away was two overlapping FIRST approvals each finding
6113 /// nothing and each creating a record, leaving the pair with two. The argument was that the
6114 /// records are additive so the damage is a possible re-prompt. That much was true, and it is
6115 /// no longer relevant: the create is conditional on the pair still being empty, so the loser
6116 /// sees the winner's record and widens it instead.
6117 ///
6118 /// The half this doc never considered is the one that was not benign, and it is the reason
6119 /// this changed. The second writer it reasoned about was another `record_consent`. The writer
6120 /// that actually mattered was [`AuthorizationServer::withdraw_consent`]: a widen that read a
6121 /// record, and wrote it back after the user clicked withdraw, RESURRECTED a consent the user
6122 /// had destroyed, and every later authorization request was answered from it. "Benign in the
6123 /// direction that matters" was a statement about the wrong direction. See the resurrection
6124 /// rule in the [`crate::store`] module docs.
6125 ///
6126 /// A host no longer owes this path a lock of its own.
6127 #[cfg(feature = "consent")]
6128 pub async fn record_consent(
6129 &self,
6130 client_id: &ClientId,
6131 subject: &str,
6132 scope: &ScopeSet,
6133 resource: &[String],
6134 authentication: Option<crate::consent::Authentication>,
6135 ) -> Result<crate::consent::ConsentRecord, StorageError> {
6136 // REFUSED AT CREATION, because refusing at withdrawal is refusing at the one operation
6137 // that undoes damage.
6138 //
6139 // `Storage::revoke_consent` rejects an empty `client_id` or `subject`, and it reads them
6140 // out of the STORED record rather than off its argument — so a record created with an
6141 // empty subject can never be withdrawn by any input at all. The record stands, the cascade
6142 // never runs, and every token and refresh chain beneath it stays live for its full
6143 // lifetime, while `withdraw_consent` answers an error that contradicts its own
6144 // documented contract ("withdrawing a consent that is already gone is `Ok(0)`, not an
6145 // error"). A host whose subject resolver can yield the empty string — which this crate
6146 // never checks, because a subject is the host's own vocabulary for users — reaches that
6147 // state through the ordinary API.
6148 //
6149 // A `StorageError` rather than a new error type: this is the same class of refusal
6150 // `revoke_consent` already answers with, and a consent that names nobody is not a consent.
6151 if subject.is_empty() {
6152 return Err(StorageError::new(
6153 "a consent must name a subject; an empty subject cannot be withdrawn",
6154 ));
6155 }
6156 let now = self.clock.now();
6157 // TWO attempts, and no more. One retry is what a compare-and-swap needs to absorb an
6158 // ordinary lost race (the other writer created or widened the record, and this call can
6159 // simply widen theirs instead); a loop would be a spin against a withdrawal that is going
6160 // to keep refusing, on a path a human drives by clicking. The second failure is reported
6161 // as a storage error, which is the honest answer: the consent was NOT recorded.
6162 // WHAT THE FIRST ATTEMPT SAW, and the retry may not contradict it. A call that started as
6163 // a WIDEN must not become a CREATE on its second attempt: the only way the record can have
6164 // vanished between them is that it was withdrawn, and turning that into a fresh consent
6165 // hands the user back a live grant moments after they ended it. The other direction is
6166 // fine and is the ordinary lost race: a call that started as a create and now finds a
6167 // record simply widens that record instead.
6168 //
6169 // This is NOT enforced by refusing on the barrier, deliberately. A consent barrier stands
6170 // for the longest token lifetime the server mints, and refusing every create for that long
6171 // would mean a user who withdraws an application and approves it again five minutes later
6172 // is told no, for an hour, with nothing to tell them why. The rule that is actually needed
6173 // is narrower: within ONE call, the shape may not change.
6174 let mut started_as_widen = None;
6175 for attempt in 0..2 {
6176 // CLONED out of the shared snapshot, because this is the one consent path that MUTATES
6177 // what it read (`extend` below widens the grant in place). An `Arc` cannot be widened
6178 // while the store still holds it, so the clone the read used to make happens here
6179 // instead: the cost moved, it did not go away. It is paid once per host consent
6180 // decision, against the read being free on the authorization endpoint, which runs per
6181 // request. See `remembered_consent`.
6182 let existing = self.store.find_consent(client_id, subject).await?;
6183 let expected = existing.as_deref().cloned();
6184 match started_as_widen {
6185 None => started_as_widen = Some(expected.is_some()),
6186 // Started as a widen, and the record is gone. It was withdrawn while this call was
6187 // in flight, which is the direction that is not benign.
6188 Some(true) if expected.is_none() => {
6189 return Err(StorageError::new(
6190 "the consent was withdrawn while it was being recorded",
6191 ))
6192 }
6193 Some(_) => {}
6194 }
6195 let _ = attempt;
6196 let mut record = match &expected {
6197 Some(existing) => existing.clone(),
6198 None => crate::consent::ConsentRecord {
6199 // 16 bytes of OS randomness, hex encoded, the same shape as every other opaque
6200 // identifier this server mints. It is not a credential (see the field's own
6201 // docs), but it names a record that can end a user's sessions, so it must not
6202 // be something a third party can produce by guessing two strings it already
6203 // knows.
6204 // `?` rather than a panic, for the reason `try_random_hex` gives. This
6205 // function answers `StorageError`, so that is the shape the refusal takes
6206 // here; the host learns what happened from its own logs either way.
6207 consent_id: try_random_hex(16)
6208 .ok_or_else(|| {
6209 StorageError::new(
6210 "the OS would not provide randomness for a consent id",
6211 )
6212 })?
6213 .into_boxed_str(),
6214 client_id: client_id.clone(),
6215 subject: subject.into(),
6216 scope: ScopeSet::empty(),
6217 resource: Vec::new(),
6218 granted_at: now,
6219 authentication: None,
6220 },
6221 };
6222 record.extend(scope, resource);
6223 // The LATEST authentication replaces the previous one: it is what the user just did,
6224 // and it is what an RFC 9470 `max_age` on the next request has to be measured against.
6225 // A host that reports nothing this time leaves the previous report standing rather
6226 // than erasing it, because "did not say" is not "no longer authenticated".
6227 if let Some(a) = &authentication {
6228 record.authentication = Some(Box::new(a.clone()));
6229 }
6230 // The write happens only if the pair still holds exactly what was read a moment ago:
6231 // still nothing when creating, still that record when widening. A withdrawal in
6232 // between refuses BOTH shapes, which is the point.
6233 if self
6234 .store
6235 .compare_and_swap_consent(expected.as_ref(), record.clone())
6236 .await?
6237 {
6238 return Ok(record);
6239 }
6240 }
6241 Err(StorageError::new(
6242 "consent record changed concurrently twice; not recorded",
6243 ))
6244 }
6245
6246 /// The consent this user has already given this client, if any.
6247 ///
6248 /// This ANSWERS a question; it does not make a decision, and nothing in this crate approves an
6249 /// authorization request on the strength of it. See the `http` feature's
6250 /// `ServiceBuilder::with_approval_resolver`: the library reports what it remembers
6251 /// and the host decides what that is worth, because "the user agreed to this once" and "the
6252 /// user agrees to this now" are different sentences and only the host can tell them apart.
6253 #[cfg(feature = "consent")]
6254 pub async fn remembered_consent(
6255 &self,
6256 client_id: &ClientId,
6257 subject: &str,
6258 ) -> Result<Option<std::sync::Arc<crate::consent::ConsentRecord>>, StorageError> {
6259 self.store.find_consent(client_id, subject).await
6260 }
6261
6262 /// Everything one resource owner has consented to, so a host can show a user what they have
6263 /// granted. Without this a user cannot SEE what they gave away, which is half of why this
6264 /// feature exists at all.
6265 #[cfg(feature = "consent")]
6266 pub async fn consents_for_subject(
6267 &self,
6268 subject: &str,
6269 ) -> Result<Vec<std::sync::Arc<crate::consent::ConsentRecord>>, StorageError> {
6270 self.store.consents_for_subject(subject).await
6271 }
6272
6273 /// WITHDRAW a consent, revoking everything issued under it. Returns how many records the
6274 /// cascade removed.
6275 ///
6276 /// This is the point of the whole feature. A withdrawal that left tokens alive would be worse
6277 /// than no withdrawal at all, because the user would believe they had stopped something they
6278 /// had not, so the cascade is one storage operation
6279 /// ([`crate::store::Storage::revoke_consent`]) and it reaches every family the consent ever
6280 /// produced, plus the authorization codes and approved-but-unpolled device grants that would
6281 /// otherwise mint tokens seconds later.
6282 ///
6283 /// Withdrawing a consent that is already gone is `Ok(0)`, not an error.
6284 #[cfg(feature = "consent")]
6285 pub async fn withdraw_consent(&self, consent_id: &str) -> Result<u64, StorageError> {
6286 // Read first, purely so the audit event can name the client and the user. Two round trips
6287 // on a path a person drives by hand is not a cost worth optimising away, and an event that
6288 // said only "some consent was withdrawn" is an event nobody can act on.
6289 let record = self.store.get_consent(consent_id).await?;
6290 let records_revoked = self
6291 .store
6292 .revoke_consent(consent_id, self.revocation_window())
6293 .await?;
6294 if let Some(record) = &record {
6295 self.hooks.emit(|| Event::ConsentWithdrawn {
6296 client_id: record.client_id.as_str(),
6297 subject: record.subject.as_ref(),
6298 records_revoked,
6299 });
6300 }
6301 Ok(records_revoked)
6302 }
6303
6304 /// RFC 7009 token revocation.
6305 ///
6306 /// Returns `Ok(())` when the token is gone, INCLUDING when it never existed: section 2.2
6307 /// requires a 200 for an unknown token, because distinguishing "revoked" from "never heard of
6308 /// it" would let an unauthenticated caller test whether a token string is real.
6309 ///
6310 /// `token_type_hint` (section 2.1) is an optimisation, not a constraint: the RFC requires the
6311 /// server to keep looking if the hint is wrong, so a wrong hint costs a second lookup and
6312 /// nothing else.
6313 ///
6314 /// PUBLIC CLIENTS MAY REVOKE THEIR OWN TOKENS here, presenting a `client_id` and no secret,
6315 /// which is section 2.1's own rule ("in case of a confidential client" scopes the credential
6316 /// check) and section 5's ("a valid `client_id`, in the case of a public client"). What stops
6317 /// a caller who merely knows a public client's id is the OWNERSHIP check, made against the
6318 /// stored record: another client's token is untouched, and answered `Ok(())` all the same.
6319 /// This is deliberately NOT what
6320 /// [`introspection_response`](AuthorizationServer::introspection_response) does; see the
6321 /// comment inside [`revoke_with_credential`](AuthorizationServer::revoke_with_credential) for
6322 /// why the two RFCs differ.
6323 ///
6324 /// # THIS FUTURE IS NOT CANCELLATION SAFE, and what a drop costs
6325 ///
6326 /// Applies equally to
6327 /// [`revoke_with_credential`](AuthorizationServer::revoke_with_credential), which is the same
6328 /// future. A dropped future stops at whatever `await` it was suspended in and never resumes,
6329 /// and this crate cannot make it finish: there is no destructor that can run an `async` store
6330 /// call. So the contract is stated rather than left to be discovered.
6331 ///
6332 /// Revoking a REFRESH token is a two-write sequence: the RFC 7009 s2.1 cascade over the
6333 /// grant's family, and the removal of the presented string. A drop between them leaves the
6334 /// family revoked, with a barrier recorded, and one live-LOOKING refresh string that names a
6335 /// family nothing will honour. That is fail-closed on purpose, and it is why the cascade runs
6336 /// first; the opposite order was worse than an incomplete revocation, because the client's
6337 /// RETRY found the presented string already gone and answered 200 without cascading at all,
6338 /// leaving every access token of a grant the user had logged out of live for its whole TTL.
6339 /// A retry after a drop now still reaches the cascade, and the cascade is idempotent
6340 /// ([`crate::store::Storage::revoke_token_family`]). `tests/revocation_cancellation.rs` pins
6341 /// it.
6342 ///
6343 /// WHAT IS STILL LOST to a drop, stated rather than implied: the [`crate::events::Event`] the
6344 /// completed call would have emitted, so an audit trail can miss a revocation that partly
6345 /// happened. A host that needs the sequence to complete must drive it from a task the
6346 /// connection cannot cancel, spawning the call and awaiting the join handle, which is what this
6347 /// crate's own axum adapter does; see [`crate::http`].
6348 pub async fn revoke(
6349 &self,
6350 client_id: &ClientId,
6351 client_secret: Option<&str>,
6352 token: &str,
6353 token_type_hint: Option<TokenTypeHint>,
6354 ) -> Result<(), ErrorResponse> {
6355 self.revoke_with_credential(
6356 client_id,
6357 &ClientCredential::secret(client_secret),
6358 token,
6359 token_type_hint,
6360 )
6361 .await
6362 }
6363
6364 /// RFC 7009 revocation for a caller authenticating with any credential this server accepts,
6365 /// including an RFC 7523 assertion. See
6366 /// [`AuthorizationServer::device_authorization_with_credential`] on why this is an addition
6367 /// rather than a replacement.
6368 pub async fn revoke_with_credential(
6369 &self,
6370 client_id: &ClientId,
6371 cred: &ClientCredential<'_>,
6372 token: &str,
6373 token_type_hint: Option<TokenTypeHint>,
6374 ) -> Result<(), ErrorResponse> {
6375 let client = self.authenticate_client(client_id, cred).await?;
6376 // PUBLIC CLIENTS ARE ADMITTED HERE, and deliberately, which is the opposite of
6377 // `introspection_response_with_credential` a few hundred lines above. The two arrived
6378 // together under one citation pair and only the introspection half of it held.
6379 //
6380 // RFC 7009 section 2.1 scopes credential validation in as many words: the server "first
6381 // validates the client credentials (in case of a confidential client) and then verifies
6382 // whether the token was issued to the client making the revocation request". Section 5
6383 // says the same thing from the other side, naming "a valid client_id, in the case of a
6384 // public client". So the OWNERSHIP check below, not client authentication, is what this
6385 // endpoint's access control rests on for a public client, and the checks below already
6386 // perform it: a record whose `client_id` is not this one is untouched and answered 200.
6387 //
6388 // Why this matters rather than being a conformance detail: this server issues tokens to
6389 // public clients through code+PKCE and through the device grant, so refusing them
6390 // revocation left a native or browser app with no standard way to make a logout mean
6391 // anything. The token stayed live for its whole TTL and the refresh chain outlived the
6392 // user's decision entirely.
6393 //
6394 // What an attacker gains is bounded by what they must already have: the token STRING. A
6395 // caller who holds it can already use it, so being able to destroy it is a strictly
6396 // smaller capability than the one they have; and holding somebody else's token buys
6397 // nothing here, because the comparison is against the record, not against the asserted
6398 // identity. Introspection is refused for exactly the reason this is allowed: it is a
6399 // request to DESCRIBE a token rather than to destroy one, and RFC 7662 section 4 says it
6400 // MUST NOT be publicly available.
6401
6402 let try_refresh = || async {
6403 // READ, then take. Section 2.1's ownership check is a question ABOUT someone else's
6404 // credential, so it must not be answered by removing it: a take-then-put-back is a
6405 // non-atomic read-modify-write on a live token, it opens a window in which the real
6406 // owner's concurrent refresh sees nothing, and if the restoring write fails the
6407 // victim's chain is destroyed permanently while this endpoint still answers 200.
6408 // Reading first means a non-owner's request touches nothing at all.
6409 match self.store.get_refresh_token(token).await {
6410 Ok(Some(record)) if record.client_id == client.client_id => {
6411 // THE CASCADE RUNS FIRST, AND THE ORDER IS THE FIX. It used to take the
6412 // presented refresh token and revoke the family afterwards, which is the
6413 // fail-OPEN order for a two-write sequence that can stop between the writes:
6414 // this future is dropped whenever the host's connection is cancelled, and a
6415 // drop after the take left the presented string gone and the family whole.
6416 // The client's retry then found nothing at `get_refresh_token`, fell to the
6417 // `Ok(_)` arm below, and answered 200 with no cascade at all, so every access
6418 // token of a grant the user had just logged out of stayed live for its whole
6419 // TTL and nothing anywhere recorded it. Note that the store-ERROR path was
6420 // already handled honestly (`cascade_failed` below, emitted either way); the
6421 // DROP path emitted nothing, which is why it was invisible.
6422 //
6423 // Revoking first inverts that. A drop between the two writes leaves the family
6424 // revoked, with a barrier recorded, and one live-looking refresh string that
6425 // names a family nothing will honour: fail-closed, and the retry re-runs a
6426 // cascade that is idempotent by contract (see `Storage::revoke_token_family`,
6427 // "removing records that are already gone is success").
6428 //
6429 // The `family_id` comes from the record READ above rather than from a taken
6430 // one. A refresh token string cannot change families, so the two always agreed;
6431 // and after this call there is usually no record left to take, because
6432 // `revoke_token_family` removes the family's refresh records as well.
6433 //
6434 // The in-flight-rotation case the previous ordering worried about is handled
6435 // by the barrier, not by the ordering: a rotation that has already taken its
6436 // record is invisible to the cascade's scan, and it is the barrier that
6437 // REFUSES its later writes rather than letting them restore the chain the user
6438 // just revoked. Recording that barrier sooner can only help.
6439 //
6440 // RFC 7009 section 2.1: "If the particular token is a refresh token and the
6441 // authorization server supports the revocation of access tokens, then the
6442 // authorization server SHOULD also invalidate all access tokens based on the
6443 // same authorization grant." This server does support it, so the SHOULD
6444 // applies, and the grant is exactly what `family_id` names (see
6445 // `RefreshTokenRecord::family_id`): every token, access or refresh, minted from
6446 // the same authorization. Killing only the presented string would leave the
6447 // access token that came out of the same redemption live for its whole TTL,
6448 // which is the opposite of what a client asking for revocation has just said.
6449 //
6450 // Deliberately NOT fatal on a storage failure. Section 2.2 makes the presented
6451 // token's own revocation the answer, and the take below still runs and still
6452 // reports its own failure; a cascade that could turn a completed revocation
6453 // into a 503 would leave the client believing nothing was revoked when the
6454 // token it named is already gone.
6455 //
6456 // NOT fatal, but no longer INVISIBLE. The event fired unconditionally, so an
6457 // operator could not tell a complete revocation from one that killed the
6458 // presented string and left every access token of the same grant alive. That
6459 // is the difference between "the client's session is over" and "the client's
6460 // session continues for up to one access token TTL", and only the host can
6461 // decide what to do about it.
6462 let cascade_failed = self
6463 .store
6464 .revoke_token_family(record.family_id.as_str(), self.revocation_window())
6465 .await
6466 .is_err();
6467 // The presented string, in case the cascade did not reach it: it will already
6468 // be gone in a store whose `revoke_token_family` removed the family's refresh
6469 // records, and removing a record that is not there is not an error. This is
6470 // what makes the sequence safe to stop after the cascade rather than before
6471 // it, and what makes a retry a no-op.
6472 self.store
6473 .take_refresh_token(token)
6474 .await
6475 .map_err(storage_error)?;
6476 self.hooks.emit(|| Event::TokenRevoked {
6477 client_id: client.client_id.as_str(),
6478 token_type: TokenTypeHint::RefreshToken,
6479 cascade_failed,
6480 });
6481 Ok(true)
6482 }
6483 // Unknown, or somebody else's: nothing to do, and section 2.2 makes both a 200.
6484 Ok(_) => Ok(false),
6485 Err(e) => Err(storage_error(e)),
6486 }
6487 };
6488 let try_access = || async {
6489 match self.store.get_token(token).await {
6490 Ok(Some(t)) if t.client_id == client.client_id => {
6491 self.store
6492 .delete_token(token)
6493 .await
6494 .map_err(storage_error)?;
6495 self.hooks.emit(|| Event::TokenRevoked {
6496 client_id: client.client_id.as_str(),
6497 token_type: TokenTypeHint::AccessToken,
6498 // An access token names no grant to cascade to: it is one record and it is
6499 // gone, or the `?` above already turned the failure into an error.
6500 cascade_failed: false,
6501 });
6502 Ok(true)
6503 }
6504 Ok(_) => Ok(false),
6505 Err(e) => Err(storage_error(e)),
6506 }
6507 };
6508
6509 // The hint only decides which lookup happens first.
6510 match token_type_hint {
6511 Some(TokenTypeHint::AccessToken) => {
6512 if !try_access().await? {
6513 try_refresh().await?;
6514 }
6515 }
6516 _ => {
6517 if !try_refresh().await? {
6518 try_access().await?;
6519 }
6520 }
6521 }
6522 Ok(())
6523 }
6524}
6525
6526#[cfg(test)]
6527#[path = "tests/server.rs"]
6528mod tests;