oauth_as/http.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (C) 2026 Matthew Jackson
3
4//! An OPTIONAL HTTP service over [`AuthorizationServer`], behind the `http` cargo feature.
5//!
6//! # Why this is optional, and stays optional
7//!
8//! The premise of this crate is that the host owns the listener. A library that drags a web
9//! framework and an async runtime into every consumer has taken that decision away, so the
10//! default feature set is empty and nothing in the library below this module knows this module
11//! exists. Turning the feature on is the host saying "serve the RFC-shaped wire surface for me
12//! rather than making me write it"; leaving it off costs nothing, not even a compiled dependency.
13//!
14//! # Why there is no web framework in this module's PUBLIC API
15//!
16//! This module speaks `http` 1.x and `http-body` 1.x and nothing else: [`AuthorizationService`]
17//! is an `async fn` from an [`http::Request`] to an [`http::Response`]. Those two crates are 1.0
18//! and their major has never moved, so a host may mount this service under whatever server it
19//! already runs.
20//!
21//! That is a deliberate correction. Until 0.9 this module handed back an `axum::Router`, which
22//! put a 0.x major in the signature of the only way to use the feature: a host on any other axum
23//! major could not enable `http` AT ALL, and an axum major bump would have been a breaking change
24//! to THIS crate for reasons that have nothing to do with OAuth. axum is now a thin ADAPTER
25//! behind the separate `axum` feature (`impl From<AuthorizationService<..>> for axum::Router`),
26//! so the hazard is confined to hosts that opt into it.
27//!
28//! Nothing was hand-rolled to get there. This module already wrote its own percent-decoder, form
29//! parser, first-wins parameter logic, Basic-auth decoder and response builders, precisely so that
30//! the RFC-mandated headers land on the same response as the body they describe; it used a
31//! framework for exactly three things (route matching, one dynamic path segment, and body
32//! collection with a cap), and those three are what `Routes::resolve` and `collect_body` now do
33//! in this file.
34//!
35//! # What it serves
36//!
37//! Exactly the endpoints [`crate::metadata::AuthorizationServerMetadata`] advertises, at exactly the paths it
38//! advertises them, plus the RFC 8628 `verification_uri`. The paths are DERIVED from the metadata
39//! document rather than hard-coded, because an advertised endpoint that 404s is a lie a client
40//! cannot recover from: if a host overrides `token_endpoint`, the route moves with it or
41//! [`ServiceBuilder::build`] refuses to produce a service at all.
42//!
43//! Under the `jwt` feature that includes `jwks_uri`: the document advertises it exactly when the
44//! server signs its access tokens, and this router serves the RFC 7517 key set there. A resource
45//! server that cannot fetch the keys cannot verify a single RFC 9068 token, so an advertised
46//! `jwks_uri` this router cannot reach is the same lie as any other unroutable endpoint.
47//!
48//! # Cost
49//!
50//! The metadata document is serialized ONCE, when the router is built, and served from the
51//! resulting [`Bytes`] (a clone is a refcount bump, not a copy). The key set is serialized once
52//! for the same reason: its contents change only when the host rebuilds the router. The
53//! `WWW-Authenticate` challenge
54//! is likewise built once. There are no lazy statics, no background tasks, and no per-request
55//! rebuilding of anything derived from configuration. Request parsing borrows out of the request
56//! body and query string and only allocates for values that actually needed percent-decoding.
57//!
58//! # What the host still owns, and MUST wire
59//!
60//! Three things this module cannot invent, each with a seam, and each of which REFUSES when the
61//! seam is not wired:
62//!
63//! 1. Authenticating the RESOURCE OWNER ([`ServiceBuilder::with_subject_resolver`]). This module
64//! cannot know how a host logs a user in.
65//! 2. CONSENT at the authorization endpoint ([`ServiceBuilder::with_approval_resolver`]). RFC 6749
66//! s10.12 requires the AS to ensure the resource owner is aware of, and explicitly consents
67//! to, the authorization. A subject resolver answers "who is this"; it does not answer "did
68//! they agree", and treating the first as the second is an AS that silently authorizes any
69//! registered client on any cross-site navigation.
70//! 3. A CSRF token bound to the host's session, for the device verification form
71//! ([`ServiceBuilder::with_csrf_tokens`]). This crate has no session store, so it cannot mint
72//! one; RFC 6749 s10.12 still requires the protection, so an unwired host gets a refusal and
73//! is never served a submittable, forgeable form.
74//!
75//! Every non-interactive endpoint works regardless of all three.
76//!
77//! # What this service CANNOT do, and a host must not advertise through it
78//!
79//! RFC 8705 mutual-TLS client authentication. A build with the `mtls` feature advertises
80//! `tls_client_auth` and `self_signed_tls_client_auth` in the RFC 8414 document's
81//! `token_endpoint_auth_methods_supported`, and a client that reads them and authenticates that
82//! way THROUGH THIS SERVICE is refused with `invalid_client`, every time. That is not an oversight: this module is handed an already-parsed request, it never
83//! terminates TLS and never sees the connection, so there is no certificate here that anybody
84//! verified. Reading one out of a proxy header would trust that header on every deployment's
85//! behalf rather than on the one host that knows whether its terminator can be trusted; the
86//! `oauth_as::mtls` module's trust boundary section is the argument in full.
87//!
88//! So a host that terminates mTLS must call `AuthorizationServer::token_with_context` (or the
89//! other `*_with_credential` entry points) itself, passing the `ClientCredential::certificate` it
90//! verified. A host that mounts only this service should not register mTLS clients at all,
91//! because the metadata document will invite them and this endpoint will refuse them.
92
93use std::borrow::Cow;
94use std::sync::Arc;
95
96use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
97use base64::Engine as _;
98use bytes::Buf as _;
99use http::{header, HeaderMap, HeaderValue, Method, Request, StatusCode, Uri};
100use http_body::Body as _;
101use serde::Serialize;
102use sha2::{Digest as _, Sha256};
103
104use crate::authorization::{AuthorizationError, AuthorizationRequest};
105use crate::client::ClientId;
106use crate::device::{normalize_user_code, DeviceGrant, DeviceGrantState};
107use crate::error::{ErrorCode, ErrorResponse};
108use crate::events::{Attempt, AttemptOutcome, RateLimitDecision};
109use crate::grant::GrantType;
110use crate::metadata::well_known_path;
111use crate::scope::ScopeSet;
112use crate::server::{AuthorizationServer, Clock, DeviceApprovalError, TokenRequest, UserApproval};
113use crate::store::Storage;
114use crate::token::TokenTypeHint;
115
116/// Re-exported so a host building a [`ApprovalDecision::Respond`] body does not have to name
117/// `bytes` in its own manifest just to agree with this crate about which version it means.
118pub use bytes::Bytes;
119
120/// The response body this module produces: a single in-memory buffer, already complete.
121///
122/// Every response an authorization server emits is a short JSON document, a small HTML page, or
123/// nothing at all, and all of them are finished before the first byte is written. So the body type
124/// is a `Bytes` rather than a stream: there is nothing to stream, and a boxed
125/// `dyn http_body::Body` would add an allocation and a virtual call per response to express a
126/// capability this module never uses.
127///
128/// It implements [`http_body::Body`] with an INFALLIBLE error type and an EXACT size hint. The
129/// second matters on the wire: a server that knows the length emits `Content-Length` rather than
130/// falling back to chunked transfer encoding.
131#[derive(Debug, Default, Clone)]
132pub struct Body(Option<Bytes>);
133
134impl Body {
135 /// A body with no bytes at all. RFC 7009 s2.2's revocation success and RFC 7592 s2.3's
136 /// deletion both answer with one.
137 pub fn empty() -> Self {
138 Body(None)
139 }
140
141 /// The bytes, consuming the body.
142 pub fn into_bytes(self) -> Bytes {
143 self.0.unwrap_or_default()
144 }
145}
146
147impl From<Bytes> for Body {
148 fn from(bytes: Bytes) -> Self {
149 // An empty `Bytes` and "no frame at all" are the same response on the wire, and
150 // collapsing them here means `is_end_stream` is true from the start for an empty body,
151 // so a server need not poll for a frame it will never get.
152 match bytes.is_empty() {
153 true => Body(None),
154 false => Body(Some(bytes)),
155 }
156 }
157}
158
159// Spelled out one type at a time rather than as `impl<T: Into<Bytes>>`, which cannot be written:
160// it would overlap with the standard library's reflexive `From<T> for T` and coherence has no way
161// to rule that out.
162impl From<Vec<u8>> for Body {
163 fn from(value: Vec<u8>) -> Self {
164 Body::from(Bytes::from(value))
165 }
166}
167
168impl From<String> for Body {
169 fn from(value: String) -> Self {
170 Body::from(Bytes::from(value))
171 }
172}
173
174impl From<&'static str> for Body {
175 fn from(value: &'static str) -> Self {
176 Body::from(Bytes::from_static(value.as_bytes()))
177 }
178}
179
180impl http_body::Body for Body {
181 type Data = Bytes;
182 // This body is already in memory, so there is no read that could fail. Naming that in the
183 // type means a host mounting the service never has to write an error arm that cannot happen.
184 type Error = std::convert::Infallible;
185
186 fn poll_frame(
187 mut self: std::pin::Pin<&mut Self>,
188 _cx: &mut std::task::Context<'_>,
189 ) -> std::task::Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
190 std::task::Poll::Ready(self.0.take().map(|b| Ok(http_body::Frame::data(b))))
191 }
192
193 fn is_end_stream(&self) -> bool {
194 self.0.is_none()
195 }
196
197 fn size_hint(&self) -> http_body::SizeHint {
198 http_body::SizeHint::with_exact(self.0.as_ref().map_or(0, |b| b.len() as u64))
199 }
200}
201
202/// The response type this module produces, and the one
203/// [`ApprovalDecision::Respond`] carries.
204///
205/// A plain [`http::Response`], deliberately: the host that renders a consent screen builds it with
206/// whatever it already has, and `http` 1.x is the one HTTP vocabulary every Rust web framework
207/// agrees on.
208pub type Response = http::Response<Body>;
209
210/// Build a response with a status and a complete body, and no headers yet.
211///
212/// Every caller sets its own `Content-Type` (and, on the token plane, the RFC 6749 s5.1 caching
213/// directives), so nothing is guessed here. That is the same reason this module never used a JSON
214/// extractor: the RFC-mandated headers and the body they describe are set in one place.
215fn respond(status: StatusCode, body: impl Into<Body>) -> Response {
216 let mut resp = Response::new(body.into());
217 *resp.status_mut() = status;
218 resp
219}
220
221/// How the host names the authenticated resource owner for the interactive endpoints.
222///
223/// The `HeaderMap` is the request's, so a host can read its own session cookie or a
224/// reverse-proxy assertion header. `None` means "nobody is logged in", which is a refusal, not
225/// an error: see [`ServiceBuilder::with_subject_resolver`].
226pub type SubjectResolver = Arc<dyn Fn(&HeaderMap) -> Option<String> + Send + Sync>;
227
228/// A CSRF token hook: issue one for, or take one back from, the session this request carries.
229///
230/// `None` means "this request has no session", which is a refusal. See
231/// [`ServiceBuilder::with_csrf_tokens`] for the contract the two hooks satisfy together.
232pub type CsrfTokenHook = Arc<dyn Fn(&HeaderMap) -> Option<String> + Send + Sync>;
233
234/// What the host's approval step decided about one authorization request.
235///
236/// APPROVAL, not CONSENT, and the distinction is what the rename in 0.9.1 bought. This type is a
237/// UI PROMPT: a question asked about one request, answered once, and never stored. The `consent`
238/// module's [`crate::consent::ConsentRecord`] is a PERSISTED GRANT: a durable statement that
239/// survives the request and can be withdrawn. Both were called "consent" at the crate root, which
240/// is two meanings of one word in the one place a reader looks first. The direct API already
241/// called the prompt's answer [`crate::server::UserApproval`], so this is the crate agreeing with
242/// itself rather than inventing a third vocabulary.
243///
244/// Naming the third variant `Respond` is the point of the type: a real host shows a consent
245/// SCREEN, which means the first request renders HTML and a later one carries the answer. The
246/// resolver returns that page here and the router serves it unchanged, so interposing a consent
247/// UI never requires abandoning this router.
248/// `#[non_exhaustive]`: this type's shape already varies with the cargo features a host
249/// enables, so an exhaustive match on it was never portable between builds of this crate.
250#[non_exhaustive]
251pub enum ApprovalDecision {
252 /// The resource owner has agreed to this exact request. RFC 6749 s4.1.2: mint the code.
253 Approve,
254 /// The resource owner refused. RFC 6749 s4.1.2.1: `access_denied` at the redirect URI, which
255 /// is an answer the client is entitled to receive.
256 Deny,
257 /// The resource owner agreed AND asked not to be asked again: mint the code, and record (or
258 /// widen) the consent so a later request can be recognised as already granted.
259 ///
260 /// A separate variant rather than something the library infers, because remembering is a
261 /// statement about a user's intent and this crate never sees a user. It will not remember a
262 /// consent nobody asked it to remember, and it will not approve one it does remember.
263 #[cfg(feature = "consent")]
264 ApproveAndRemember,
265 /// Serve this response instead, unchanged: a consent screen, a step-up challenge, a redirect
266 /// back into the host's own flow. Nothing is issued.
267 ///
268 /// READ WHEN THIS IS REACHED, because one case a reader expects is not among them. The
269 /// authorization endpoint refuses BEFORE this resolver runs when
270 /// [`ServiceBuilder::with_subject_resolver`] answers `None`, so a resolver cannot answer a
271 /// signed-out visitor with a login redirect through this router: there is no subject to build
272 /// an [`ApprovalRequest`] around, and inventing one would be this crate deciding who the user
273 /// is. A host that wants to send an anonymous visitor to its login page does it in front of
274 /// this service, where its session already lives; what this variant serves is every decision
275 /// taken about a user the host has already named, step-up included.
276 Respond(Box<Response>),
277}
278
279/// What the host's approval resolver is told about the request it is being asked to approve.
280///
281/// Everything borrows: the resolver is called inside the request path and nothing here outlives
282/// it. The request has already passed RFC 6749 s4.1.1 validation, so `client_id`, `redirect_uri`
283/// and `scope` are the VALIDATED values (the redirect URI is a registered one, the scope is
284/// inside the client's registration), not raw query text.
285/// `#[non_exhaustive]`: this type's shape already varies with the cargo features a host
286/// enables, so an exhaustive match on it was never portable between builds of this crate.
287#[non_exhaustive]
288pub struct ApprovalRequest<'a> {
289 /// The request's headers, so the host can find its own session.
290 pub headers: &'a HeaderMap,
291 /// The authenticated resource owner, as named by the subject resolver.
292 pub subject: &'a str,
293 /// The client asking.
294 pub client_id: &'a ClientId,
295 /// The scope that will be granted if this is approved.
296 pub scope: &'a ScopeSet,
297 /// The registered redirect URI this request resolved to.
298 pub redirect_uri: &'a str,
299 /// The client's `state`, if it sent one.
300 pub state: Option<&'a str>,
301 /// The RFC 8707 resource indicators this request asked for, already validated against the
302 /// server's `allowed_resources`. Empty when the client named none.
303 ///
304 /// It is here because the audience a token will carry is part of what the user is being asked
305 /// to approve: "read your calendar" means something different at one resource server than at
306 /// another, and the host cannot recover this from the query. For a PAR request the query holds
307 /// only `client_id` and the request URI, and the pushed record has already been consumed by
308 /// the time this resolver runs; for a JAR request the values are inside the signed object.
309 pub resource: &'a [String],
310 /// The RFC 9396 `authorization_details` this request asked for, already parsed and already
311 /// checked against the server's supported types (section 5).
312 ///
313 /// THE TYPE CHECK IS NOT AN APPROVAL. `AuthorizationDetails::require_supported_types` inspects
314 /// the `type` string alone, so the amount, the `identifier`, the creditor account and every
315 /// other type-specific member of an element reach the issued token unexamined unless this
316 /// resolver looks at them. RFC 9396 section 2 makes the elements the thing being authorized,
317 /// and this crate never renders a screen, so the decision belongs here: a host that shows only
318 /// [`ApprovalRequest::scope`] is asking the user to approve a payment they were never shown.
319 /// Like [`ApprovalRequest::resource`], it cannot be recovered from the query on the PAR or JAR
320 /// paths.
321 #[cfg(feature = "rar")]
322 pub authorization_details: &'a crate::rar::AuthorizationDetails,
323 /// The full request URI, so a host that renders a consent screen can round-trip the user
324 /// back to exactly this request after they answer.
325 pub uri: &'a Uri,
326 /// What this user has already granted this client, if anything.
327 ///
328 /// This is the library REPORTING and the host DECIDING, and that split is the whole design.
329 /// [`crate::consent::ConsentRecord::covers`] answers whether the remembered grant already
330 /// covers what is being asked for now; whether that is a good enough reason to skip the prompt
331 /// depends on how long ago it was, what the scope means in this deployment, and whether the
332 /// user is on a device the host trusts, none of which this crate knows. So it is handed over,
333 /// and nothing here ever approves on the strength of it.
334 ///
335 /// `covers` takes all three of what is being asked for, and the third is
336 /// [`ApprovalRequest::authorization_details`], wrapped by
337 /// [`crate::consent::RequestedDetails::of`]. It answers `false` for any request that carries
338 /// one, so a resolver that skips its prompt on a `true` still asks about every RFC 9396
339 /// element: a remembered consent records a scope and a resource list, and an element it never
340 /// recorded is not something it can be said to cover. That method's docs give the argument in
341 /// full, including why the answer would barely change if it did record them.
342 #[cfg(feature = "consent")]
343 pub remembered: Option<&'a crate::consent::ConsentRecord>,
344}
345
346/// How the host makes the RFC 6749 s10.12 approval decision. See
347/// [`ServiceBuilder::with_approval_resolver`].
348pub type ApprovalResolver = Arc<dyn Fn(&ApprovalRequest<'_>) -> ApprovalDecision + Send + Sync>;
349
350/// How the host answers "when, and how, did you authenticate this user".
351///
352/// The third identity seam, and the one RFC 9470 needs: a subject resolver answers WHO, an approval
353/// resolver answers WHETHER THEY AGREED, and this answers HOW STRONGLY AND HOW RECENTLY. `None`
354/// means the host is not reporting one, which satisfies no `acr_values` and no `max_age`; see
355/// [`ServiceBuilder::with_authentication_reporter`].
356#[cfg(feature = "consent")]
357pub type AuthenticationReporter =
358 Arc<dyn Fn(&HeaderMap) -> Option<crate::consent::Authentication> + Send + Sync>;
359
360/// How the device verification form is protected against RFC 6749 s10.12 cross-site forced
361/// approval.
362enum VerificationProtection {
363 /// No seam wired. Every interactive path refuses; see [`ServiceBuilder::with_csrf_tokens`].
364 Unwired,
365 /// The host mints and takes back a session-bound token.
366 Tokens {
367 /// Mint a token for the form this GET is about to render.
368 issue: CsrfTokenHook,
369 /// Take back (and invalidate) the token this session was last issued.
370 consume: CsrfTokenHook,
371 },
372 /// Explicitly disabled by the host. See
373 /// [`ServiceBuilder::dangerously_disable_verification_protections`].
374 Disabled,
375}
376
377/// Why a router could not be built. Every variant is a host configuration mistake that would
378/// otherwise become a runtime 404 on an endpoint the metadata document promises.
379#[derive(Debug, Clone, PartialEq, Eq)]
380/// `#[non_exhaustive]`: this type's shape already varies with the cargo features a host
381/// enables, so an exhaustive match on it was never portable between builds of this crate.
382#[non_exhaustive]
383pub enum ServiceError {
384 /// An advertised endpoint is not under the issuer, so this router cannot serve it. The host
385 /// is either fronting a separate service or has a typo; either way, silently not routing it
386 /// would publish a promise nothing keeps.
387 EndpointOutsideIssuer {
388 /// The RFC 8414 member name.
389 endpoint: &'static str,
390 /// The URL as advertised.
391 url: String,
392 },
393 /// Two endpoints resolved to the same path, so one would shadow the other.
394 DuplicatePath {
395 /// The path claimed twice.
396 path: String,
397 },
398 /// The metadata document could not be serialized. Structurally impossible for the derived
399 /// document, but reported rather than panicked: a library does not abort a host's process.
400 MetadataNotSerializable {
401 /// The serializer's message.
402 detail: String,
403 },
404 /// The RFC 7517 key set could not be serialized. As structurally impossible as the metadata
405 /// case, and reported for the same reason.
406 #[cfg(feature = "jwt")]
407 JwksNotSerializable {
408 /// The serializer's message.
409 detail: String,
410 },
411 /// The document advertises a `jwks_uri` under the issuer, in a build with no `jwt` feature.
412 /// Nothing here can serve a key set, so the member is a promise of a path that can only 404.
413 /// A key set some other component holds is still fine: point the member outside the issuer,
414 /// which is what "some other component holds the keys" looks like in a URL.
415 #[cfg(not(feature = "jwt"))]
416 JwksNotServable {
417 /// The URL as advertised.
418 url: String,
419 },
420}
421
422impl std::fmt::Display for ServiceError {
423 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424 match self {
425 ServiceError::EndpointOutsideIssuer { endpoint, url } => write!(
426 f,
427 "advertised {endpoint} ({url}) is not under the issuer, so this router cannot \
428 serve it"
429 ),
430 ServiceError::DuplicatePath { path } => {
431 write!(f, "two endpoints resolve to the same path {path}")
432 }
433 ServiceError::MetadataNotSerializable { detail } => {
434 write!(f, "RFC 8414 metadata could not be serialized: {detail}")
435 }
436 #[cfg(feature = "jwt")]
437 ServiceError::JwksNotSerializable { detail } => {
438 write!(f, "RFC 7517 key set could not be serialized: {detail}")
439 }
440 #[cfg(not(feature = "jwt"))]
441 ServiceError::JwksNotServable { url } => write!(
442 f,
443 "advertised jwks_uri ({url}) is under the issuer, but this build has no jwt \
444 feature and so has no key set to serve there"
445 ),
446 }
447 }
448}
449
450impl std::error::Error for ServiceError {}
451
452/// Everything a handler needs, built once and shared by refcount.
453struct Inner<S: Storage, C: Clock> {
454 server: Arc<AuthorizationServer<S, C>>,
455 /// The RFC 8414 document, serialized at build time. Serving it is a refcount bump.
456 metadata: Bytes,
457 /// The RFC 7517 key set, serialized at build time, present exactly when the document
458 /// advertises a `jwks_uri` this router serves. PUBLIC parameters only: the bytes come from
459 /// [`AuthorizationServer::jwks`], which has no way to emit a private key parameter.
460 #[cfg(feature = "jwt")]
461 jwks: Option<Bytes>,
462 /// The RFC 6749 s5.2 / RFC 7617 challenge, built at build time because the realm never
463 /// changes and formatting it per request would be pure waste.
464 challenge: HeaderValue,
465 /// The issuer's `scheme://authority`, for the RFC 6749 s10.12 `Origin` check. Derived once,
466 /// because comparing against a freshly parsed issuer on every POST is pure waste.
467 origin: String,
468 subject: Option<SubjectResolver>,
469 approval: Option<ApprovalResolver>,
470 #[cfg(feature = "consent")]
471 authentication: Option<AuthenticationReporter>,
472 verification: VerificationProtection,
473 /// The paths, derived from the metadata document when the service was built.
474 routes: Routes,
475}
476
477impl<S: Storage, C: Clock> Inner<S, C> {
478 /// The authenticated resource owner, if the host can name one.
479 fn subject(&self, headers: &HeaderMap) -> Option<String> {
480 self.subject.as_ref().and_then(|f| f(headers))
481 }
482}
483
484/// Builds the router. Construct, attach the seams the interactive endpoints need, then
485/// [`build`](ServiceBuilder::build).
486///
487/// # The interactive endpoints refuse until they are wired
488///
489/// [`with_subject_resolver`](ServiceBuilder::with_subject_resolver) alone is NOT enough to run an
490/// authorization server safely, and this is the one thing to read in this file. It answers "who
491/// is this user"; RFC 6749 s10.12 also demands "did the user knowingly agree". Wire
492/// [`with_approval_resolver`](ServiceBuilder::with_approval_resolver) and
493/// [`with_csrf_tokens`](ServiceBuilder::with_csrf_tokens) too, or the authorization endpoint and
494/// the device verification form refuse rather than guessing that silence means yes.
495pub struct ServiceBuilder<S: Storage, C: Clock> {
496 server: Arc<AuthorizationServer<S, C>>,
497 subject: Option<SubjectResolver>,
498 approval: Option<ApprovalResolver>,
499 #[cfg(feature = "consent")]
500 authentication: Option<AuthenticationReporter>,
501 verification: VerificationProtection,
502}
503
504impl<S: Storage + 'static, C: Clock + 'static> ServiceBuilder<S, C> {
505 /// Start from a running server. `Arc` rather than ownership so the host keeps its handle for
506 /// administration (client registration, sweeping) while the router serves the same instance.
507 pub fn new(server: Arc<AuthorizationServer<S, C>>) -> Self {
508 ServiceBuilder {
509 server,
510 subject: None,
511 approval: None,
512 #[cfg(feature = "consent")]
513 authentication: None,
514 verification: VerificationProtection::Unwired,
515 }
516 }
517
518 /// Supply the host's answer to "who is the logged-in user for this request".
519 ///
520 /// The authorization endpoint cannot mint a code without a resource owner, and the device
521 /// verification page cannot approve a grant without one. This crate has no login UI and no
522 /// session model by design, so without a resolver both endpoints refuse with 403 rather than
523 /// inventing a user.
524 ///
525 /// This resolver is IDENTITY ONLY. It does not express approval; see
526 /// [`with_approval_resolver`](ServiceBuilder::with_approval_resolver).
527 pub fn with_subject_resolver<F>(mut self, resolver: F) -> Self
528 where
529 F: Fn(&HeaderMap) -> Option<String> + Send + Sync + 'static,
530 {
531 self.subject = Some(Arc::new(resolver));
532 self
533 }
534
535 /// Supply the host's answer to "has this user knowingly agreed to this exact request".
536 ///
537 /// RFC 6749 s10.12 requires the AS to "ensure that the malicious client cannot obtain
538 /// authorization without the awareness and explicit consent of the resource owner". An
539 /// authorization endpoint that mints a code as soon as it knows who the user is satisfies
540 /// neither half: any cross-site top-level navigation makes a logged-in user's browser hand a
541 /// registered client a code they never asked to issue. PKCE and exact redirect-URI matching
542 /// bound WHO may redeem that code; they say nothing about whether it should have existed.
543 ///
544 /// With NO resolver the authorization endpoint refuses with 403 and issues nothing. That is
545 /// deliberate and it is a behaviour change: a host that previously wired only
546 /// [`with_subject_resolver`](ServiceBuilder::with_subject_resolver) was running an
547 /// AUTO-APPROVING authorization server, and the fix is to say what the approval step is rather
548 /// than to leave it implied.
549 ///
550 /// Return [`ApprovalDecision::Respond`] to render a consent screen and finish the flow on a
551 /// later request; return [`ApprovalDecision::Approve`] only once the user has actually agreed.
552 pub fn with_approval_resolver<F>(mut self, resolver: F) -> Self
553 where
554 F: Fn(&ApprovalRequest<'_>) -> ApprovalDecision + Send + Sync + 'static,
555 {
556 self.approval = Some(Arc::new(resolver));
557 self
558 }
559
560 /// Supply the host's answer to "when, and how, did you authenticate this user".
561 ///
562 /// REQUIRED for RFC 9470 step-up authentication and useless without it. A client answering a
563 /// resource server's `insufficient_user_authentication` challenge repeats its authorization
564 /// request with `acr_values` and/or `max_age`; this server enforces those against whatever the
565 /// reporter returns, and a host with no reporter wired fails every such request. That is the
566 /// correct answer rather than a bug: an authorization server that cannot say when the user
567 /// logged in cannot honestly claim they logged in recently.
568 ///
569 /// Ordinary requests, which carry neither parameter, are unaffected whether this is wired or
570 /// not.
571 ///
572 /// The report is taken at FACE VALUE. This crate cannot authenticate anyone and has nothing to
573 /// check it against; see the [`crate::consent`] module docs.
574 #[cfg(feature = "consent")]
575 pub fn with_authentication_reporter<F>(mut self, reporter: F) -> Self
576 where
577 F: Fn(&HeaderMap) -> Option<crate::consent::Authentication> + Send + Sync + 'static,
578 {
579 self.authentication = Some(Arc::new(reporter));
580 self
581 }
582
583 /// Supply the host's session-bound CSRF token for the device verification form.
584 ///
585 /// `issue` is called when the form is RENDERED: it mints a token, binds it to whatever
586 /// session the request carries, and returns it to be embedded in the form. `consume` is
587 /// called when the form is SUBMITTED: it returns the token that session was last issued AND
588 /// invalidates it, which is what makes the token single use. The router compares the
589 /// submitted token with the consumed one in constant time; a mismatch, or either hook
590 /// answering `None`, is a refusal.
591 ///
592 /// Why this is a seam and not something the library does: approving a device grant binds a
593 /// third party's grant to the logged-in user, so RFC 6749 s10.12's CSRF requirement applies
594 /// with full force, and the countermeasure has to be bound to the SESSION. This crate has no
595 /// session store and will not grow one. With no hooks the verification endpoint renders no
596 /// form and approves nothing, because a form that works and is forgeable is worse than no
597 /// form at all.
598 pub fn with_csrf_tokens<I, V>(mut self, issue: I, consume: V) -> Self
599 where
600 I: Fn(&HeaderMap) -> Option<String> + Send + Sync + 'static,
601 V: Fn(&HeaderMap) -> Option<String> + Send + Sync + 'static,
602 {
603 self.verification = VerificationProtection::Tokens {
604 issue: Arc::new(issue),
605 consume: Arc::new(consume),
606 };
607 self
608 }
609
610 /// Turn OFF the device verification form's CSRF token requirement, its `Origin` check, and
611 /// its affirmative-action requirement.
612 ///
613 /// FOR NON-BROWSER TEST HARNESSES ONLY. On an endpoint a browser can reach this re-enables
614 /// the complete RFC 6749 s10.12 cross-site forced-approval chain: an attacker starts a device
615 /// grant for a client they control, gets any authenticated victim's browser to POST the
616 /// `user_code`, and polls out an access token and a refresh token for the victim's account.
617 /// That is account takeover, and it needs no interaction beyond loading a page.
618 ///
619 /// It exists because this crate's black-box conformance harness drives the verification
620 /// endpoint with an HTTP client and no browser session, so it cannot hold a CSRF token. It is
621 /// spelled this loudly so that it is greppable, and so that no production host reaches for it
622 /// without having read what it does.
623 pub fn dangerously_disable_verification_protections(mut self) -> Self {
624 self.verification = VerificationProtection::Disabled;
625 self
626 }
627
628 /// Derive the routes from the metadata document and build the service.
629 ///
630 /// # Errors
631 ///
632 /// [`ServiceError`] when the configuration advertises something this service cannot serve.
633 pub fn build(self) -> Result<AuthorizationService<S, C>, ServiceError> {
634 let config = self.server.config();
635 // The SERVER's document, not the configuration's: what this service can honour depends on
636 // the seams the host installed on the server as well as on the configuration, and RFC 7523
637 // `private_key_jwt` is the case that separates the two. See
638 // [`crate::AuthorizationServer::metadata`].
639 // `mut` for the RFC 8705 strip below, which is the one place the document this service
640 // SERVES has to say less than the document the server describes itself with.
641 #[allow(unused_mut)]
642 let mut meta = self.server.metadata();
643 // `from_config` trims the issuer, and derives every default endpoint from that trimmed
644 // form, so the prefix relation below holds for an unconfigured host by construction.
645 let issuer = meta.issuer.clone();
646
647 // RFC 8705 sections 2.1.1, 2.2.1 and 3.3, REMOVED from the served document.
648 //
649 // Every one of them is true of `AuthorizationServer` reached through a host's own handler
650 // with a certificate its TLS terminator verified, and none of them is true of THIS router,
651 // which is handed an already-parsed request and passes `certificate: None` on every
652 // credential it builds (see `Credentials::credential`). A client that reads
653 // `tls_client_auth` here and presents a certificate is answered `invalid_client` forever;
654 // one that reads the section 3.3 flag believes its token is certificate bound when it is a
655 // bearer token. The field doc used to offer "a host that is not doing that should not
656 // compile the `mtls` feature in", which cargo feature unification takes out of the host's
657 // hands: one other crate in the graph enabling `mtls` makes this router lie.
658 //
659 // Only the SERVED copy is touched. `AuthorizationServer::metadata()` is unchanged, so a
660 // host serving its own routes still publishes the full document, which for that host is
661 // the honest one.
662 #[cfg(feature = "mtls")]
663 {
664 meta.token_endpoint_auth_methods_supported.retain(|m| {
665 m != crate::mtls::TLS_CLIENT_AUTH && m != crate::mtls::SELF_SIGNED_TLS_CLIENT_AUTH
666 });
667 meta.tls_client_certificate_bound_access_tokens = false;
668 }
669
670 let default_introspection_endpoint = format!("{issuer}/introspect");
671
672 let authorize = endpoint_path(
673 &issuer,
674 "authorization_endpoint",
675 &meta.authorization_endpoint,
676 )?;
677 let token = endpoint_path(&issuer, "token_endpoint", &meta.token_endpoint)?;
678 let device = endpoint_path(
679 &issuer,
680 "device_authorization_endpoint",
681 &meta.device_authorization_endpoint,
682 )?;
683 // RFC 7662. The one route derived from the CONFIGURATION rather than from the document,
684 // and deliberately: `from_config` publishes `introspection_endpoint` only where the host
685 // named it, because whether this server answers a RESOURCE SERVER depends on whether the
686 // deployment registered any (`ServerConfig::resource_servers`), and only the host knows
687 // that. See the field's doc in `crate::metadata` for why 0.9.2 building the channel did
688 // NOT make the member unconditional. Withdrawing the ROUTE with the promise would take
689 // away the half that always works -- a client asking about its own token -- which is a
690 // functional regression rather than an honesty fix. Serving a path the document does not
691 // name misleads nobody; the rule this module opens with is about the other direction.
692 let introspect = Some(endpoint_path(
693 &issuer,
694 "introspection_endpoint",
695 match &config.introspection_endpoint {
696 Some(u) => u,
697 None => &default_introspection_endpoint,
698 },
699 )?);
700 let revoke = match &meta.revocation_endpoint {
701 Some(u) => Some(endpoint_path(&issuer, "revocation_endpoint", u)?),
702 None => None,
703 };
704 // RFC 7591 s3 / RFC 8414 s2. `from_config` advertises this exactly when the host enabled
705 // dynamic registration, so an off-issuer value is an error for the same reason
706 // introspection's is: these bytes are produced by this server and nothing else can produce
707 // them. A host that never enabled registration routes nothing here at all, which is the
708 // only way an endpoint that mints clients should ever come to exist.
709 let register = match &meta.registration_endpoint {
710 Some(u) => Some(endpoint_path(&issuer, "registration_endpoint", u)?),
711 None => None,
712 };
713 // RFC 7592 s3 `registration_client_uri`: `{registration_endpoint}/{client_id}`, which is
714 // exactly what `registration::register_dynamic_client` hands the client, so the URL it is
715 // told to use is the URL this service answers on.
716 //
717 // Stored as the PREFIX (with the trailing slash) because that is what the matcher needs;
718 // the pattern form below exists only so the collision check and its error message name
719 // something a host can recognise in its own configuration.
720 let manage_prefix = register
721 .as_ref()
722 .filter(|_| {
723 config
724 .registration
725 .as_ref()
726 .is_some_and(|r| r.management_enabled)
727 })
728 .map(|p| format!("{p}/"));
729 let manage = manage_prefix.as_ref().map(|p| format!("{p}{{client_id}}"));
730 // The verification URI is NOT part of the RFC 8414 document (it is announced in each RFC
731 // 8628 s3.2 response), and a host may legitimately host its device page on a different
732 // origin entirely. So an off-issuer verification URI is not an error, it just means the
733 // host serves that page itself.
734 let verification =
735 endpoint_path(&issuer, "verification_uri", &config.verification_uri).ok();
736
737 // RFC 9126 s5 `pushed_authorization_request_endpoint`. `from_config` advertises it exactly
738 // when the host set `ServerConfig::par`, and section 5 says its presence is sufficient for
739 // a client to decide it may use PAR. So an advertised endpoint that is not routed is not a
740 // convenience gap, it is the one lie a client had no way to check first: it would push its
741 // whole request, including the PKCE challenge, at a 404 and have nowhere to fall back to.
742 // Off-issuer is an error for the same reason introspection's is: these bytes are minted by
743 // this server and nothing else can mint them.
744 #[cfg(feature = "par")]
745 let par = match &meta.pushed_authorization_request_endpoint {
746 Some(u) => Some(endpoint_path(
747 &issuer,
748 "pushed_authorization_request_endpoint",
749 u,
750 )?),
751 None => None,
752 };
753
754 // RFC 8414 s2 `jwks_uri`. `from_config` advertises it exactly when this server signs its
755 // access tokens, so when it is present the key set is ours to serve and an off-issuer URL
756 // is an error, exactly as it is for introspection and revocation. That is stricter than
757 // `verification_uri` above on purpose: the device page is a host's own branded HTML, while
758 // these bytes are produced by this server and nothing else can produce them.
759 #[cfg(feature = "jwt")]
760 let jwks_path = match &meta.jwks_uri {
761 Some(url) => Some(endpoint_path(&issuer, "jwks_uri", url)?),
762 None => None,
763 };
764 // RFC 8414 s2 `jwks_uri` in a build WITHOUT the `jwt` feature, which is a build that signs
765 // nothing and has no key set to serve. `http` does not imply `jwt` and
766 // `ServerConfig::jwks_uri` is a plain public field, so the document could advertise the
767 // member while every branch that routes it above is compiled out: `build` returned `Ok`,
768 // the document promised the endpoint, and `resolve` answered `None`. An advertised endpoint
769 // that 404s is the exact defect this module's docs open by naming, and RFC 9068 s4 makes it
770 // expensive: a resource server that cannot fetch the keys cannot verify anything.
771 //
772 // Refused rather than silently dropped, and only when the URL is UNDER the issuer. Off
773 // issuer is the documented case for this build (`metadata::advertised_jwks_uri`: some other
774 // component holds the keys), this service never claimed that path, and nothing it serves
775 // 404s. Under the issuer there is no reading on which the promise is kept.
776 #[cfg(not(feature = "jwt"))]
777 if let Some(url) = &meta.jwks_uri {
778 if endpoint_path(&issuer, "jwks_uri", url).is_ok() {
779 return Err(ServiceError::JwksNotServable {
780 url: url.to_string(),
781 });
782 }
783 }
784
785 // Serialized ONCE here rather than per request: a key set changes only when the host
786 // rebuilds the router, and a verifier may fetch this on every cold cache.
787 #[cfg(feature = "jwt")]
788 let jwks = match (&jwks_path, self.server.jwks()) {
789 (Some(_), Some(keys)) => Some(Bytes::from(serde_json::to_vec(&keys).map_err(|e| {
790 ServiceError::JwksNotSerializable {
791 detail: e.to_string(),
792 }
793 })?)),
794 // Both sides read the same `access_token_format`, so a path without keys cannot
795 // arise; if it somehow did, not routing is better than routing an empty key set that
796 // a verifier would read as "this issuer has no keys".
797 _ => None,
798 };
799
800 // RFC 8414 s3.1: the well-known string is inserted BETWEEN the host and the issuer's
801 // path, so this route is NOT `{issuer path}/.well-known/...` and is not the bare
802 // well-known path either once the issuer has a path. See `metadata::well_known_path`.
803 // Normalised like every route `endpoint_path` produces, and for the same reason: this one
804 // also carries the issuer's path, so a tenant whose name a client must escape is escaped
805 // here too and the document is served at the path the client actually asks for.
806 let well_known = encode_route_path(&well_known_path(&issuer));
807
808 let metadata = serde_json::to_vec(&meta)
809 .map_err(|e| ServiceError::MetadataNotSerializable {
810 detail: e.to_string(),
811 })?
812 .into();
813
814 let mut paths: Vec<&str> = vec![&well_known, &authorize, &token, &device];
815 paths.extend(introspect.as_deref());
816 paths.extend(revoke.as_deref());
817 paths.extend(verification.as_deref());
818 paths.extend(register.as_deref());
819 paths.extend(manage.as_deref());
820 #[cfg(feature = "par")]
821 paths.extend(par.as_deref());
822 #[cfg(feature = "jwt")]
823 paths.extend(jwks_path.as_deref());
824 for i in 0..paths.len() {
825 if paths[i + 1..].contains(&paths[i]) {
826 return Err(ServiceError::DuplicatePath {
827 path: paths[i].to_string(),
828 });
829 }
830 }
831
832 let routes = Routes {
833 well_known,
834 authorize,
835 token,
836 device,
837 introspect,
838 revoke,
839 verification,
840 register,
841 manage: manage_prefix,
842 #[cfg(feature = "par")]
843 par,
844 #[cfg(feature = "jwt")]
845 jwks: jwks_path,
846 };
847
848 Ok(AuthorizationService {
849 inner: Arc::new(Inner {
850 server: self.server,
851 metadata,
852 #[cfg(feature = "jwt")]
853 jwks,
854 // RFC 7617 s2: the realm is a quoted-string, so `"` and `\` must be escaped.
855 //
856 // The issuer OUGHT to be a URL and so ought to contain neither, and until the
857 // 0.9.1 audit this code said so and stopped there. But `ServerConfig::issuer` is a
858 // bare `String` that this crate deliberately does not validate — it does not even
859 // require `https` — so "the issuer is a URL" is an assumption about the host's
860 // configuration, not a property of the type. A stray quote picked up from a config
861 // template would otherwise put a second `realm` auth-param on every 401 this
862 // server sends, which a conforming client parses as a different challenge.
863 //
864 // `HeaderValue::from_str` already refuses CR and LF, so there was never a response
865 // splitting hole here; the escape is about producing a challenge that parses as
866 // the one thing it means.
867 challenge: HeaderValue::from_str(&format!(
868 "Basic realm=\"{}\"",
869 escape_quoted_string(&issuer)
870 ))
871 .unwrap_or_else(|_| HeaderValue::from_static("Basic realm=\"oauth\"")),
872 origin: issuer_origin(&issuer).to_string(),
873 subject: self.subject,
874 approval: self.approval,
875 #[cfg(feature = "consent")]
876 authentication: self.authentication,
877 verification: self.verification,
878 routes,
879 }),
880 })
881 }
882}
883
884/// The largest request body any endpoint this service serves will read.
885///
886/// 64 KiB, chosen against the largest legitimate body rather than picked round. The biggest is an
887/// RFC 7591 s2 client metadata document (a registration with many redirect URIs and a `jwks`), and
888/// after that an RFC 9126 push carrying an RFC 9101 signed request object; both are kilobytes, not
889/// tens of them. Every other body is a form of a dozen short parameters.
890///
891/// Stated rather than inherited from the framework's default, because a cap this file did not
892/// choose is a cap that can change under it, and this one is a security property: these endpoints
893/// buffer the whole body before parsing, and they are reachable before the client is
894/// authenticated, so the ceiling on "how much memory can an anonymous request make this server
895/// hold" is set here.
896/// Public because it is a ceiling a host needs when sizing its own proxy or gateway limits, and
897/// because [`MAX_FORM_PARAMETERS`] documents itself in terms of it.
898pub const MAX_BODY_BYTES: usize = 64 * 1024;
899
900/// The largest number of form or query parameters any endpoint this service serves will decode.
901///
902/// [`MAX_BODY_BYTES`] does NOT bound this, and that is why the constant exists. Decoding is per
903/// PARAMETER, not per byte: every pair is split, percent-decoded and pushed onto a vector, and
904/// every later `param` lookup is a linear scan across all of them. MEASURED with
905/// `benches/http_surface.rs`, on an aarch64 macOS laptop, before this cap existed: `POST /token`
906/// costs 2.65 us with no extra parameters, 7.55 us with 64 ignored ones, 22.15 us with 256 and
907/// 83.33 us with 1024, against 163 ns for a 404 on an unrouted path. The growth is LINEAR (81 to
908/// 118 ns per parameter across that sweep), which is the problem rather than the reassurance: 64
909/// KiB of `&a=b` pairs is roughly 2300 parameters, so a byte cap alone left one unauthenticated
910/// packet buying about three orders of magnitude more work than the service's cheapest answer.
911/// The GET endpoints are worse still: their parameters arrive in a URL, which [`MAX_BODY_BYTES`]
912/// never applied to at all.
913///
914/// With the cap, the same 1024-parameter request costs 14.50 us, and what is left is not decoding:
915/// it is buffering and UTF-8 validating the body, which [`MAX_BODY_BYTES`] already bounds and which
916/// no parameter cap can avoid, since the count cannot be known before the bytes have arrived.
917///
918/// SIXTY-FOUR, counted from the largest legitimate request rather than rounded. The biggest form
919/// this crate defines is an RFC 9126 push of an authorization request, which can carry
920/// `response_type`, `client_id`, `redirect_uri`, `scope`, `state`, `code_challenge`,
921/// `code_challenge_method`, `nonce`, `prompt`, `login_hint`, `max_age`, `acr_values`, `request`,
922/// `authorization_details` and up to four of client authentication's parameters: about twenty.
923/// After it comes an RFC 8693 exchange at about thirteen. RFC 8707 s2 allows `resource` to repeat,
924/// which is the one parameter a conforming client can send many of, and RFC 6749 s3.1 lets a
925/// deployment define extension parameters this server ignores. Sixty-four is therefore roughly
926/// three times the largest request this crate can construct, with the whole of that headroom left
927/// for repetition and extensions, and it is still 36 times below what the byte cap alone allowed.
928///
929/// The check is a count of `&` separators with an early exit, so REFUSING is cheaper than parsing
930/// even the first pair: it stops reading the input at the sixty-fourth separator. That matters
931/// because a refusal is work an attacker chooses the rate of.
932pub const MAX_FORM_PARAMETERS: usize = 64;
933
934/// The absolute request path an advertised URL occupies, measured from the ORIGIN's root.
935///
936/// Origin-rooted rather than issuer-relative because that is what a router matches against. For
937/// an issuer with no path the two are the same string; for `https://as.example/tenant1` the
938/// token endpoint is served at `/tenant1/token`, so a host can build one router per tenant and
939/// merge them without any of them colliding.
940fn endpoint_path(issuer: &str, endpoint: &'static str, url: &str) -> Result<String, ServiceError> {
941 match url.strip_prefix(issuer) {
942 Some(rest) if rest.starts_with('/') => {
943 let prefix = crate::metadata::issuer_path(issuer);
944 let mut path = String::with_capacity(prefix.len() + rest.len());
945 path.push_str(prefix);
946 path.push_str(rest);
947 // NORMALISED TO WIRE FORM here, at build time, and nowhere else. The issuer arrives
948 // exactly as the host configured it and the two legal spellings of a path a client
949 // must escape produce the same bytes on the wire; `encode_route_path` is what makes
950 // the table hold those bytes, so `handle` can compare the raw path and nothing that
951 // sits in front of this service disagrees with it about which route was asked for.
952 Ok(encode_route_path(&path))
953 }
954 _ => Err(ServiceError::EndpointOutsideIssuer {
955 endpoint,
956 url: url.to_string(),
957 }),
958 }
959}
960
961/// The issuer's `scheme://authority`, which is what an `Origin` header carries (RFC 6454 s6.1:
962/// scheme, host, and port, with no path).
963///
964/// SEARCHED for, rather than computed by subtraction. Subtracting the length of [`crate::metadata::issuer_path`] from the
965/// issuer would put the split point wherever a trailing slash the path trimmed used to be, so an
966/// issuer with both a non-ASCII path and a trailing slash split inside a character and PANICKED:
967/// `https://as.example/\u{e9}//` landed on the second byte of the two-byte character. Searching
968/// for the separator can only ever land on a boundary, whatever the issuer contains. The one
969/// caller passes an issuer already trimmed by `from_config`, so nothing reachable produced that
970/// panic, but "safe because one caller trims first" is not a property this function can state.
971fn issuer_origin(issuer: &str) -> &str {
972 // Past `://` when there is one, so a colon in the scheme cannot be read as a port and the
973 // first `/` found is the one that starts the path. `issuer_path` reads the same shape.
974 let authority_at = match issuer.find("://") {
975 Some(i) => i + 3,
976 None => 0,
977 };
978 match issuer[authority_at..].find('/') {
979 Some(i) => &issuer[..authority_at + i],
980 None => issuer,
981 }
982}
983
984// ---------------------------------------------------------------------------------------------
985// The service, its route table, and its body reader
986// ---------------------------------------------------------------------------------------------
987
988/// The paths this service answers on, derived once by [`ServiceBuilder::build`].
989///
990/// Every field is an ORIGIN-ROOTED absolute path, and every optional one is `Some` exactly when
991/// the RFC 8414 document advertises the corresponding endpoint. That equivalence is the point:
992/// [`ServiceBuilder::build`] derives both from the same document, so "advertised" and "routed"
993/// cannot drift apart, and it refuses to build when two of them land on one path.
994#[derive(Debug)]
995struct Routes {
996 well_known: String,
997 authorize: String,
998 token: String,
999 device: String,
1000 introspect: Option<String>,
1001 revoke: Option<String>,
1002 verification: Option<String>,
1003 register: Option<String>,
1004 /// RFC 7592 `{registration_endpoint}/{client_id}`, held as the prefix INCLUDING its trailing
1005 /// slash. The one dynamic segment this service has.
1006 manage: Option<String>,
1007 #[cfg(feature = "par")]
1008 par: Option<String>,
1009 #[cfg(feature = "jwt")]
1010 jwks: Option<String>,
1011}
1012
1013/// Which endpoint a request path resolved to, plus anything captured out of the path.
1014enum Route<'a> {
1015 Metadata,
1016 Authorize,
1017 Token,
1018 Device,
1019 Introspect,
1020 Revoke,
1021 Verification,
1022 Register,
1023 /// RFC 7592: the `client_id` segment, RAW as it arrived. Decoded at the match arms by
1024 /// `decode_path_segment`, and only after the route has been decided, for the reason the
1025 /// resolver's own comment gives: the path is matched in wire form.
1026 ///
1027 /// THE DECODED VALUE IS NOT VALIDATED, and it goes to the host's
1028 /// [`crate::store::Storage::get_client`]. `%2F` decodes to a real `/` here, `%2E%2E` to `..`,
1029 /// `%00` to a NUL, and invalid UTF-8 to U+FFFD. Routing is unaffected — the raw path is what
1030 /// was matched, so nothing mounted under the registration endpoint can be reached this way —
1031 /// and this crate does not refuse the value, because an identifier's syntax is the host's
1032 /// (RFC 6749 s2.2) and a host whose ids are HTTPS URLs has a `/` in every one. It is also not
1033 /// a shape unique to this route: the authorization and token endpoints hand `get_client` an
1034 /// arbitrary unauthenticated string too, so validating here would close nothing. The
1035 /// obligation is stated where the value lands, on [`crate::store::Storage::get_client`], and
1036 /// `tests/storage_client_id_contract.rs` pins it.
1037 Manage(&'a str),
1038 #[cfg(feature = "par")]
1039 Par,
1040 #[cfg(feature = "jwt")]
1041 Jwks,
1042}
1043
1044impl Routes {
1045 /// Resolve a request path, or `None` for a 404.
1046 ///
1047 /// STATIC PATHS ARE TRIED FIRST, and that ordering is load bearing rather than incidental.
1048 /// The one dynamic route (RFC 7592 management, `{register}/{client_id}`) is a prefix match, so
1049 /// a host whose configuration puts some other endpoint underneath the registration endpoint
1050 /// would otherwise see that endpoint shadowed by a client id that can never exist. The
1051 /// duplicate-path check in [`ServiceBuilder::build`] compares literal strings and cannot see
1052 /// that case, so the matcher settles it the same way a trie-based router would: a literal
1053 /// segment beats a parameter.
1054 fn resolve<'a>(&self, path: &'a str) -> Option<Route<'a>> {
1055 // A linear walk over at most eleven short strings. A trie would be the right shape for a
1056 // table of hundreds; here it would be more code, more allocation at build time, and
1057 // slower, because the first comparison usually fails on its first byte.
1058 if path == self.well_known {
1059 return Some(Route::Metadata);
1060 }
1061 if path == self.authorize {
1062 return Some(Route::Authorize);
1063 }
1064 if path == self.token {
1065 return Some(Route::Token);
1066 }
1067 if path == self.device {
1068 return Some(Route::Device);
1069 }
1070 if self.introspect.as_deref() == Some(path) {
1071 return Some(Route::Introspect);
1072 }
1073 if self.revoke.as_deref() == Some(path) {
1074 return Some(Route::Revoke);
1075 }
1076 if self.verification.as_deref() == Some(path) {
1077 return Some(Route::Verification);
1078 }
1079 if self.register.as_deref() == Some(path) {
1080 return Some(Route::Register);
1081 }
1082 #[cfg(feature = "par")]
1083 if self.par.as_deref() == Some(path) {
1084 return Some(Route::Par);
1085 }
1086 #[cfg(feature = "jwt")]
1087 if self.jwks.as_deref() == Some(path) {
1088 return Some(Route::Jwks);
1089 }
1090 // ONE segment, and a non-empty one. `crate::registration`'s `registration_client_uri`
1091 // percent-encodes the id into the URL this server itself minted (RFC 7592 s3), and
1092 // `decode_path_segment` undoes exactly that below, so a RAW slash arriving here is a
1093 // different path rather than a client id whose name contains one. Until the 0.9.1 audit
1094 // this comment asserted the encoding while the minting side did not perform it; the
1095 // asymmetry was invisible only because a minted id is 32 hex characters.
1096 if let Some(prefix) = &self.manage {
1097 if let Some(rest) = path.strip_prefix(prefix.as_str()) {
1098 if !rest.is_empty() && !rest.contains('/') {
1099 return Some(Route::Manage(rest));
1100 }
1101 }
1102 }
1103 None
1104 }
1105}
1106
1107/// The methods a route answers, for the RFC 9110 s15.5.6 `Allow` header a 405 must carry.
1108fn allowed(route: &Route<'_>) -> &'static str {
1109 match route {
1110 // HEAD is listed wherever GET is, because it is served: RFC 9110 s9.3.2 defines it as GET
1111 // with the body dropped, and a client (or a health check) that probes with HEAD must not
1112 // be told the endpoint does not accept it.
1113 Route::Metadata | Route::Authorize => "GET, HEAD",
1114 #[cfg(feature = "jwt")]
1115 Route::Jwks => "GET, HEAD",
1116 Route::Token | Route::Device | Route::Introspect | Route::Revoke | Route::Register => {
1117 "POST"
1118 }
1119 #[cfg(feature = "par")]
1120 Route::Par => "POST",
1121 Route::Verification => "GET, HEAD, POST",
1122 Route::Manage(_) => "GET, HEAD, PUT, DELETE",
1123 }
1124}
1125
1126/// An RFC-shaped authorization server as an HTTP service.
1127///
1128/// Built by [`ServiceBuilder`]. Cheap to clone (one refcount bump) and safe to share, so a host
1129/// clones one per connection or per task without duplicating any of the state or re-serializing
1130/// anything.
1131///
1132/// # Mounting it
1133///
1134/// With the `axum` feature, `axum::Router::from(service)` is the whole wiring. Without it, call
1135/// [`handle`](AuthorizationService::handle) from whatever the host's own server hands it: it takes
1136/// an [`http::Request`] over any [`http_body::Body`] and answers with an
1137/// [`http::Response<Body>`](Response).
1138pub struct AuthorizationService<S: Storage, C: Clock> {
1139 inner: Arc<Inner<S, C>>,
1140}
1141
1142// The route table and nothing else. Every field of `Inner` beyond it is either a host-supplied
1143// closure (which has no useful representation) or bytes already published on the wire, so the
1144// paths are the only part a host debugging a 404 wants to see. Hand written rather than derived
1145// for the same reason `Clone` is, and because a derive would print the whole metadata document.
1146impl<S: Storage, C: Clock> std::fmt::Debug for AuthorizationService<S, C> {
1147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1148 f.debug_struct("AuthorizationService")
1149 .field("routes", &self.inner.routes)
1150 .finish_non_exhaustive()
1151 }
1152}
1153
1154// Hand written rather than derived: `#[derive(Clone)]` would demand `S: Clone` and `C: Clone`,
1155// which is a bound on the HOST's storage that nothing here needs, since the only field is an
1156// `Arc`.
1157impl<S: Storage, C: Clock> Clone for AuthorizationService<S, C> {
1158 fn clone(&self) -> Self {
1159 AuthorizationService {
1160 inner: Arc::clone(&self.inner),
1161 }
1162 }
1163}
1164
1165impl<S: Storage, C: Clock> AuthorizationService<S, C> {
1166 /// Answer one request.
1167 ///
1168 /// Generic over the request body so that a host on any HTTP server can call it: `hyper`,
1169 /// `axum`, a test harness holding a `String`. The body is read whole, up to 64 KiB, before it
1170 /// is parsed, which is what these endpoints require (client
1171 /// authentication for `client_secret_post` is IN the body, so nothing can be checked before
1172 /// it has all arrived).
1173 pub async fn handle<B>(&self, request: Request<B>) -> Response
1174 where
1175 B: http_body::Body,
1176 {
1177 let state = &*self.inner;
1178 let (parts, body) = request.into_parts();
1179 let method = parts.method;
1180 let headers = parts.headers;
1181 let uri = parts.uri;
1182
1183 // THE RAW WIRE PATH, matched byte for byte. The normalisation happens on the other side:
1184 // `ServiceBuilder::build` runs every route through `encode_route_path`, so the table is
1185 // already in the form a client sends. Decoding here instead would make `/%74oken` the
1186 // token endpoint and `/%72egister` the registration endpoint, which is a different string
1187 // to every reverse proxy, ingress rule and WAF in front of this service and the same one
1188 // to this service: a host restricting RFC 7591 registration to an internal network BY PATH
1189 // would be serving it to everyone. Only the captured RFC 7592 id is decoded, and only
1190 // after the route has been decided.
1191 //
1192 // "BYTE FOR BYTE" IS QUALIFIED BY ONE RULE, and only one: RFC 3986 s6.2.2.1 says the
1193 // hexadecimal digits of a percent-encoding are case INSENSITIVE and directs a normaliser
1194 // to prefer the uppercase form, so `%c3` and `%C3` are the same octet and any client
1195 // library, proxy or ingress in the path is entitled to convert between them. Both sides
1196 // are therefore brought to the uppercase form -- the table by `encode_route_path` at build
1197 // time, the wire path by `uppercase_escapes` here -- and compared verbatim after that.
1198 // This is NOT decoding and gives none of decoding's ground away: `%74oken` uppercases to
1199 // `%74oken`, which is still not `/token`, and every rule in front of this service that
1200 // matched the raw path is looking at a string this transformation cannot alter the
1201 // meaning of. It costs a scan for `%` per request and allocates only for a path that
1202 // carries a lowercase escape.
1203 let path = uppercase_escapes(uri.path());
1204 let route = match state.routes.resolve(&path) {
1205 Some(route) => route,
1206 None => return respond(StatusCode::NOT_FOUND, Body::empty()),
1207 };
1208
1209 // RFC 9110 s9.3.2: HEAD is GET with the body suppressed. Handled here, once, rather than
1210 // in eleven handlers: the response is produced exactly as it would have been for GET
1211 // (headers included, `Content-Length` above all) and only the bytes are dropped.
1212 let head = method == Method::HEAD;
1213 let method = match head {
1214 true => Method::GET,
1215 false => method,
1216 };
1217
1218 let mut response = self.dispatch(route, &method, headers, &uri, body).await;
1219 if head {
1220 // The LENGTH is kept and only the bytes are dropped. RFC 9110 s9.3.2 says a HEAD
1221 // response's header fields SHOULD be identical to the GET's, and `Content-Length` is
1222 // the one a cache and a health check actually read; answering zero would tell them
1223 // the representation is empty. Setting it here rather than leaving the full body for
1224 // the transport to suppress means a host that does not special-case HEAD still emits
1225 // a correct response instead of content the RFC forbids.
1226 let length = response.body().size_hint().exact().unwrap_or(0);
1227 *response.body_mut() = Body::empty();
1228 if let Ok(value) = HeaderValue::from_str(&length.to_string()) {
1229 response.headers_mut().insert(header::CONTENT_LENGTH, value);
1230 }
1231 }
1232 response
1233 }
1234
1235 /// The method check and the body read, then the handler.
1236 async fn dispatch<B>(
1237 &self,
1238 route: Route<'_>,
1239 method: &Method,
1240 headers: HeaderMap,
1241 uri: &Uri,
1242 body: B,
1243 ) -> Response
1244 where
1245 B: http_body::Body,
1246 {
1247 let state = &*self.inner;
1248 // Read the body only where a body is read. A GET whose sender attached one is not this
1249 // service's problem, and buffering it would be a memory cost with no reader.
1250 macro_rules! form_body {
1251 () => {
1252 match collect_body(body, MAX_BODY_BYTES).await {
1253 Ok(bytes) => bytes,
1254 Err(e) => return body_error(e),
1255 }
1256 };
1257 }
1258 match (route, method.as_str()) {
1259 (Route::Metadata, "GET") => metadata_handler(state),
1260 #[cfg(feature = "jwt")]
1261 (Route::Jwks, "GET") => jwks_handler(state),
1262 (Route::Authorize, "GET") => authorize_handler(state, &headers, uri).await,
1263 (Route::Token, "POST") => token_handler(state, &headers, &form_body!()).await,
1264 (Route::Device, "POST") => {
1265 device_authorization_handler(state, &headers, &form_body!()).await
1266 }
1267 (Route::Introspect, "POST") => introspect_handler(state, &headers, &form_body!()).await,
1268 (Route::Revoke, "POST") => revoke_handler(state, &headers, &form_body!()).await,
1269 #[cfg(feature = "par")]
1270 (Route::Par, "POST") => {
1271 pushed_authorization_handler(state, &headers, &form_body!()).await
1272 }
1273 (Route::Verification, "GET") => verification_page_handler(state, &headers, uri).await,
1274 (Route::Verification, "POST") => {
1275 verification_submit_handler(state, &headers, &form_body!()).await
1276 }
1277 (Route::Register, "POST") => register_handler(state, &headers, &form_body!()).await,
1278 // The captured segment arrives RAW, because the route was decided on the wire path
1279 // (see `handle`), and it is decoded here: a client id may contain characters a path
1280 // segment reserves, and `registration_client_uri` percent-encodes them into the URL
1281 // this server minted (RFC 7592 s3), so this is the other half of that round trip. It
1282 // is the ONE decode this module performs on a path, and it happens after the route is
1283 // settled, so it can never turn one route into another.
1284 (Route::Manage(client_id), "GET") => {
1285 read_registration_handler(state, &headers, &decode_path_segment(client_id)).await
1286 }
1287 (Route::Manage(client_id), "PUT") => {
1288 let id = decode_path_segment(client_id).into_owned();
1289 update_registration_handler(state, &headers, &id, &form_body!()).await
1290 }
1291 (Route::Manage(client_id), "DELETE") => {
1292 delete_registration_handler(state, &headers, &decode_path_segment(client_id)).await
1293 }
1294 // RFC 9110 s15.5.6: a 405 MUST carry `Allow`. Without it a client cannot tell a
1295 // wrong method from a route that does not exist.
1296 (route, _) => {
1297 let mut resp = respond(StatusCode::METHOD_NOT_ALLOWED, Body::empty());
1298 resp.headers_mut()
1299 .insert(header::ALLOW, HeaderValue::from_static(allowed(&route)));
1300 resp
1301 }
1302 }
1303 }
1304}
1305
1306/// Why a request body could not be read.
1307enum BodyError {
1308 /// It exceeded [`MAX_BODY_BYTES`].
1309 TooLarge,
1310 /// The transport gave up: a truncated body, a broken connection, a bad chunk encoding.
1311 Incomplete,
1312}
1313
1314/// The answer to a body that could not be read.
1315///
1316/// Not an RFC 6749 s5.2 error body, and deliberately not: section 5.2 describes what the server
1317/// says about a REQUEST it managed to parse, and neither of these got that far. A 413 and a 400
1318/// are what an HTTP client (and every proxy between it and here) already understands.
1319fn body_error(e: BodyError) -> Response {
1320 match e {
1321 BodyError::TooLarge => text_response(
1322 StatusCode::PAYLOAD_TOO_LARGE,
1323 "request body exceeds this server's limit",
1324 ),
1325 BodyError::Incomplete => {
1326 text_response(StatusCode::BAD_REQUEST, "request body was not received")
1327 }
1328 }
1329}
1330
1331/// Read a request body whole, refusing at `limit` bytes.
1332///
1333/// The cap is checked TWICE and both checks are needed. The size hint catches a declared
1334/// `Content-Length` before a single byte is buffered, which is what makes a hostile
1335/// `Content-Length: 4000000000` cost nothing; the running total catches a chunked body that
1336/// declares nothing and just keeps sending, which is the case the first check cannot see.
1337async fn collect_body<B>(body: B, limit: usize) -> Result<Bytes, BodyError>
1338where
1339 B: http_body::Body,
1340{
1341 let hint = body.size_hint();
1342 if hint.lower() > limit as u64 {
1343 return Err(BodyError::TooLarge);
1344 }
1345 // Sized from the hint when there is one, so the common case (a form body with a
1346 // `Content-Length`) allocates exactly once. Clamped to the limit so the hint cannot itself be
1347 // the allocation primitive.
1348 let expected = hint.upper().unwrap_or(hint.lower()).min(limit as u64) as usize;
1349 let mut collected: Vec<u8> = Vec::with_capacity(expected);
1350
1351 // Pinned on the stack: `poll_frame` needs `Pin<&mut B>` and `B` is not required to be
1352 // `Unpin`, so boxing would be the only alternative and it would allocate on every request.
1353 let mut body = std::pin::pin!(body);
1354 loop {
1355 match std::future::poll_fn(|cx| body.as_mut().poll_frame(cx)).await {
1356 None => break,
1357 Some(Err(_)) => return Err(BodyError::Incomplete),
1358 Some(Ok(frame)) => {
1359 // Trailers carry no request content. `into_data` hands them back rather than
1360 // panicking, and they are dropped.
1361 if let Ok(mut data) = frame.into_data() {
1362 if collected.len().saturating_add(data.remaining()) > limit {
1363 return Err(BodyError::TooLarge);
1364 }
1365 while data.has_remaining() {
1366 let chunk = data.chunk();
1367 collected.extend_from_slice(chunk);
1368 let n = chunk.len();
1369 data.advance(n);
1370 }
1371 }
1372 }
1373 }
1374 }
1375 Ok(Bytes::from(collected))
1376}
1377
1378// ---------------------------------------------------------------------------------------------
1379// The axum adapter, behind the `axum` cargo feature
1380// ---------------------------------------------------------------------------------------------
1381
1382/// Mount the service on axum.
1383///
1384/// This is the ENTIRE axum surface of this crate, and it is one function on purpose. axum is a
1385/// 0.x crate: its major has moved before and will move again, and every earlier version of this
1386/// module put `axum::Router` in the return type of the only way to use the `http` feature, which
1387/// meant a host on a different axum major could not enable the feature at all. Confining axum to
1388/// an adapter behind its own feature makes that a per-host decision instead of this crate's.
1389///
1390/// A `fallback` rather than a route per endpoint: the route table is DERIVED from the metadata
1391/// document at build time, so re-declaring it here in axum's syntax would create
1392/// a second table that could disagree with the first. A 404 from
1393/// [`AuthorizationService::handle`] is a path this server does not serve, which is exactly what a
1394/// fallback means.
1395///
1396/// # Why the request is answered on a spawned task
1397///
1398/// BECAUSE A CLIENT THAT HANGS UP MUST NOT BE ABLE TO STOP THE SERVER MID-SEQUENCE. hyper drops
1399/// the service future when the connection closes, and a dropped future does not fail: the code
1400/// after the `.await` it was suspended on simply never runs. `crate::server` has no transactions
1401/// (`Storage` deliberately offers none), so several of its sequences are an atomic TAKE followed
1402/// by a write, and every one of those arguments about which way the pair fails assumes that a
1403/// failure HAPPENS. The refresh rotation is the sharp case: `Storage::take_refresh_token` removes
1404/// the chain and the spent marker that arms RFC 9700 s4.14.2 reuse detection is written after it,
1405/// so a drop in between leaves the chain gone with no marker, which is the exact state that
1406/// ordering exists to prevent. The authorization code path has the same shape with its consumed
1407/// record. Neither is a race an attacker has to win by timing: whoever presents the credential is
1408/// whoever decides when to close the socket.
1409///
1410/// Awaiting a `JoinHandle` moves the cancellation to the RIGHT place. The client's disconnect
1411/// cancels this adapter's await on the handle; the spawned task keeps its own place in the runtime
1412/// and runs the store sequence to the end. Nothing else in this crate can do this, because the
1413/// `http` feature deliberately pulls in no runtime; the `axum` feature is the one place a runtime
1414/// is already present (`axum = ["http", "dep:axum", "dep:tokio"]`), so it is the one place this can
1415/// be contained. A HOST MOUNTING [`AuthorizationService::handle`] ITSELF OWNS THIS, and should
1416/// spawn for the same reason.
1417///
1418/// # What a host may notice
1419///
1420/// IN-FLIGHT WORK IS NO LONGER BOUNDED BY CONNECTIONS. That is the point of the spawn and it is
1421/// also its cost: a client that hangs up stops waiting but no longer stops the work, so axum's and
1422/// hyper's connection limits, and any accept-side bound the host set, no longer bound the tasks
1423/// this service is running. The bound becomes request RATE times handler latency, and NEITHER
1424/// FACTOR IS THIS CRATE'S TO SET.
1425///
1426/// The rate half is the stronger one: the limiter runs INSIDE [`AuthorizationService::handle`] and
1427/// refuses before the store is touched, so a refused request costs a spawn and nothing more. It is
1428/// still not a global ceiling — the budgets [`crate::rate_limit`] ships for the endpoints that
1429/// name a client are keyed per `client_id`, which RFC 6749 section 2.2 makes public, so a caller
1430/// spraying identifiers gets a budget apiece up to
1431/// [`crate::rate_limit::DEFAULT_MAX_TRACKED_CLIENTS`] counters before the rest share an overflow
1432/// counter.
1433///
1434/// The latency half is the host's outright. A handler makes a bounded NUMBER of store calls, but
1435/// each one is the host's [`crate::store::Storage`] and this crate sets no timeout anywhere, on
1436/// anything; the token path additionally awaits [`crate::jwt::Es256Signer`], which that trait's
1437/// own docs say may be a network round trip to a KMS. Nor is the latency all waiting:
1438/// a host-installed [`crate::client::SecretVerifier`] runs its KDF INLINE on the executor thread
1439/// polling the request — that trait prices argon2id at ordinary parameters at roughly 200 ms, paid
1440/// per token request and on the unknown-client path too — so it occupies a worker rather than
1441/// yielding it. A host that wants a hard
1442/// ceiling should take a semaphore permit before the spawn, or spawn into a `JoinSet` it owns, and
1443/// answer 503 when it cannot get one.
1444///
1445/// A PANIC in a handler no longer unwinds into hyper. It arrives here as a `JoinError` and is
1446/// answered with an empty 500, which is what the panicking connection produced anyway, minus the
1447/// connection dying with it. RUNTIME SHUTDOWN is the other `JoinError`: a task cancelled because
1448/// its runtime is going away answers the same 500. The two are not distinguished on the wire on
1449/// purpose, because they are the same news to the client (this request did not complete and it
1450/// does not know whether anything happened), and both are already visible to the host: a panic
1451/// through its own hook, a shutdown because it asked for one.
1452///
1453/// # Cost
1454///
1455/// One `tokio::spawn` per request, which is ONE allocation: measured with `tests/support/alloc.rs`
1456/// on aarch64-apple-darwin at 1 alloc and 128 bytes for a trivial task, the block sized by the task
1457/// header plus the handler future. `tests/allocation.rs` budgets the REQUEST path, which this does
1458/// not touch: nothing inside [`AuthorizationService::handle`] changes, and the token endpoint's own
1459/// budget there is two orders of magnitude larger than one task. It buys the store sequence the
1460/// right to finish.
1461///
1462/// The other half of the cost is not an allocation. Detaching the handler from the connection
1463/// means in-flight work is bounded by request RATE rather than by concurrent connections: a client
1464/// that disconnects immediately after sending no longer sheds any load, because the handler it
1465/// started runs to completion regardless. That is the same property that buys the store sequence
1466/// its right to finish, seen from the load side. A host that relied on disconnects for
1467/// backpressure needs a concurrency limit in front of this service — `tower::limit` or the
1468/// equivalent — and the rate limiter this crate already has does not substitute for one, because
1469/// it refuses attempts rather than bounding work already accepted.
1470#[cfg(feature = "axum")]
1471impl<S, C> From<AuthorizationService<S, C>> for axum::Router
1472where
1473 S: Storage + Send + Sync + 'static,
1474 C: Clock + Send + Sync + 'static,
1475{
1476 fn from(service: AuthorizationService<S, C>) -> axum::Router {
1477 axum::Router::new().fallback(move |request: axum::extract::Request| {
1478 let service = service.clone();
1479 async move {
1480 match tokio::spawn(async move { service.handle(request).await }).await {
1481 // `Body` is already a complete `Bytes`, so this is a move, not a copy or a
1482 // stream adapter.
1483 Ok(response) => response.map(|body| axum::body::Body::from(body.into_bytes())),
1484 // A panic, or a runtime being torn down. No body: there is nothing this
1485 // service knows about the failure that a client could act on, and every
1486 // endpoint here answers a different content type, so an invented JSON error
1487 // would be a guess about which one this request wanted.
1488 Err(_) => {
1489 let mut response = axum::http::Response::new(axum::body::Body::empty());
1490 *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
1491 response
1492 }
1493 }
1494 }
1495 })
1496 }
1497}
1498
1499// ---------------------------------------------------------------------------------------------
1500// Wire helpers
1501// ---------------------------------------------------------------------------------------------
1502
1503fn json_content_type() -> HeaderValue {
1504 // RFC 6749 s5.1: "application/json;charset=UTF-8".
1505 HeaderValue::from_static("application/json;charset=UTF-8")
1506}
1507
1508/// RFC 7517 s8.5.1 registers `application/jwk-set+json` for a JWK Set, which is what this is; a
1509/// verifier that only checks for a JSON suffix still accepts it.
1510#[cfg(feature = "jwt")]
1511fn jwks_content_type() -> HeaderValue {
1512 HeaderValue::from_static("application/jwk-set+json")
1513}
1514
1515fn html_content_type() -> HeaderValue {
1516 HeaderValue::from_static("text/html;charset=UTF-8")
1517}
1518
1519/// Serialize a wire type. A serialization failure cannot happen for these shapes (they are plain
1520/// structs of strings and numbers), but a library must not panic inside a host's request path, so
1521/// the fallback is a valid RFC 6749 s5.2 body rather than an `unwrap`.
1522fn json_body<T: Serialize>(value: &T) -> Vec<u8> {
1523 serde_json::to_vec(value).unwrap_or_else(|_| br#"{"error":"server_error"}"#.to_vec())
1524}
1525
1526/// Stamp the RFC 6749 s5.1 caching directives onto a token-plane response.
1527///
1528/// The bodies carry bearer credentials. A shared cache that stores one hands it to whoever asks
1529/// next, which is why the RFC makes this a MUST rather than advice. `Pragma: no-cache` is the
1530/// HTTP/1.0 belt to `no-store`'s braces, and the RFC names both.
1531fn no_store(headers: &mut HeaderMap) {
1532 headers.insert(
1533 header::CACHE_CONTROL,
1534 HeaderValue::from_static("no-store, no-cache, max-age=0"),
1535 );
1536 headers.insert(header::PRAGMA, HeaderValue::from_static("no-cache"));
1537}
1538
1539/// A successful JSON response on the token plane.
1540fn ok_json<T: Serialize>(value: &T) -> Response {
1541 let mut resp = respond(StatusCode::OK, json_body(value));
1542 let headers = resp.headers_mut();
1543 headers.insert(header::CONTENT_TYPE, json_content_type());
1544 no_store(headers);
1545 resp
1546}
1547
1548/// An RFC 6749 s5.2 error response.
1549///
1550/// `via_header` records whether the client presented credentials in the `Authorization` header,
1551/// and it is the ONLY thing that decides between 400 and 401. Section 5.2 mandates 401 exactly
1552/// when header authentication was attempted and failed, and says the server MAY use 401
1553/// otherwise. It does not, because RFC 9110 s15.5.2 requires every 401 to carry a challenge, and
1554/// challenging a client that never offered header credentials tells it to retry a scheme it did
1555/// not choose. So header failures get 401 plus `WWW-Authenticate`, and everything else gets 400.
1556fn error_response(err: &ErrorResponse, via_header: bool, challenge: &HeaderValue) -> Response {
1557 let mut status =
1558 StatusCode::from_u16(err.http_status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
1559 if status == StatusCode::UNAUTHORIZED && !via_header {
1560 status = StatusCode::BAD_REQUEST;
1561 }
1562 let mut resp = respond(status, json_body(err));
1563 let headers = resp.headers_mut();
1564 headers.insert(header::CONTENT_TYPE, json_content_type());
1565 no_store(headers);
1566 if status == StatusCode::UNAUTHORIZED {
1567 headers.insert(header::WWW_AUTHENTICATE, challenge.clone());
1568 }
1569 resp
1570}
1571
1572/// The RFC 8628 verification page, and the only HTML this server emits.
1573///
1574/// It carries more headers than any other response here because it is the only one a BROWSER
1575/// renders, and the only one whose defences a browser can be tricked into satisfying on the user's
1576/// behalf.
1577///
1578/// FRAMING. The page's CSRF defence is `Sec-Fetch-Site: same-origin` (see
1579/// `same_origin_submission`), and a document inside a cross-site iframe posting to its own origin
1580/// sends exactly that. So without a framing refusal the whole defence is decorative against a
1581/// clickjack: an attacker frames the page invisibly, starts a device flow of their own, and lands
1582/// the user's click on Approve. `frame-ancestors 'none'` is the standard's answer and
1583/// `X-Frame-Options: DENY` is the one older browsers obey; both are sent because they are
1584/// enforced by different code paths and neither supersedes the other everywhere.
1585///
1586/// CACHING. The body carries a live single-use CSRF token and the details of a third party's
1587/// pending grant. This was the one response in this file that did not call `no_store`, which
1588/// meant a shared cache, or a browser's back button, could re-serve another user's approval form.
1589///
1590/// The rest is the ordinary hardening for a page with no scripts, no styles, no images and one
1591/// same-origin form: `default-src 'none'` (nothing may be loaded), `form-action 'self'` (the
1592/// submission cannot be redirected off-origin by injected markup), `base-uri 'none'` (a `<base>`
1593/// cannot relocate the relative form action), `nosniff`, and `no-referrer` so the user code in
1594/// the deep-link URL is not handed to a third party.
1595fn html_response(status: StatusCode, body: String) -> Response {
1596 let mut resp = respond(status, body);
1597 let headers = resp.headers_mut();
1598 headers.insert(header::CONTENT_TYPE, html_content_type());
1599 no_store(headers);
1600 headers.insert(
1601 header::CONTENT_SECURITY_POLICY,
1602 HeaderValue::from_static(
1603 "default-src 'none'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'",
1604 ),
1605 );
1606 headers.insert(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY"));
1607 headers.insert(
1608 header::X_CONTENT_TYPE_OPTIONS,
1609 HeaderValue::from_static("nosniff"),
1610 );
1611 headers.insert(
1612 header::REFERRER_POLICY,
1613 HeaderValue::from_static("no-referrer"),
1614 );
1615 resp
1616}
1617
1618// ---------------------------------------------------------------------------------------------
1619// application/x-www-form-urlencoded, in both the body and the query string
1620// ---------------------------------------------------------------------------------------------
1621
1622fn hex_value(b: u8) -> Option<u8> {
1623 match b {
1624 b'0'..=b'9' => Some(b - b'0'),
1625 b'a'..=b'f' => Some(b - b'a' + 10),
1626 b'A'..=b'F' => Some(b - b'A' + 10),
1627 _ => None,
1628 }
1629}
1630
1631/// Decode one `application/x-www-form-urlencoded` component.
1632///
1633/// Borrows when there is nothing to decode, which is the common case for `grant_type`, `code`,
1634/// and every opaque token this server issues (hex and base64url need no escaping). Only a value
1635/// that actually contains `%` or `+` costs an allocation.
1636fn decode_component(raw: &str) -> Cow<'_, str> {
1637 percent_decode(raw, true)
1638}
1639
1640/// Decode ONE path segment: percent escapes only.
1641///
1642/// A `+` in a path segment is a literal plus (RFC 3986 s3.3 puts it in `sub-delims`); only
1643/// `application/x-www-form-urlencoded` gives it the "space" meaning. Decoding it as a space here
1644/// would rewrite the RFC 7592 s3 `registration_client_uri` this server itself minted, and a client
1645/// whose id contains a plus would find its own management URL pointing at a different client.
1646///
1647/// ONE SEGMENT, never the whole path. For a few days of the 0.9.1 audit this crate decoded the
1648/// entire request path before matching it, to make a raw non-ASCII issuer routable, and the price
1649/// was two defects at once: `/%74oken` became the token endpoint and `/%72egister` became the RFC
1650/// 7591 registration endpoint, under every reverse proxy, ingress rule and WAF that had matched
1651/// the RAW path and seen no such string; and an issuer spelled the way RFC 3986 section 3.3
1652/// requires (`https://as.example/tenant%20a`) stopped routing at all, because
1653/// [`endpoint_path`] holds the issuer verbatim. Both directions are fixed in the ROUTE TABLE
1654/// instead, by [`encode_route_path`]: the table is normalised into wire form once at build time
1655/// and the wire path is compared byte for byte, which is what every layer in front of this service
1656/// is also doing.
1657fn decode_path_segment(raw: &str) -> Cow<'_, str> {
1658 percent_decode(raw, false)
1659}
1660
1661/// Normalise a route-table path into the form a client puts on the wire.
1662///
1663/// The table is derived from the issuer AS THE HOST CONFIGURED IT (see [`endpoint_path`]), and a
1664/// host may legitimately configure either of two spellings for a path a client must escape: the
1665/// RFC 3986 section 3.3 one, `https://as.example/tenant%20a`, which is a legal URI, or a raw
1666/// non-ASCII one, `https://as.example/\u{e9}`, which is not but which this crate accepts and
1667/// `tests/issuer_origin_boundary.rs` pins as buildable. A client fetching from either sends the
1668/// same bytes: percent-encoded ones. So the table is brought to that form ONCE, here, and the
1669/// matcher never touches the wire path.
1670///
1671/// `%` IS LEFT ALONE, which is what makes the first spelling survive: an issuer that already
1672/// carries escapes passes through unchanged rather than being encoded a second time into `%2520`.
1673/// The cost is that a literal `%` in an issuer that is not an escape cannot be expressed, which is
1674/// a URI that is malformed under section 2.1 anyway.
1675///
1676/// Left alone EXCEPT FOR THE CASE OF ITS TWO HEX DIGITS, which are uppercased. RFC 3986 s6.2.2.1
1677/// makes those digits case insensitive and directs a normaliser to prefer the uppercase form, so
1678/// `https://as.example/caf%c3%a9` and `https://as.example/caf%C3%A9` are the same issuer and a
1679/// client, proxy or ingress that normalises sends the uppercase one whichever the document
1680/// carried. Uppercasing here is what makes the table CANONICAL rather than a copy of one host's
1681/// spelling; `uppercase_escapes` does the same to the wire path, and the comparison between the
1682/// two is still byte for byte. A `%` NOT followed by two hex digits is not an escape at all and
1683/// keeps the pass-through behaviour above.
1684///
1685/// Run once per route at build time, so its cost is not on any request path.
1686fn encode_route_path(path: &str) -> String {
1687 const HEX: &[u8; 16] = b"0123456789ABCDEF";
1688 let bytes = path.as_bytes();
1689 let mut out = String::with_capacity(path.len());
1690 let mut skip = 0usize;
1691 for (i, &b) in bytes.iter().enumerate() {
1692 if skip > 0 {
1693 skip -= 1;
1694 continue;
1695 }
1696 if b == b'%' {
1697 if let (Some(&h), Some(&l)) = (bytes.get(i + 1), bytes.get(i + 2)) {
1698 if hex_value(h).is_some() && hex_value(l).is_some() {
1699 out.push('%');
1700 out.push(h.to_ascii_uppercase() as char);
1701 out.push(l.to_ascii_uppercase() as char);
1702 skip = 2;
1703 continue;
1704 }
1705 }
1706 }
1707 // RFC 3986 s3.3 `pchar` (unreserved / sub-delims / ":" / "@"), plus the separator itself
1708 // and the escape introducer.
1709 let verbatim = b.is_ascii_alphanumeric()
1710 || matches!(
1711 b,
1712 b'-' | b'.'
1713 | b'_'
1714 | b'~'
1715 | b'!'
1716 | b'$'
1717 | b'&'
1718 | b'\''
1719 | b'('
1720 | b')'
1721 | b'*'
1722 | b'+'
1723 | b','
1724 | b';'
1725 | b'='
1726 | b':'
1727 | b'@'
1728 | b'/'
1729 | b'%'
1730 );
1731 if verbatim {
1732 out.push(b as char);
1733 } else {
1734 out.push('%');
1735 out.push(HEX[(b >> 4) as usize] as char);
1736 out.push(HEX[(b & 0x0f) as usize] as char);
1737 }
1738 }
1739 out
1740}
1741
1742/// The wire half of [`encode_route_path`]'s normalisation: uppercase the hex digits of every
1743/// percent-encoding in a path and change nothing else.
1744///
1745/// RFC 3986 s6.2.2.1 defines this as case normalisation and it is the only transformation this
1746/// service applies to a path before matching it. It is NOT decoding: the number of characters is
1747/// unchanged, `%74oken` stays `%74oken`, and every rule in front of this service that matched on
1748/// the raw path is matching a string this cannot alter. Doing it on both sides is what lets a host
1749/// spell its issuer's escapes either way and a client normalise or not: all four combinations meet
1750/// in the same canonical form, and a table normalised alone would have swapped one broken pairing
1751/// for another.
1752///
1753/// Borrows unless the path actually carries a lowercase escape, so an ASCII route (every route, in
1754/// every deployment that does not put an escape in its issuer) allocates nothing and pays one scan
1755/// for `%`.
1756fn uppercase_escapes(path: &str) -> Cow<'_, str> {
1757 let bytes = path.as_bytes();
1758 let needs = bytes.iter().enumerate().any(|(i, &b)| {
1759 b == b'%'
1760 && matches!(
1761 (bytes.get(i + 1), bytes.get(i + 2)),
1762 (Some(&h), Some(&l))
1763 if hex_value(h).is_some()
1764 && hex_value(l).is_some()
1765 && (h.is_ascii_lowercase() || l.is_ascii_lowercase())
1766 )
1767 });
1768 if !needs {
1769 return Cow::Borrowed(path);
1770 }
1771 // Written as its own loop rather than as a call to `encode_route_path`: that function also
1772 // ESCAPES what is not a `pchar`, which is right for a path a host configured and wrong for one
1773 // that arrived on the wire, where anything outside the grammar is the client's problem and not
1774 // something this service should quietly rewrite into a route.
1775 //
1776 // Copied in RUNS between escapes rather than byte by byte, which keeps it correct for a
1777 // multi-byte character (`%` is ASCII, so every index this slices at is a character boundary)
1778 // as well as cheaper.
1779 let mut out = String::with_capacity(path.len());
1780 let mut i = 0;
1781 while i < bytes.len() {
1782 match (bytes[i], bytes.get(i + 1), bytes.get(i + 2)) {
1783 (b'%', Some(&h), Some(&l)) if hex_value(h).is_some() && hex_value(l).is_some() => {
1784 out.push('%');
1785 out.push(h.to_ascii_uppercase() as char);
1786 out.push(l.to_ascii_uppercase() as char);
1787 i += 3;
1788 }
1789 _ => {
1790 let start = i;
1791 i += 1;
1792 while i < bytes.len() && bytes[i] != b'%' {
1793 i += 1;
1794 }
1795 out.push_str(&path[start..i]);
1796 }
1797 }
1798 }
1799 Cow::Owned(out)
1800}
1801
1802/// The shared decoder. Borrows when there is nothing to unescape, which is what keeps the common
1803/// case free.
1804fn percent_decode(raw: &str, plus_is_space: bool) -> Cow<'_, str> {
1805 if !raw
1806 .bytes()
1807 .any(|b| b == b'%' || (plus_is_space && b == b'+'))
1808 {
1809 return Cow::Borrowed(raw);
1810 }
1811 let bytes = raw.as_bytes();
1812 let mut out = Vec::with_capacity(bytes.len());
1813 let mut i = 0;
1814 while i < bytes.len() {
1815 match bytes[i] {
1816 b'+' if plus_is_space => {
1817 out.push(b' ');
1818 i += 1;
1819 }
1820 b'%' if i + 2 < bytes.len() => {
1821 match hex_pair(hex_value(bytes[i + 1]), hex_value(bytes[i + 2])) {
1822 Some((h, l)) => {
1823 out.push((h << 4) | l);
1824 i += 3;
1825 }
1826 None => {
1827 // A stray `%` is not an escape. Passing it through unchanged keeps the
1828 // value intact for the comparison that will reject it anyway, rather
1829 // than failing the whole request on a byte we do not need to understand.
1830 out.push(b'%');
1831 i += 1;
1832 }
1833 }
1834 }
1835 b => {
1836 out.push(b);
1837 i += 1;
1838 }
1839 }
1840 }
1841 // Lossy on purpose: a form field that is not UTF-8 cannot match any registered client id,
1842 // token, or scope, so it will be refused a moment later on its merits. Rejecting the whole
1843 // request here would just replace a precise OAuth error with a vague one.
1844 Cow::Owned(match String::from_utf8(out) {
1845 Ok(s) => s,
1846 Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
1847 })
1848}
1849
1850/// `Some((high, low))` only when both nibbles are hex.
1851fn hex_pair(a: Option<u8>, b: Option<u8>) -> Option<(u8, u8)> {
1852 match (a, b) {
1853 (Some(h), Some(l)) => Some((h, l)),
1854 _ => None,
1855 }
1856}
1857
1858type Pair<'a> = (Cow<'a, str>, Cow<'a, str>);
1859
1860/// A request carrying more parameters than [`MAX_FORM_PARAMETERS`], refused before it is decoded.
1861struct TooManyParameters;
1862
1863/// The answer to one of those.
1864///
1865/// A bare 413 rather than an RFC 6749 s5.2 error body, for exactly the reason [`body_error`] is
1866/// one: nothing has been parsed, so there is no `grant_type`, no authenticated client and no
1867/// validated redirect URI to shape a protocol error around, and 413 is what every proxy between
1868/// here and the caller already understands. It says PAYLOAD even when the parameters came from a
1869/// query string, because the payload being refused is the parameter list; 414 would assert the
1870/// URI was too long in BYTES, which it need not be.
1871fn too_many_parameters() -> Response {
1872 text_response(
1873 StatusCode::PAYLOAD_TOO_LARGE,
1874 "request carries too many parameters",
1875 )
1876}
1877
1878/// A refusal that happens BEFORE the request is parsed far enough to have an OAuth error code.
1879///
1880/// These are the only responses in this file that carry bytes without an RFC 6749 s5.2 JSON body:
1881/// the body cap and the parameter cap both fire on the raw request, where there is no `grant_type`
1882/// and no `client_id` to name, and inventing an OAuth error for them would be a claim about a
1883/// request this server never read. They still need a `Content-Type` (RFC 9110 s8.3) or the client
1884/// cannot decode the sentence explaining what happened — which is what they are for. The payloads
1885/// are fixed ASCII literals with no attacker-controlled substring, so `text/plain` is safe here in
1886/// a way it would not be for anything echoing input.
1887fn text_response(status: StatusCode, body: &'static str) -> Response {
1888 let mut resp = respond(status, body);
1889 resp.headers_mut().insert(
1890 header::CONTENT_TYPE,
1891 HeaderValue::from_static("text/plain;charset=UTF-8"),
1892 );
1893 resp
1894}
1895
1896/// Split a form body or query string into decoded pairs. A parameter with no `=` is kept with an
1897/// empty value, which is how a client spells "present but empty" and must not be mistaken for
1898/// absent.
1899/// Sized up front rather than grown. `Split` has no `size_hint`, so `collect` starts from nothing
1900/// and doubles: a six-parameter token body reallocates three times and memcpys 64 bytes per pair
1901/// each time. Counting the separators is one linear pass over bytes that are about to be walked
1902/// anyway, and it is an exact upper bound (empty segments are filtered out, so it can only
1903/// overshoot). This runs on EVERY routed request, which is what makes a free win worth taking.
1904///
1905/// THE SAME PASS ENFORCES [`MAX_FORM_PARAMETERS`], and it is the reason the count is taken here
1906/// rather than at the eight call sites: decoding is per parameter, so a cap any one caller could
1907/// forget to apply is not a cap. The loop returns at the separator that crosses the ceiling, so a
1908/// 64 KiB body of junk is refused after reading the first few hundred bytes of it and allocating
1909/// nothing at all. Separators rather than parameters is a conservative over-count (`a&&&&b` is two
1910/// parameters and five segments), which is the right direction for a bound whose only job is to
1911/// stop absurd requests: nothing legitimate sends empty segments.
1912fn parse_pairs(input: &str) -> Result<Vec<Pair<'_>>, TooManyParameters> {
1913 let mut separators = 0usize;
1914 for b in input.bytes() {
1915 if b == b'&' {
1916 separators += 1;
1917 if separators >= MAX_FORM_PARAMETERS {
1918 return Err(TooManyParameters);
1919 }
1920 }
1921 }
1922 let bound = separators + 1;
1923 let mut pairs = Vec::with_capacity(bound);
1924 pairs.extend(
1925 input
1926 .split('&')
1927 .filter(|part| !part.is_empty())
1928 .map(|part| match part.split_once('=') {
1929 Some((k, v)) => (decode_component(k), decode_component(v)),
1930 None => (decode_component(part), Cow::Borrowed("")),
1931 }),
1932 );
1933 Ok(pairs)
1934}
1935
1936/// The FIRST occurrence of a parameter.
1937///
1938/// RFC 6749 s3.1 says a parameter MUST NOT be sent more than once. First-wins rather than
1939/// last-wins is the deliberate choice: when two intermediaries disagree about which copy counts,
1940/// last-wins is the one that lets a smuggled duplicate override what the earlier layers saw.
1941fn param<'a>(pairs: &'a [Pair<'a>], name: &str) -> Option<&'a str> {
1942 pairs
1943 .iter()
1944 .find(|(k, _)| k == name)
1945 .map(|(_, v)| v.as_ref())
1946}
1947
1948/// A required parameter, or the RFC 6749 s5.2 `invalid_request` naming it.
1949///
1950/// The description is BORROWED from the table below rather than formatted, and the reason is the
1951/// rule `tests/allocation.rs` states on `refused_token_request_allocation_bound`: a refusal is
1952/// work an attacker sets the rate of, so a refusal that allocates is an allocation anyone who can
1953/// open a socket may ask for at whatever rate they like. Every other refusal in this file already
1954/// passes a literal into `error_description`, which is a `Cow<'static, str>`; this one formatted,
1955/// although `name` is a `&'static str` drawn from the finite set of parameters the endpoints
1956/// below actually demand. `src/tests/http.rs` reads that set out of this file's source and fails
1957/// if a call site's name is missing from the table, so the fallback cannot quietly become the
1958/// common case.
1959fn required<'a>(pairs: &'a [Pair<'a>], name: &'static str) -> Result<&'a str, ErrorResponse> {
1960 param(pairs, name).ok_or_else(|| {
1961 let description: Cow<'static, str> = match name {
1962 "code" => Cow::Borrowed("missing required parameter code"),
1963 "device_code" => Cow::Borrowed("missing required parameter device_code"),
1964 "refresh_token" => Cow::Borrowed("missing required parameter refresh_token"),
1965 "token" => Cow::Borrowed("missing required parameter token"),
1966 "subject_token" => Cow::Borrowed("missing required parameter subject_token"),
1967 "subject_token_type" => Cow::Borrowed("missing required parameter subject_token_type"),
1968 // Unreachable from this file today, and kept total rather than made a panic: a
1969 // refusal is the wrong place to introduce a way for the process to die.
1970 other => Cow::Owned(format!("missing required parameter {other}")),
1971 };
1972 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(description)
1973 })
1974}
1975
1976/// Every `resource` parameter, in wire order (RFC 8707 s2 permits repetition, so this is the one
1977/// parameter [`param`]'s first-wins rule must not be applied to: dropping the second occurrence
1978/// would silently issue a token for half of what the client asked for).
1979fn resource_indicators(pairs: &[Pair<'_>]) -> Vec<String> {
1980 pairs
1981 .iter()
1982 .filter(|(k, _)| k == "resource")
1983 .map(|(_, v)| v.as_ref().to_string())
1984 .collect()
1985}
1986
1987/// RFC 9396 s5 for the two doors whose grant has NOWHERE to carry an authorization detail, in any
1988/// build: the RFC 8628 device authorization request and the RFC 8693 token exchange grant.
1989///
1990/// Every other door refuses in the core, gated on `not(feature = "rar")`, because the core has an
1991/// argument the parameter arrives in and can therefore see it. These two do not:
1992/// `device_authorization_with_credential` takes a scope and nothing else, and
1993/// [`crate::token_exchange::TokenExchangeRequest`] derives what it issues from the SUBJECT token,
1994/// so the parameter dies in this router unless this router answers it. That made the same POST to
1995/// the same `/token` URL refuse for `authorization_code` and silently drop for
1996/// `grant_type=...:token-exchange`.
1997///
1998/// UNGATED, unlike the core's refusals, and the difference is what is being refused. There the
1999/// answer turns on whether the build supports any detail TYPE; here it turns on the GRANT, which
2000/// has no field for a detail whether the type is supported or not. `AuthorizationServer::token`
2001/// already refuses to mint detail for a device grant under `rar` for exactly this reason; this is
2002/// that refusal moved to the door the client knocks on, where it can still be told which parameter
2003/// was wrong instead of receiving codes and discovering the omission at the resource server.
2004///
2005/// Checked BEFORE the credential and before the grant, like the DPoP proof this router refuses on
2006/// the same grant: a client asking for something this server cannot do is a wiring mistake, and
2007/// the refusal is only useful if it names the parameter rather than whatever was checked first.
2008fn refuse_authorization_details(pairs: &[Pair<'_>]) -> Option<ErrorResponse> {
2009 param(pairs, "authorization_details").map(|_| {
2010 // The VALUE is never echoed (RFC 6749 s5.2 restricts the charset, and this one is
2011 // attacker-supplied JSON); naming the parameter is what the developer who sent it needs.
2012 ErrorResponse::new(ErrorCode::InvalidAuthorizationDetails)
2013 .with_description("this server does not accept authorization_details on this grant")
2014 })
2015}
2016
2017/// Parse an optional `scope` parameter. A malformed scope is `invalid_scope` (RFC 6749 s5.2)
2018/// rather than `invalid_request`: the parameter was supplied, it is its VALUE that is not a
2019/// scope.
2020fn optional_scope(pairs: &[Pair<'_>]) -> Result<Option<ScopeSet>, ErrorResponse> {
2021 match param(pairs, "scope") {
2022 None => Ok(None),
2023 Some(s) => ScopeSet::parse(s).map(Some).map_err(|_| {
2024 ErrorResponse::new(ErrorCode::InvalidScope)
2025 .with_description("scope is not a space-delimited RFC 6749 s3.3 token list")
2026 }),
2027 }
2028}
2029
2030// ---------------------------------------------------------------------------------------------
2031// Client authentication (RFC 6749 s2.3)
2032// ---------------------------------------------------------------------------------------------
2033
2034/// Authenticated (or merely identified) client credentials from one request.
2035///
2036/// DELIBERATELY NOT `Debug`, and not by omission. Every field but `client_id` is a live credential
2037/// decoded straight off the wire (a shared secret, or an RFC 7523 assertion that is a bearer
2038/// credential for as long as it is unexpired), and this value is in scope in five handlers. A
2039/// derived `Debug` would put all of it verbatim into a host's logs the first time somebody wrote
2040/// `tracing::debug!(?creds)`. A redacting `Debug` would make that line compile and print something
2041/// safe; no `Debug` at all makes it fail to compile, which is the stronger guarantee and costs
2042/// nothing, because nothing in this crate prints this type.
2043struct Credentials {
2044 client_id: String,
2045 /// `None` for a public client, which has no secret to present.
2046 client_secret: Option<String>,
2047 /// RFC 7521 s4.2 `client_assertion_type`, verbatim.
2048 #[cfg(feature = "client-assertion")]
2049 client_assertion_type: Option<String>,
2050 /// RFC 7523 `client-assertion`, verbatim.
2051 #[cfg(feature = "client-assertion")]
2052 client_assertion: Option<String>,
2053}
2054
2055impl Credentials {
2056 /// The borrowed form the server takes. Borrowed rather than owned so that reading a credential
2057 /// off the wire costs the same as it did before these two parameters existed.
2058 fn credential(&self) -> crate::server::ClientCredential<'_> {
2059 crate::server::ClientCredential {
2060 client_secret: self.client_secret.as_deref(),
2061 #[cfg(feature = "client-assertion")]
2062 client_assertion_type: self.client_assertion_type.as_deref(),
2063 #[cfg(feature = "client-assertion")]
2064 client_assertion: self.client_assertion.as_deref(),
2065 // ALWAYS `None`, and it has to be. This router is handed a parsed request; it
2066 // does not terminate TLS and never sees the connection, so there is no
2067 // certificate here that anybody verified. RFC 8705 clients reach the server
2068 // through `ClientCredential::certificate` from a host that DID terminate the
2069 // connection. Reading one out of a proxy header here would be the exact
2070 // mistake `crate::mtls`'s trust boundary section warns about, and it would be
2071 // made on every deployment's behalf rather than on the one host that knows
2072 // whether its terminator can be trusted.
2073 #[cfg(feature = "mtls")]
2074 certificate: None,
2075 }
2076 }
2077}
2078
2079/// Whether the request offered HTTP Basic credentials at all. Decided before any parsing, because
2080/// it is what selects 401-with-challenge over 400 even when the parsing then fails.
2081fn basic_attempted(headers: &HeaderMap) -> bool {
2082 headers
2083 .get(header::AUTHORIZATION)
2084 .and_then(|v| v.to_str().ok())
2085 .is_some_and(|v| v.len() >= 6 && v[..6].eq_ignore_ascii_case("basic "))
2086}
2087
2088/// Decode `Authorization: Basic ...` into `(client_id, client_secret)`.
2089///
2090/// RFC 6749 s2.3.1 is specific and frequently got wrong: the client identifier and password are
2091/// each form-urlencoded FIRST, then joined with a colon and base64ed. A server that skips the
2092/// decode silently rejects every client whose secret contains a character the encoding escapes.
2093fn decode_basic(headers: &HeaderMap) -> Result<(String, String), ErrorResponse> {
2094 let malformed = || {
2095 ErrorResponse::new(ErrorCode::InvalidClient)
2096 .with_description("malformed HTTP Basic credentials (RFC 6749 s2.3.1)")
2097 };
2098 let raw = headers
2099 .get(header::AUTHORIZATION)
2100 .and_then(|v| v.to_str().ok())
2101 .ok_or_else(malformed)?;
2102 let encoded = raw.get(6..).ok_or_else(malformed)?.trim();
2103 let decoded = BASE64_STANDARD.decode(encoded).map_err(|_| malformed())?;
2104 let text = String::from_utf8(decoded).map_err(|_| malformed())?;
2105 // Split on the FIRST colon: RFC 7617 says the userid cannot contain one, so any later colon
2106 // belongs to the password.
2107 let (id, secret) = text.split_once(':').ok_or_else(malformed)?;
2108 Ok((
2109 decode_component(id).into_owned(),
2110 decode_component(secret).into_owned(),
2111 ))
2112}
2113
2114/// Resolve the client from the request, across all three methods this server advertises in
2115/// `token_endpoint_auth_methods_supported`.
2116///
2117/// RFC 6749 s2.3: "The client MUST NOT use more than one authentication method in each request."
2118/// Presenting Basic credentials AND body credentials is therefore refused outright rather than
2119/// silently resolved by precedence: a server that picks one is a server whose behaviour differs
2120/// from the next server's, and the ambiguity is exactly what a request-smuggling intermediary
2121/// would exploit.
2122fn credentials(headers: &HeaderMap, form: &[Pair<'_>]) -> Result<Credentials, ErrorResponse> {
2123 credentials_where(headers, form, false)
2124}
2125
2126/// [`credentials`] for the RFC 9126 PAR endpoint, where a form `client_id` alongside header
2127/// credentials is NOT a second authentication method.
2128///
2129/// This is the one endpoint where the distinction bites. RFC 9126 s2.1 says the pushed body
2130/// carries the authorization request parameters of RFC 6749 s4.1.1, in which `client_id` is
2131/// REQUIRED, AND that the client authenticates as it does at the token endpoint. A client using
2132/// `client_secret_basic` therefore MUST send both, and this crate's token-endpoint rule (any
2133/// `client_id` in the body alongside Basic is two methods) would make PAR unusable for every
2134/// confidential client that authenticates with a header. The RFC settles it: there the parameter
2135/// is a REQUEST parameter that happens to name the same client, and it carries no credential, so
2136/// it cannot be a second authentication method.
2137///
2138/// Nothing is loosened about actual credentials: a `client_secret` or an assertion alongside Basic
2139/// is still two methods and still refused. And the pushed `client_id` is not trusted either, it is
2140/// checked against the AUTHENTICATED client inside
2141/// [`AuthorizationServer::pushed_authorization_request`], which is RFC 9126 s2.1's own rule and
2142/// what stops a client lodging a request under a victim's identity.
2143#[cfg(feature = "par")]
2144fn pushed_request_credentials(
2145 headers: &HeaderMap,
2146 form: &[Pair<'_>],
2147) -> Result<Credentials, ErrorResponse> {
2148 credentials_where(headers, form, true)
2149}
2150
2151/// The shared body of the two above. `client_id_is_a_request_parameter` is the only difference.
2152fn credentials_where(
2153 headers: &HeaderMap,
2154 form: &[Pair<'_>],
2155 client_id_is_a_request_parameter: bool,
2156) -> Result<Credentials, ErrorResponse> {
2157 let basic = basic_attempted(headers);
2158 let body_id = param(form, "client_id");
2159 let body_secret = param(form, "client_secret");
2160
2161 // RFC 7523 s2.2 / RFC 7521 s4.2. Handled BEFORE the three older methods, because an assertion
2162 // is a complete client authentication on its own and s2.2 makes `client_id` OPTIONAL alongside
2163 // it: the assertion already names the client, so requiring the parameter would refuse a
2164 // conforming client over a redundancy.
2165 #[cfg(feature = "client-assertion")]
2166 if let Some(assertion) = param(form, "client_assertion") {
2167 // RFC 6749 s2.3: one authentication method per request. Basic credentials or a
2168 // `client_secret` alongside an assertion is two, and a server that resolves the ambiguity
2169 // by precedence is a server whose behaviour differs from the next one's.
2170 if basic || body_secret.is_some() {
2171 return Err(ErrorResponse::new(ErrorCode::InvalidRequest)
2172 .with_description("more than one client authentication method (RFC 6749 s2.3)"));
2173 }
2174 // UNVERIFIED, and only used to LOCATE the registration. The registration then decides the
2175 // algorithm and the key, and `verify_assertion` re-checks `iss`/`sub` against the client id
2176 // resolved here, so nothing is trusted on the strength of this read. A form `client_id`
2177 // wins when present, because that is the value the client explicitly asserted.
2178 let client_id = match body_id {
2179 Some(id) => id.to_string(),
2180 None => crate::client_assertion::unverified_subject(assertion)
2181 .ok_or_else(|| {
2182 ErrorResponse::new(ErrorCode::InvalidClient)
2183 .with_description("the client assertion names no client")
2184 })?
2185 .to_string(),
2186 };
2187 return Ok(Credentials {
2188 client_id,
2189 client_secret: None,
2190 client_assertion_type: param(form, "client_assertion_type").map(str::to_string),
2191 client_assertion: Some(assertion.to_string()),
2192 });
2193 }
2194
2195 match (basic, body_id, body_secret) {
2196 // Header credentials, and no credential in the body. The `client_id` that may sit
2197 // alongside them is IGNORED for authentication: whether it may be there at all was decided
2198 // by the caller, and where it may, the endpoint checks it against the authenticated client
2199 // itself rather than letting it select one.
2200 (true, None, None) | (true, Some(_), None) if client_id_is_a_request_parameter => {
2201 let (client_id, client_secret) = decode_basic(headers)?;
2202 Ok(Credentials {
2203 client_id,
2204 client_secret: Some(client_secret),
2205 #[cfg(feature = "client-assertion")]
2206 client_assertion_type: None,
2207 #[cfg(feature = "client-assertion")]
2208 client_assertion: None,
2209 })
2210 }
2211 (true, None, None) => {
2212 let (client_id, client_secret) = decode_basic(headers)?;
2213 Ok(Credentials {
2214 client_id,
2215 client_secret: Some(client_secret),
2216 #[cfg(feature = "client-assertion")]
2217 client_assertion_type: None,
2218 #[cfg(feature = "client-assertion")]
2219 client_assertion: None,
2220 })
2221 }
2222 (true, _, _) => Err(ErrorResponse::new(ErrorCode::InvalidRequest)
2223 .with_description("more than one client authentication method (RFC 6749 s2.3)")),
2224 // `client_secret_post`, and the bare `client_id` a public client sends (RFC 6749 s3.2.1:
2225 // a client that is not authenticating still identifies itself).
2226 (false, Some(id), secret) => Ok(Credentials {
2227 client_id: id.to_string(),
2228 client_secret: secret.map(str::to_string),
2229 #[cfg(feature = "client-assertion")]
2230 client_assertion_type: None,
2231 #[cfg(feature = "client-assertion")]
2232 client_assertion: None,
2233 }),
2234 // RFC 6749 s5.2 names this case explicitly under `invalid_client`: "no client
2235 // authentication included".
2236 (false, None, _) => Err(ErrorResponse::new(ErrorCode::InvalidClient)
2237 .with_description("no client authentication or client_id")),
2238 }
2239}
2240
2241// ---------------------------------------------------------------------------------------------
2242// Handlers
2243// ---------------------------------------------------------------------------------------------
2244
2245/// RFC 8414 s3.1. Served from the bytes produced when the router was built.
2246fn metadata_handler<S: Storage, C: Clock>(state: &Inner<S, C>) -> Response {
2247 let mut resp = respond(StatusCode::OK, state.metadata.clone());
2248 resp.headers_mut()
2249 .insert(header::CONTENT_TYPE, json_content_type());
2250 resp
2251}
2252
2253/// RFC 7517 s5: the JWK Set a resource server fetches to verify the RFC 9068 access tokens this
2254/// server signs. Served from the bytes produced when the router was built.
2255///
2256/// PUBLIC key parameters only, and no `Cache-Control: no-store`: unlike the token plane this body
2257/// carries no credential, and a key set that may not be cached would be re-fetched by every
2258/// verifier on every token, which is how rotation-capable deployments fall over.
2259#[cfg(feature = "jwt")]
2260fn jwks_handler<S: Storage, C: Clock>(state: &Inner<S, C>) -> Response {
2261 match &state.jwks {
2262 Some(bytes) => {
2263 let mut resp = respond(StatusCode::OK, bytes.clone());
2264 resp.headers_mut()
2265 .insert(header::CONTENT_TYPE, jwks_content_type());
2266 resp
2267 }
2268 // Unreachable: `build` routes this path only when it has the bytes.
2269 None => respond(StatusCode::NOT_FOUND, Body::empty()),
2270 }
2271}
2272
2273/// RFC 6749 s3.2, plus RFC 8628 s3.4 for the device grant.
2274async fn token_handler<S: Storage, C: Clock>(
2275 state: &Inner<S, C>,
2276 headers: &HeaderMap,
2277 body: &Bytes,
2278) -> Response {
2279 let via_header = basic_attempted(headers);
2280 let text = String::from_utf8_lossy(body);
2281 let form = match parse_pairs(&text) {
2282 Ok(form) => form,
2283 Err(TooManyParameters) => return too_many_parameters(),
2284 };
2285
2286 // grant_type is resolved BEFORE client authentication so that a request naming a grant this
2287 // server does not implement gets `unsupported_grant_type` rather than a client-auth error
2288 // about a parameter it never reached.
2289 let grant = match param(&form, "grant_type") {
2290 None => {
2291 return error_response(
2292 &ErrorResponse::new(ErrorCode::InvalidRequest)
2293 .with_description("missing required parameter grant_type"),
2294 via_header,
2295 &state.challenge,
2296 )
2297 }
2298 // `GrantType::parse` and NOT `value.parse::<GrantType>()`: `FromStr`'s error carries an
2299 // owned copy of the caller's value, and the arm below discards it unread. That copy was
2300 // sized by an unauthenticated caller (one form parameter can be nearly the whole 64 KiB
2301 // body), which made a refusal cost the server a 60 KiB malloc and memcpy at whatever rate
2302 // the caller could open sockets. See `tests/refusal_cost.rs`.
2303 Some(value) => match GrantType::parse(value) {
2304 Some(g) => g,
2305 // The value is NOT echoed. RFC 6749 s5.2 restricts error_description to a charset
2306 // that excludes the double quote and backslash, and an attacker controls this string;
2307 // saying which grant was asked for is not worth having to sanitize it.
2308 None => {
2309 return error_response(
2310 &ErrorResponse::new(ErrorCode::UnsupportedGrantType)
2311 .with_description("this server does not implement the requested grant"),
2312 via_header,
2313 &state.challenge,
2314 )
2315 }
2316 },
2317 };
2318
2319 let mut creds = match credentials(headers, &form) {
2320 Ok(c) => c,
2321 Err(e) => return error_response(&e, via_header, &state.challenge),
2322 };
2323 // TAKEN, not cloned. `creds` has to outlive this because `creds.credential()` borrows the
2324 // secret fields below, but nothing reads `client_id` off it again, so moving the String out
2325 // and leaving an empty one behind saves an allocation on every request to this endpoint.
2326 let client_id = ClientId::new(std::mem::take(&mut creds.client_id));
2327 // NOT moved onto the TokenRequest variant any more: every credential this endpoint accepts now
2328 // travels together on the request CONTEXT, so there is one place a reader has to look to see
2329 // what the client presented, rather than one for secrets and another for everything else.
2330 let client_secret: Option<String> = None;
2331
2332 // RFC 9449 s4.3 (1): there must be exactly ONE `DPoP` header. Several is not a request this
2333 // server may pick a favourite from: an intermediary that appended one, or a client that sent
2334 // two, leaves it ambiguous which proof the client meant to bind the token to.
2335 //
2336 // RESOLVED HERE, before the grant is dispatched, and that placement is the fix for a silent
2337 // downgrade rather than tidiness. The RFC 8693 arm below RETURNS, so while this block sat
2338 // after the dispatch a `DPoP` header sent with `grant_type=token-exchange` was never read at
2339 // all: no duplicate check, no proof verification, and an issued token with no `jkt`. A client
2340 // that asked for a sender-constrained token got a bearer token and no way to find out.
2341 #[cfg(feature = "dpop")]
2342 let dpop_proof = {
2343 let mut values = headers.get_all(crate::dpop::DPOP_HEADER).iter();
2344 let first = values.next();
2345 if values.next().is_some() {
2346 // EMITTED like every other proof refusal. These two are refused HERE rather than in
2347 // `verify_proof`, which is the only reason they were silent through 0.9.0: a
2348 // deployment reading `DpopProofRefused` to tell its failure modes apart would have
2349 // seen nothing whatever for a client sending two headers, which is a client bug the
2350 // operator is the only one who can report back.
2351 state
2352 .server
2353 .hooks()
2354 .emit(|| crate::events::Event::DpopProofRefused {
2355 failure: crate::dpop::DpopFailure::Malformed,
2356 });
2357 return error_response(
2358 &ErrorResponse::new(ErrorCode::InvalidDpopProof)
2359 .with_description("more than one DPoP header (RFC 9449 s4.3)"),
2360 via_header,
2361 &state.challenge,
2362 );
2363 }
2364 match first.map(|v| v.to_str()) {
2365 None => None,
2366 Some(Ok(value)) => Some(value),
2367 // A header that is not visible ASCII cannot be a compact JWS, so this is a malformed
2368 // proof rather than an absent one, and answering "absent" would silently downgrade a
2369 // client that asked for a bound token to a bearer one.
2370 Some(Err(_)) => {
2371 state
2372 .server
2373 .hooks()
2374 .emit(|| crate::events::Event::DpopProofRefused {
2375 failure: crate::dpop::DpopFailure::Malformed,
2376 });
2377 return error_response(
2378 &ErrorResponse::new(ErrorCode::InvalidDpopProof)
2379 .with_description("the DPoP header is not a compact JWS"),
2380 via_header,
2381 &state.challenge,
2382 );
2383 }
2384 }
2385 };
2386
2387 let request = match grant {
2388 GrantType::AuthorizationCode => {
2389 let code = match required(&form, "code") {
2390 Ok(v) => v.to_string(),
2391 Err(e) => return error_response(&e, via_header, &state.challenge),
2392 };
2393 TokenRequest::AuthorizationCode {
2394 client_id,
2395 client_secret,
2396 code,
2397 redirect_uri: param(&form, "redirect_uri").map(str::to_string),
2398 code_verifier: param(&form, "code_verifier").map(str::to_string),
2399 }
2400 }
2401 GrantType::ClientCredentials => {
2402 let scope = match optional_scope(&form) {
2403 Ok(s) => s,
2404 Err(e) => return error_response(&e, via_header, &state.challenge),
2405 };
2406 TokenRequest::ClientCredentials {
2407 client_id,
2408 client_secret,
2409 scope,
2410 }
2411 }
2412 GrantType::DeviceCode => {
2413 let device_code = match required(&form, "device_code") {
2414 Ok(v) => v.to_string(),
2415 Err(e) => return error_response(&e, via_header, &state.challenge),
2416 };
2417 TokenRequest::DeviceCode {
2418 client_id,
2419 client_secret,
2420 device_code,
2421 }
2422 }
2423 GrantType::RefreshToken => {
2424 let refresh_token = match required(&form, "refresh_token") {
2425 Ok(v) => v.to_string(),
2426 Err(e) => return error_response(&e, via_header, &state.challenge),
2427 };
2428 let scope = match optional_scope(&form) {
2429 Ok(s) => s,
2430 Err(e) => return error_response(&e, via_header, &state.challenge),
2431 };
2432 TokenRequest::RefreshToken {
2433 client_id,
2434 client_secret,
2435 refresh_token,
2436 scope,
2437 }
2438 }
2439 // RFC 8693 s2 shares the token endpoint with the RFC 6749 grants but NOT their
2440 // response body (s2.2.1 adds a REQUIRED member), so it answers from here rather
2441 // than producing a `TokenRequest`. Serving it matters beyond convenience: the RFC
2442 // 8414 document advertises the grant when this feature is on, and an advertised
2443 // grant this router refused would be exactly the lie that document exists to
2444 // avoid.
2445 #[cfg(feature = "token-exchange")]
2446 GrantType::TokenExchange => {
2447 // RFC 9449: this grant CANNOT bind the token it issues, so a presented proof is
2448 // REFUSED rather than ignored. `TokenExchangeRequest` carries no proof and
2449 // `crate::token_exchange` has nowhere to record a `jkt`, so honouring the header would
2450 // mean issuing an unbound token to a client that asked for a bound one and telling it
2451 // nothing: the silent downgrade that module argues against at length for the SUBJECT
2452 // token, applied to the ISSUED token by the same module. Loud beats quiet, and the
2453 // refusal is what an operator who turned DPoP on can actually see.
2454 #[cfg(feature = "dpop")]
2455 if dpop_proof.is_some() {
2456 // And it is REPORTED, for the same reason it is refused loudly: a client asking
2457 // this server for something it cannot do is a wiring mistake, and the operator who
2458 // turned DPoP on is the only party who can tell the client's author.
2459 //
2460 // `NotAcceptedHere`, NOT `Malformed`, which is what this said until the 0.9.1
2461 // audit: the proof has not been parsed at this point and is probably a perfectly
2462 // good JWS. Reporting it as malformed described the client's string instead of
2463 // this server's capability, and pointed the one person who could fix it at the one
2464 // person who could not.
2465 state
2466 .server
2467 .hooks()
2468 .emit(|| crate::events::Event::DpopProofRefused {
2469 failure: crate::dpop::DpopFailure::NotAcceptedHere,
2470 });
2471 return error_response(
2472 &ErrorResponse::new(ErrorCode::InvalidDpopProof).with_description(
2473 "this server does not issue sender-constrained tokens through RFC 8693 \
2474 token exchange",
2475 ),
2476 via_header,
2477 &state.challenge,
2478 );
2479 }
2480 // The WHOLE credential, not the secret alone. RFC 8693 s2.1 authenticates the client
2481 // exactly as the other grants do, and `TokenExchange::exchange_token` REFUSES a client
2482 // that is not confidential: forwarding only `client_secret` first made every exchange
2483 // `invalid_client` (the `None` the other arms carry), and then, once the secret was
2484 // restored, still refused every client registered for `private_key_jwt` or
2485 // `client_secret_jwt`, whose credential arrives in `client-assertion`. Half a repair
2486 // is what left the second half invisible.
2487 return token_exchange_response(state, &form, client_id, &creds, via_header).await;
2488 }
2489 };
2490
2491 // RFC 8707 s2: `resource` is a parameter of the token request itself, independent of
2492 // `grant_type`, so it is collected once here rather than inside each arm above.
2493 let resources = resource_indicators(&form);
2494
2495 let context = crate::server::TokenRequestContext {
2496 credential: creds.credential(),
2497 resources: &resources,
2498 // RFC 9396 s2 makes this ONE JSON array, so `param`'s first-wins rule is the right
2499 // one here and a duplicate is a smuggled parameter rather than a second value.
2500 // That is the opposite of `resource`, which s2 of RFC 8707 explicitly allows to
2501 // repeat, and the difference is why the two are read differently.
2502 //
2503 // READ IN EVERY BUILD, not only under `rar`: a build that supports no authorization
2504 // detail type has to refuse the parameter (RFC 9396 s5), and a router that never read it
2505 // off the form left the endpoint nothing to refuse. See `TokenRequestContext`.
2506 authorization_details: param(&form, "authorization_details"),
2507 #[cfg(feature = "dpop")]
2508 dpop_proof,
2509 };
2510 match state.server.token_with_context(request, context).await {
2511 Ok(response) => ok_json(&response),
2512 Err(e) => error_response(&e, via_header, &state.challenge),
2513 }
2514}
2515
2516/// RFC 8693 s2: the token exchange grant.
2517///
2518/// The router serves the WIRE response only. A host that needs to know whether the exchange
2519/// was delegation or impersonation (s1.1), or that needs the s4.1 `act` claim to put into a
2520/// token of its own, calls [`crate::token_exchange::TokenExchange::exchange_token`] directly:
2521/// neither is a response parameter RFC 8693 defines, so neither belongs in this body.
2522#[cfg(feature = "token-exchange")]
2523async fn token_exchange_response<S: Storage, C: Clock>(
2524 state: &Inner<S, C>,
2525 form: &[Pair<'_>],
2526 client_id: ClientId,
2527 creds: &Credentials,
2528 via_header: bool,
2529) -> Response {
2530 // The three refusals below, written out rather than built from the parameter's name. There
2531 // are exactly three call sites and the name is a constant at each of them, so `format!` was
2532 // copying one of three fixed sentences onto the heap per refused request; this refusal happens
2533 // BEFORE the exchange is attempted, so before the presented client credential has been
2534 // checked, which makes its rate the caller's to choose.
2535 const SUBJECT_NOT_A_TOKEN_TYPE: &str =
2536 "subject_token_type is not a token type RFC 8693 s3 registers";
2537 const ACTOR_NOT_A_TOKEN_TYPE: &str =
2538 "actor_token_type is not a token type RFC 8693 s3 registers";
2539 const REQUESTED_NOT_A_TOKEN_TYPE: &str =
2540 "requested_token_type is not a token type RFC 8693 s3 registers";
2541
2542 fn token_type(
2543 refusal: &'static str,
2544 value: &str,
2545 ) -> Result<crate::token_exchange::TokenTypeIdentifier, ErrorResponse> {
2546 // The VALUE is not echoed, for the reason `grant_type` is not echoed above: RFC
2547 // 6749 s5.2 restricts error_description to a charset an attacker-supplied URN need
2548 // not respect, and naming the parameter is enough for the developer who sent it.
2549 //
2550 // And because it is not echoed, the parse must not COPY it either: `FromStr` builds an
2551 // `UnknownTokenTypeIdentifier` holding an owned, caller-sized `String` that this line then
2552 // throws away. `TokenTypeIdentifier::parse` is the same match with no payload.
2553 crate::token_exchange::TokenTypeIdentifier::parse(value)
2554 .ok_or_else(|| ErrorResponse::new(ErrorCode::InvalidRequest).with_description(refusal))
2555 }
2556
2557 // RFC 9396 s5, before anything else is parsed. This grant issues from the SUBJECT token, so
2558 // `TokenExchangeRequest` has no member an authorization detail could travel in and the
2559 // parameter would die here silently; the token endpoint's shared handling, which refuses it,
2560 // is downstream of the arm that called this function and never runs for this grant. See
2561 // `refuse_authorization_details`.
2562 if let Some(refusal) = refuse_authorization_details(form) {
2563 return error_response(&refusal, via_header, &state.challenge);
2564 }
2565
2566 let subject_token = match required(form, "subject_token") {
2567 Ok(v) => v,
2568 Err(e) => return error_response(&e, via_header, &state.challenge),
2569 };
2570 let subject_token_type = match required(form, "subject_token_type")
2571 .and_then(|v| token_type(SUBJECT_NOT_A_TOKEN_TYPE, v))
2572 {
2573 Ok(v) => v,
2574 Err(e) => return error_response(&e, via_header, &state.challenge),
2575 };
2576 let actor_token = param(form, "actor_token");
2577 let actor_token_type = match param(form, "actor_token_type")
2578 .map(|v| token_type(ACTOR_NOT_A_TOKEN_TYPE, v))
2579 .transpose()
2580 {
2581 Ok(v) => v,
2582 Err(e) => return error_response(&e, via_header, &state.challenge),
2583 };
2584 let requested_token_type = match param(form, "requested_token_type")
2585 .map(|v| token_type(REQUESTED_NOT_A_TOKEN_TYPE, v))
2586 .transpose()
2587 {
2588 Ok(v) => v,
2589 Err(e) => return error_response(&e, via_header, &state.challenge),
2590 };
2591 let scope = match optional_scope(form) {
2592 Ok(s) => s,
2593 Err(e) => return error_response(&e, via_header, &state.challenge),
2594 };
2595 // Both target parameters may repeat (RFC 8693 s2.1, RFC 8707 s2), so neither may go
2596 // through `param`'s first-wins rule: dropping the second occurrence would silently
2597 // narrow the request to half of what the client asked for.
2598 let resource = resource_indicators(form);
2599 let audience: Vec<String> = form
2600 .iter()
2601 .filter(|(k, _)| k == "audience")
2602 .map(|(_, v)| v.as_ref().to_string())
2603 .collect();
2604
2605 let request = crate::token_exchange::TokenExchangeRequest {
2606 client_id: &client_id,
2607 client_secret: creds.client_secret.as_deref(),
2608 // RFC 7521 s4.2 / RFC 7523 s2.2, forwarded rather than dropped: without these two an
2609 // assertion-authenticated confidential client cannot use this grant at all, and this is
2610 // the endpoint that advertises it.
2611 #[cfg(feature = "client-assertion")]
2612 client_assertion_type: creds.client_assertion_type.as_deref(),
2613 #[cfg(feature = "client-assertion")]
2614 client_assertion: creds.client_assertion.as_deref(),
2615 subject_token,
2616 subject_token_type,
2617 actor_token,
2618 actor_token_type,
2619 resource: &resource,
2620 audience: &audience,
2621 scope: scope.as_ref(),
2622 requested_token_type,
2623 };
2624 match crate::token_exchange::TokenExchange::exchange_token(&*state.server, &request).await {
2625 Ok(exchanged) => ok_json(&exchanged.response),
2626 Err(e) => error_response(&e, via_header, &state.challenge),
2627 }
2628}
2629
2630/// RFC 8628 s3.1: the device authorization request.
2631async fn device_authorization_handler<S: Storage, C: Clock>(
2632 state: &Inner<S, C>,
2633 headers: &HeaderMap,
2634 body: &Bytes,
2635) -> Response {
2636 let via_header = basic_attempted(headers);
2637 let text = String::from_utf8_lossy(body);
2638 let form = match parse_pairs(&text) {
2639 Ok(form) => form,
2640 Err(TooManyParameters) => return too_many_parameters(),
2641 };
2642
2643 // RFC 9396 s3 names the device authorization request as a place this parameter may be used,
2644 // and s5 requires refusing one this server will not honour. See
2645 // `refuse_authorization_details`: a `DeviceGrant` has no field for a detail, so accepting one
2646 // here mints a user code for a permission that can never reach the token.
2647 if let Some(refusal) = refuse_authorization_details(&form) {
2648 return error_response(&refusal, via_header, &state.challenge);
2649 }
2650
2651 let mut creds = match credentials(headers, &form) {
2652 Ok(c) => c,
2653 Err(e) => return error_response(&e, via_header, &state.challenge),
2654 };
2655 // TAKEN rather than cloned, for the reason the token endpoint gives: `creds` must
2656 // outlive this call because `creds.credential()` borrows out of it, but its
2657 // `client_id` is never read again.
2658 let client_id = ClientId::new(std::mem::take(&mut creds.client_id));
2659 let scope = match optional_scope(&form) {
2660 Ok(s) => s,
2661 Err(e) => return error_response(&e, via_header, &state.challenge),
2662 };
2663 match state
2664 .server
2665 .device_authorization_with_credential(&client_id, &creds.credential(), scope.as_ref())
2666 .await
2667 {
2668 Ok(response) => ok_json(&response),
2669 Err(e) => error_response(&e, via_header, &state.challenge),
2670 }
2671}
2672
2673/// RFC 9126 s2: the pushed authorization request endpoint.
2674///
2675/// It is on the TOKEN plane, not the authorization plane, and everything about this handler
2676/// follows from that: the client authenticates here exactly as it does at the token endpoint
2677/// (s2.1 step 1), so client authentication is resolved by the same [`credentials`] function and a
2678/// failure gets the same RFC 6749 s5.2 shape with the same 401-versus-400 rule; and the response
2679/// carries a capability handle, so it gets the same s5.1 caching directives. The one thing that
2680/// differs is the success status, which s2.2 states rather than suggests: 201, not 200.
2681#[cfg(feature = "par")]
2682async fn pushed_authorization_handler<S: Storage, C: Clock>(
2683 state: &Inner<S, C>,
2684 headers: &HeaderMap,
2685 body: &Bytes,
2686) -> Response {
2687 let via_header = basic_attempted(headers);
2688 let text = String::from_utf8_lossy(body);
2689 let form = match parse_pairs(&text) {
2690 Ok(form) => form,
2691 Err(TooManyParameters) => return too_many_parameters(),
2692 };
2693
2694 let mut creds = match pushed_request_credentials(headers, &form) {
2695 Ok(c) => c,
2696 Err(e) => return error_response(&e, via_header, &state.challenge),
2697 };
2698 // TAKEN rather than cloned, for the reason the token endpoint gives: `creds` must
2699 // outlive this call because `creds.credential()` borrows out of it, but its
2700 // `client_id` is never read again.
2701 let client_id = ClientId::new(std::mem::take(&mut creds.client_id));
2702 // The form EXACTLY as it arrived, with nothing filtered out. RFC 9126 s2.1 step 2 REFUSES a
2703 // pushed `request_uri` and s3 treats a `request` as a signed request object, and both of those
2704 // are decisions for the server: a router that quietly dropped either parameter would turn a
2705 // refusal the RFC requires into a silent acceptance of a different request. Borrowed out of
2706 // the parsed form, so passing it costs no allocation per parameter.
2707 let parameters: Vec<(&str, &str)> =
2708 form.iter().map(|(k, v)| (k.as_ref(), v.as_ref())).collect();
2709 match state
2710 .server
2711 .pushed_authorization_request_with_credential(&client_id, &creds.credential(), ¶meters)
2712 .await
2713 {
2714 Ok(response) => {
2715 // s2.2: "with a 201 HTTP status code". Taken from the response type rather than
2716 // written here twice, so the wire status and the type's own answer cannot drift.
2717 let status =
2718 StatusCode::from_u16(response.http_status()).unwrap_or(StatusCode::CREATED);
2719 let mut resp = respond(status, json_body(&response));
2720 let h = resp.headers_mut();
2721 h.insert(header::CONTENT_TYPE, json_content_type());
2722 no_store(h);
2723 resp
2724 }
2725 Err(e) => error_response(&e, via_header, &state.challenge),
2726 }
2727}
2728
2729/// RFC 7662 s2.1: token introspection, for a caller that authenticates as a client.
2730async fn introspect_handler<S: Storage, C: Clock>(
2731 state: &Inner<S, C>,
2732 headers: &HeaderMap,
2733 body: &Bytes,
2734) -> Response {
2735 let via_header = basic_attempted(headers);
2736 let text = String::from_utf8_lossy(body);
2737 let form = match parse_pairs(&text) {
2738 Ok(form) => form,
2739 Err(TooManyParameters) => return too_many_parameters(),
2740 };
2741
2742 let mut creds = match credentials(headers, &form) {
2743 Ok(c) => c,
2744 Err(e) => return error_response(&e, via_header, &state.challenge),
2745 };
2746 // TAKEN rather than cloned, for the reason the token endpoint gives: `creds` must
2747 // outlive this call because `creds.credential()` borrows out of it, but its
2748 // `client_id` is never read again.
2749 let client_id = ClientId::new(std::mem::take(&mut creds.client_id));
2750 let token = match required(&form, "token") {
2751 Ok(v) => v,
2752 Err(e) => return error_response(&e, via_header, &state.challenge),
2753 };
2754 match state
2755 .server
2756 .introspection_response_with_credential(&client_id, &creds.credential(), token)
2757 .await
2758 {
2759 Ok(response) => ok_json(&response),
2760 Err(e) => error_response(&e, via_header, &state.challenge),
2761 }
2762}
2763
2764/// RFC 7009 s2.1: token revocation. Success is a 200 with an empty body (s2.2).
2765async fn revoke_handler<S: Storage, C: Clock>(
2766 state: &Inner<S, C>,
2767 headers: &HeaderMap,
2768 body: &Bytes,
2769) -> Response {
2770 let via_header = basic_attempted(headers);
2771 let text = String::from_utf8_lossy(body);
2772 let form = match parse_pairs(&text) {
2773 Ok(form) => form,
2774 Err(TooManyParameters) => return too_many_parameters(),
2775 };
2776
2777 let mut creds = match credentials(headers, &form) {
2778 Ok(c) => c,
2779 Err(e) => return error_response(&e, via_header, &state.challenge),
2780 };
2781 // TAKEN rather than cloned, for the reason the token endpoint gives: `creds` must
2782 // outlive this call because `creds.credential()` borrows out of it, but its
2783 // `client_id` is never read again.
2784 let client_id = ClientId::new(std::mem::take(&mut creds.client_id));
2785 let token = match required(&form, "token") {
2786 Ok(v) => v,
2787 Err(e) => return error_response(&e, via_header, &state.challenge),
2788 };
2789 // An unrecognised hint is IGNORED rather than refused. RFC 7009 s2.1 requires the server to
2790 // keep looking when the hint is wrong, and s2.2.1's `unsupported_token_type` is for a token
2791 // type the server cannot revoke at all; this server revokes both types it issues, so there is
2792 // nothing here it is unable to do.
2793 let hint = param(&form, "token_type_hint").and_then(|h| h.parse::<TokenTypeHint>().ok());
2794
2795 // The WHOLE credential, exactly as the other three protected endpoints in this module do.
2796 // Forwarding only `client_secret` dropped every other way a client can authenticate: an RFC
2797 // 7523 assertion arrives in `client-assertion`, not in `client_secret`, so an
2798 // assertion-authenticated confidential client was refused `invalid_client` at this endpoint
2799 // and could never revoke anything through this service. Same defect, same cause and same
2800 // invisibility as the RFC 8693 one `tests/wire_reachability.rs` was written after: an arm not
2801 // updated when credentials moved onto the request context, with a revocation suite that only
2802 // ever drove the library API.
2803 match state
2804 .server
2805 .revoke_with_credential(&client_id, &creds.credential(), token, hint)
2806 .await
2807 {
2808 Ok(()) => {
2809 let mut resp = respond(StatusCode::OK, Body::empty());
2810 no_store(resp.headers_mut());
2811 resp
2812 }
2813 Err(e) => error_response(&e, via_header, &state.challenge),
2814 }
2815}
2816
2817// ---------------------------------------------------------------------------------------------
2818// RFC 7591 registration and RFC 7592 management
2819// ---------------------------------------------------------------------------------------------
2820
2821/// The RFC 6750 s2.1 bearer token from the `Authorization` header, if the request carried one.
2822///
2823/// Used for BOTH credentials this pair of RFCs defines: the RFC 7591 s1.2 initial access token and
2824/// the RFC 7592 s2 registration access token. Both are access tokens presented the same way, so
2825/// there is one parser rather than two that could disagree.
2826fn bearer_token(headers: &HeaderMap) -> Option<&str> {
2827 let raw = headers
2828 .get(header::AUTHORIZATION)
2829 .and_then(|v| v.to_str().ok())?;
2830 // Case-insensitive scheme (RFC 9110 s11.1), then the token68 with its surrounding space
2831 // trimmed. An empty remainder is `None`: a header that names the scheme and supplies nothing
2832 // presented no credential.
2833 if raw.len() < 7 || !raw[..7].eq_ignore_ascii_case("bearer ") {
2834 return None;
2835 }
2836 let token = raw[7..].trim();
2837 (!token.is_empty()).then_some(token)
2838}
2839
2840/// Turn an RFC 7591 s3.2.2 / RFC 7592 s2 refusal into a response.
2841///
2842/// A 401 carries an RFC 6750 s3 `Bearer` challenge rather than the `Basic` one the token plane
2843/// uses: this endpoint authenticates with a bearer token, and telling a client to retry with a
2844/// scheme it cannot use here would be worse than saying nothing. Only the `Invalid` case has a
2845/// body, because it is the only one with something a client can act on; a 401 that described what
2846/// was wrong with the token would be describing somebody else's credential.
2847fn registration_error(failure: &crate::registration::RegistrationFailure) -> Response {
2848 let status =
2849 StatusCode::from_u16(failure.http_status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
2850 // The `Content-Type` goes on the arm that HAS a body. An empty octet stream is not a valid
2851 // `application/json` document (RFC 8259 s2), and announcing it as one turns every RFC 7592
2852 // 401, 404 and 500 into a decode exception in the client rather than into the status it meant
2853 // to report: `response.json()` raises before anything can read `response.status`.
2854 let mut resp = match failure {
2855 crate::registration::RegistrationFailure::Invalid(body) => {
2856 let mut resp = respond(status, json_body(body));
2857 resp.headers_mut()
2858 .insert(header::CONTENT_TYPE, json_content_type());
2859 resp
2860 }
2861 _ => respond(status, Body::empty()),
2862 };
2863 let headers = resp.headers_mut();
2864 no_store(headers);
2865 if status == StatusCode::UNAUTHORIZED {
2866 headers.insert(header::WWW_AUTHENTICATE, HeaderValue::from_static("Bearer"));
2867 }
2868 resp
2869}
2870
2871/// Parse an RFC 7591 s2 metadata document out of a request body.
2872///
2873/// A body that is not JSON at all is `invalid_client_metadata`: s3.2.2 has no code for "your body
2874/// was not JSON", and the client's problem is genuinely that the metadata it submitted is not
2875/// metadata this server can read.
2876///
2877/// The error half is BOXED. An `axum::Response` is a large value (a status, a header map and a
2878/// body), and the `Ok` half here is the common one, so an unboxed `Result` would make every
2879/// successful parse carry the error variant's footprint on the stack. That is what
2880/// `clippy::result_large_err` is pointing at, and boxing is the fix it asks for rather than a
2881/// lint to silence: the allocation happens only on the refusal path, which is the path that is
2882/// about to write a response to a socket anyway.
2883fn client_metadata(body: &Bytes) -> Result<crate::registration::ClientMetadata, Box<Response>> {
2884 serde_json::from_slice(body).map_err(|_| {
2885 Box::new(registration_error(
2886 &crate::registration::RegistrationFailure::Invalid(
2887 crate::registration::RegistrationErrorResponse::new(
2888 crate::registration::RegistrationErrorCode::InvalidClientMetadata,
2889 "the request body is not an RFC 7591 s2 client metadata JSON object",
2890 ),
2891 ),
2892 ))
2893 })
2894}
2895
2896/// RFC 7591 s3.1: the client registration request. Success is a 201 (s3.2.1).
2897async fn register_handler<S: Storage, C: Clock>(
2898 state: &Inner<S, C>,
2899 headers: &HeaderMap,
2900 body: &Bytes,
2901) -> Response {
2902 // THROTTLE FIRST, on the one endpoint of this service where there may be no credential to
2903 // look at instead. RFC 7591 s3.1 registration may be ANONYMOUS, so the trick
2904 // `update_registration_handler` uses below — authenticate, then parse — has nothing to work
2905 // with here; what it does have is `Attempt::ClientRegistration`, which is keyed on nothing and
2906 // therefore answerable before a single byte of the body means anything. Parsing up to
2907 // `MAX_BODY_BYTES` of a stranger's JSON before asking the only gate this endpoint has is the
2908 // shape `MAX_FORM_PARAMETERS`'s own comment argues against: a refusal is work an attacker sets
2909 // the rate of.
2910 //
2911 // Unlike the management plane's arrangement, the check is NOT repeated inside the server
2912 // method: `admit_registration` and `register_admitted_client` are the two halves of
2913 // `register_dynamic_client` precisely so that one HTTP request is one charge. The registration
2914 // budget is global and small (60 per window by default), so a second charge would halve a
2915 // host's configured ceiling rather than cost it a rounding error.
2916 if let Err(e) = state.server.admit_registration() {
2917 return registration_error(&e);
2918 }
2919 let metadata = match client_metadata(body) {
2920 Ok(m) => m,
2921 Err(response) => return *response,
2922 };
2923 match state
2924 .server
2925 .register_admitted_client(&metadata, bearer_token(headers))
2926 .await
2927 {
2928 Ok(info) => {
2929 // s3.2.1: "201 Created", and the body carries a client secret and a registration
2930 // access token, so the s5.1 caching rules of RFC 6749 apply exactly as they do to a
2931 // token response.
2932 let mut resp = respond(StatusCode::CREATED, json_body(&info));
2933 let h = resp.headers_mut();
2934 h.insert(header::CONTENT_TYPE, json_content_type());
2935 no_store(h);
2936 resp
2937 }
2938 Err(e) => registration_error(&e),
2939 }
2940}
2941
2942/// RFC 7592 s2.1: read a registration.
2943async fn read_registration_handler<S: Storage, C: Clock>(
2944 state: &Inner<S, C>,
2945 headers: &HeaderMap,
2946 client_id: &str,
2947) -> Response {
2948 let token = bearer_token(headers).unwrap_or_default();
2949 match state
2950 .server
2951 .read_registration(&ClientId::new(client_id), token)
2952 .await
2953 {
2954 Ok(info) => ok_json(&info),
2955 Err(e) => registration_error(&e),
2956 }
2957}
2958
2959/// RFC 7592 s2.2: replace a registration's metadata.
2960async fn update_registration_handler<S: Storage, C: Clock>(
2961 state: &Inner<S, C>,
2962 headers: &HeaderMap,
2963 client_id: &str,
2964 body: &Bytes,
2965) -> Response {
2966 // AUTHENTICATE FIRST, and unlike `register_handler` this handler can afford to. RFC 7591 s3.1
2967 // registration may be anonymous, so there is nothing to check before the body there; RFC 7592
2968 // management is credentialed on every request, and parsing up to `MAX_BODY_BYTES` of a
2969 // stranger's JSON before looking at the credential is the shape `MAX_FORM_PARAMETERS`'s own
2970 // comment argues against: a refusal is work an attacker sets the rate of. The read and delete
2971 // handlers already touched nothing before the token; this one parsed first, and that asymmetry
2972 // was the whole of the defect.
2973 //
2974 // The check is repeated inside `update_registration`, which is deliberate: this one is a
2975 // cheaper refusal, not the authority. The cost of the repeat is one storage read and one hash
2976 // on the SUCCESS path of an endpoint a deployment uses rarely, against a full JSON parse an
2977 // anonymous caller could buy at whatever rate it liked.
2978 let token = bearer_token(headers).unwrap_or_default();
2979 if let Err(e) = state
2980 .server
2981 .authenticate_registration(&ClientId::new(client_id), token)
2982 .await
2983 {
2984 return registration_error(&e);
2985 }
2986 let metadata = match client_metadata(body) {
2987 Ok(m) => m,
2988 Err(response) => return *response,
2989 };
2990 match state
2991 .server
2992 .update_registration(&ClientId::new(client_id), token, &metadata)
2993 .await
2994 {
2995 Ok(info) => ok_json(&info),
2996 Err(e) => registration_error(&e),
2997 }
2998}
2999
3000/// RFC 7592 s2.3: delete a registration. Success is a 204 with no body.
3001async fn delete_registration_handler<S: Storage, C: Clock>(
3002 state: &Inner<S, C>,
3003 headers: &HeaderMap,
3004 client_id: &str,
3005) -> Response {
3006 let token = bearer_token(headers).unwrap_or_default();
3007 match state
3008 .server
3009 .delete_registration(&ClientId::new(client_id), token)
3010 .await
3011 {
3012 Ok(()) => {
3013 let mut resp = respond(StatusCode::NO_CONTENT, Body::empty());
3014 no_store(resp.headers_mut());
3015 resp
3016 }
3017 Err(e) => registration_error(&e),
3018 }
3019}
3020
3021/// Which of the ways an authorization request may arrive this one used, validated.
3022///
3023/// Three, and the two that are not query text exist because query text is the problem: it travels
3024/// through the browser, its history, its `Referer` headers and every proxy in front of it, and
3025/// anything able to rewrite the URL can change it before this server sees it.
3026///
3027/// - RFC 6749 s4.1.1, the parameters in the query.
3028/// - RFC 9126 s4, `client_id` plus a `request_uri` this server minted at its own PAR endpoint.
3029/// - RFC 9101 s5.1, `client_id` plus a signed `request` object.
3030///
3031/// For the latter two, EVERY other query parameter is ignored. That is not this function's choice
3032/// to make and it is not made here: RFC 9101 s6.3 (which RFC 9126 s4 builds on) requires the
3033/// server to use only the parameters carried by the reference or the object "even if the same
3034/// parameter is provided in the query parameter", and the two server methods enforce it by not
3035/// accepting any others, so there is no code path in which an appended `scope` could win.
3036///
3037/// With neither feature compiled in this is the plain query path and nothing else, which is what
3038/// the crate did before either feature existed.
3039async fn resolve_authorization_request<S: Storage, C: Clock>(
3040 state: &Inner<S, C>,
3041 pairs: &[Pair<'_>],
3042) -> Result<crate::authorization::ValidatedAuthorizationRequest, AuthorizationError> {
3043 #[cfg(any(feature = "par", feature = "jar"))]
3044 {
3045 // A `request_uri` this server cannot resolve (the `par` feature is off) is an UNKNOWN
3046 // parameter, and RFC 6749 s3.1 says an unknown parameter is ignored. That is safe here
3047 // only because a server without PAR compiled in never minted one, so there is no handle
3048 // for the ignoring to downgrade.
3049 #[cfg(feature = "par")]
3050 let by_reference = param(pairs, "request_uri");
3051 #[cfg(not(feature = "par"))]
3052 let by_reference: Option<&str> = None;
3053 #[cfg(feature = "jar")]
3054 let by_value = param(pairs, "request");
3055 #[cfg(not(feature = "jar"))]
3056 let by_value: Option<&str> = None;
3057
3058 // RFC 9101 s5: "The client MUST NOT send both". Refused rather than resolved by
3059 // precedence, for the reason RFC 6749 s2.3 gives about two client authentication methods:
3060 // a server that picks one behaves differently from the next server, and that difference is
3061 // what a smuggling intermediary exploits.
3062 if by_reference.is_some() && by_value.is_some() {
3063 return Err(AuthorizationError::Direct(
3064 ErrorResponse::new(ErrorCode::InvalidRequest).with_description(
3065 "request and request_uri must not both be sent (RFC 9101 s5)",
3066 ),
3067 ));
3068 }
3069
3070 if by_reference.is_some() || by_value.is_some() {
3071 // RFC 9126 s4 and RFC 9101 s5 both make `client_id` REQUIRED alongside the handle or
3072 // the object, and it is load bearing rather than decorative: it selects the pushed
3073 // record whose binding is then checked (s2.2, and s7.5 is the swapping attack), or the
3074 // registered key the signature is verified with. Resolved once here rather than in a
3075 // closure per branch, because a closure whose `Ok` is a `&str` and whose `Err` is a
3076 // 128 byte `AuthorizationError` is exactly what `clippy::result_large_err` objects to.
3077 let client_id = match param(pairs, "client_id") {
3078 Some(id) => id,
3079 None => {
3080 return Err(AuthorizationError::Direct(
3081 ErrorResponse::new(ErrorCode::InvalidRequest)
3082 .with_description("client_id is required (RFC 9126 s4, RFC 9101 s5)"),
3083 ))
3084 }
3085 };
3086
3087 #[cfg(feature = "par")]
3088 if let Some(request_uri) = by_reference {
3089 return state
3090 .server
3091 .validate_pushed_authorization_request(client_id, request_uri)
3092 .await;
3093 }
3094 #[cfg(feature = "jar")]
3095 if let Some(request_object) = by_value {
3096 return state
3097 .server
3098 .validate_signed_authorization_request(client_id, request_object)
3099 .await;
3100 }
3101 }
3102 }
3103
3104 let request =
3105 AuthorizationRequest::from_pairs(pairs.iter().map(|(k, v)| (k.as_ref(), v.clone())));
3106 state.server.validate_authorization_request(&request).await
3107}
3108
3109/// RFC 6749 s4.1.1: the authorization endpoint.
3110async fn authorize_handler<S: Storage, C: Clock>(
3111 state: &Inner<S, C>,
3112 headers: &HeaderMap,
3113 uri: &Uri,
3114) -> Response {
3115 // WHEN THIS REQUEST ARRIVED, read before anything is looked up, and the instant the code
3116 // minted below is dated from.
3117 //
3118 // The decision this handler acts on is not always made during this handler. With a remembered
3119 // consent it was made when the user first approved, and the read that surfaces it happens
3120 // several awaits from here. Dating the code at ISSUANCE would let a standing approval outrank
3121 // a withdrawal recorded in between: the user clicks "remove this application" elsewhere, the
3122 // withdrawal cascades and records its barrier, this request resumes on its pre-withdrawal
3123 // snapshot, and a code dated NOW postdates the barrier — so the token is issued and its
3124 // refresh chain inherits the same instant and rotates long after the barrier is swept.
3125 //
3126 // Request entry is the latest instant this service can honestly claim: any withdrawal
3127 // recorded before it is one the consent read below would have seen, and any recorded after it
3128 // is later than this instant and refuses the write. See `UserApproval::granted_at`.
3129 let received_at = state.server.now();
3130
3131 let pairs = match parse_pairs(uri.query().unwrap_or_default()) {
3132 Ok(pairs) => pairs,
3133 Err(TooManyParameters) => return too_many_parameters(),
3134 };
3135
3136 let validated = match resolve_authorization_request(state, &pairs).await {
3137 Ok(v) => v,
3138 // RFC 6749 s4.1.2.1. `Direct` means the client or the redirect URI could not be
3139 // validated, so there is no address the server may safely send this to; it is rendered
3140 // to the user agent instead. `via_header` is false because the authorization endpoint has
3141 // no client authentication to challenge.
3142 Err(AuthorizationError::Direct(e)) => {
3143 return error_response(&e, false, &state.challenge);
3144 }
3145 Err(AuthorizationError::Redirect(r)) => return redirect(r.location()),
3146 };
3147
3148 // The resource owner. Without one there is nobody whose consent a code could represent.
3149 let subject = match state.subject(headers) {
3150 Some(s) => s,
3151 // Deliberately NOT an error redirect. `access_denied` at the client's redirect URI would
3152 // tell the client a user refused, when in truth no user was ever asked. A direct 403 says
3153 // that without lying to the client.
3154 //
3155 // TWO STATES, and they are not the same mistake, which is why they are no longer the same
3156 // sentence. Until the 0.9.1 audit both said "the host must supply a subject resolver", and
3157 // for the common one that is false: `SubjectResolver` documents `None` as "nobody is
3158 // logged in", so an ordinary signed-out browser navigation told a fully wired host to
3159 // install what it had already installed, and sent whoever read it to the wrong file.
3160 None => {
3161 return match state.subject.is_some() {
3162 true => {
3163 unwired("no authenticated resource owner: nobody is signed in for this request")
3164 }
3165 false => unwired(
3166 "no authenticated resource owner; the host must supply a subject resolver",
3167 ),
3168 }
3169 }
3170 };
3171
3172 // RFC 6749 s10.12: knowing WHO the user is does not establish that they agreed. Without a
3173 // approval seam this endpoint would mint a code on any cross-site top-level navigation a
3174 // logged-in user's browser is made to follow, so an unwired host refuses. This is a direct
3175 // 403 for the same reason as the missing subject above: no user refused, none was asked.
3176
3177 // What this user has already granted this client, handed to the resolver below. A storage
3178 // failure reads as "nothing remembered", which makes the host ask again: the failure mode of
3179 // this lookup has to be an extra prompt, never a skipped one.
3180 #[cfg(feature = "consent")]
3181 let remembered = state
3182 .server
3183 .remembered_consent(&validated.client_id, &subject)
3184 .await
3185 .unwrap_or(None);
3186
3187 let approval = match &state.approval {
3188 Some(resolver) => resolver(&ApprovalRequest {
3189 headers,
3190 subject: &subject,
3191 client_id: &validated.client_id,
3192 scope: &validated.scope,
3193 redirect_uri: &validated.redirect_uri,
3194 state: validated.state.as_deref(),
3195 resource: &validated.resource,
3196 #[cfg(feature = "rar")]
3197 authorization_details: &validated.authorization_details,
3198 uri,
3199 #[cfg(feature = "consent")]
3200 // Deref through the shared `Arc<ConsentRecord>` the storage seam now returns: the
3201 // resolver borrows for the length of the call and never needs the handle.
3202 remembered: remembered.as_deref(),
3203 }),
3204 None => {
3205 return unwired(
3206 "no approval step is configured; the host must supply an approval resolver \
3207 (RFC 6749 s10.12)",
3208 )
3209 }
3210 };
3211 // Only ever set by the host's own `ApproveAndRemember`; see that variant's docs.
3212 #[cfg(feature = "consent")]
3213 let mut remember = false;
3214 match approval {
3215 ApprovalDecision::Approve => {}
3216 #[cfg(feature = "consent")]
3217 ApprovalDecision::ApproveAndRemember => remember = true,
3218 // A refusal is an answer the client is entitled to receive at its (validated) redirect
3219 // URI, which is exactly what RFC 6749 s4.1.2.1 `access_denied` is for.
3220 ApprovalDecision::Deny => return redirect(validated.denied().location()),
3221 ApprovalDecision::Respond(response) => return *response,
3222 }
3223
3224 // The host's report of how and when it authenticated this user, for RFC 9470 s4's parameters to
3225 // be enforced against. An unwired host reports `None`, which satisfies no requirement.
3226 #[cfg(feature = "consent")]
3227 let authentication = state.authentication.as_ref().and_then(|f| f(headers));
3228 // The requirement comes off the RESOLVED request, not off `pairs`. For a PAR or JAR request the
3229 // query holds only `client_id` plus the handle or the object, so reading `pairs` here dropped
3230 // `acr_values` and `max_age` entirely and silently disabled step-up for both (RFC 9126 s4, RFC
3231 // 9101 s6.3). A malformed `max_age` is now refused during validation, on the same redirect the
3232 // rest of the redirectable checks use.
3233 #[cfg(feature = "consent")]
3234 let issued = state
3235 .server
3236 .issue_authorization_code_with_authentication(
3237 // The assertion `UserApproval::granted_at` makes is exactly what this service has
3238 // just finished doing: the approval resolver returned `Approve` for THIS request, on
3239 // behalf of the subject the host's own resolver named. Nowhere else in this file may
3240 // mint one. It is dated from request entry rather than from now because the decision
3241 // may be a standing one; see `received_at` above.
3242 UserApproval::granted_at(&validated, subject.clone(), received_at),
3243 &validated.authentication_requirement,
3244 authentication.as_ref(),
3245 )
3246 .await;
3247 #[cfg(not(feature = "consent"))]
3248 let issued = state
3249 .server
3250 .issue_authorization_code(UserApproval::granted_at(&validated, subject, received_at))
3251 .await;
3252
3253 // AFTER issuance, and only on success: a consent records that the user granted something, and
3254 // nothing was granted if the code was refused.
3255 #[cfg(feature = "consent")]
3256 if remember && issued.is_ok() {
3257 // A failure to remember is not a failure to authorize. The user consented and the code is
3258 // already minted; turning that into an error would throw away an approval the user actually
3259 // gave, and the only consequence of the lost record is being asked again next time.
3260 let _ = state
3261 .server
3262 .record_consent(
3263 &validated.client_id,
3264 &subject,
3265 &validated.scope,
3266 &validated.resource,
3267 authentication,
3268 )
3269 .await;
3270 }
3271
3272 match issued {
3273 Ok(response) => redirect(response.location(&validated.redirect_uri)),
3274 Err(AuthorizationError::Direct(e)) => error_response(&e, false, &state.challenge),
3275 Err(AuthorizationError::Redirect(r)) => redirect(r.location()),
3276 }
3277}
3278
3279/// The authorization endpoint's answer when a seam the host had to wire is missing.
3280///
3281/// 403 with `access_denied` and a description naming the gap: the client learns the request was
3282/// not authorized, and the host's developer learns why without the server having invented a user
3283/// or a decision. Never a redirect, for the reason above.
3284fn unwired(why: &'static str) -> Response {
3285 let err = ErrorResponse::new(ErrorCode::AccessDenied).with_description(why);
3286 let mut resp = respond(StatusCode::FORBIDDEN, json_body(&err));
3287 let headers = resp.headers_mut();
3288 headers.insert(header::CONTENT_TYPE, json_content_type());
3289 no_store(headers);
3290 resp
3291}
3292
3293/// A 302 to `location`.
3294///
3295/// RFC 6749 s4.1.2 leaves the exact 3xx to the server; 302 is what the RFC's own examples show
3296/// and what every client understands.
3297///
3298/// `no_store`, because this is a credential-bearing response like any other on the token plane:
3299/// the `Location` of a successful authorization carries the authorization CODE and the `state`.
3300/// RFC 9111 s4.2.2 does not list 302 as heuristically cacheable, so a conforming shared cache will
3301/// not keep it — but `no-store` is what turns that from a hope about the intermediary into an
3302/// instruction, and every other credential-bearing constructor in this file already sends it.
3303///
3304/// THE FALLBACK IS REACHABLE, contrary to what this comment said until the 0.9.1 audit. The
3305/// appended parameters are percent-encoded by [`crate::authorization`], but the REGISTERED redirect
3306/// URI is pushed verbatim, so a URI with a space in it reaches `HeaderValue::from_str` and fails
3307/// it, AFTER the code has been minted and persisted.
3308///
3309/// WHICH DOOR IS OPEN was named wrongly here until 0.9.2, and the correction matters because it
3310/// tells the reader where to look. This said "only the RFC 7591 dynamic path validates it — a host
3311/// calling `register_client` directly supplies a bare `Vec<String>`".
3312/// [`crate::server::AuthorizationServer::register_client`] DOES validate, through the same
3313/// predicate the dynamic path uses (`crate::authorization::is_valid_resource_indicator`), and
3314/// `tests/authorization_code.rs` has held it to that since 0.9.1. Both of this crate's
3315/// registration entry points are therefore closed.
3316///
3317/// What is open is BELOW them: [`crate::store::Storage::put_client`] and
3318/// [`crate::store::Storage::compare_and_swap_client`] take a [`crate::client::Client`] as given,
3319/// they are public on a public trait, and `AuthorizationServer::store` hands the host the store to
3320/// call them on. A host that provisions clients by writing rows — directly, or by migrating a
3321/// legacy table, or by implementing `Storage` over a database it also writes from elsewhere — puts
3322/// a `redirect_uris` entry into circulation that no validator in this crate ever saw. That is the
3323/// path that ends here.
3324///
3325/// The 500 is the honest answer at that point; the fix belongs at provisioning, and the failure is
3326/// named here so that whoever meets it once knows where to look.
3327fn redirect(location: String) -> Response {
3328 match HeaderValue::from_str(&location) {
3329 Ok(value) => {
3330 let mut resp = respond(StatusCode::FOUND, Body::empty());
3331 let headers = resp.headers_mut();
3332 headers.insert(header::LOCATION, value);
3333 no_store(headers);
3334 resp
3335 }
3336 Err(_) => error_response(
3337 &ErrorResponse::new(ErrorCode::ServerError),
3338 false,
3339 &HeaderValue::from_static("Basic realm=\"oauth\""),
3340 ),
3341 }
3342}
3343
3344/// Constant-time equality, over SHA-256 digests so the loop bound does not depend on either
3345/// input's length.
3346///
3347/// A CSRF token is a secret the submitter is claiming to know, so comparing it with `==` leaks
3348/// the length of the match through timing exactly as a secret comparison would. This mirrors
3349/// `client::constant_time_eq`, which is private to that module; duplicating six lines is cheaper
3350/// than widening that function's visibility, and this one is exercised by its own test.
3351fn constant_time_eq(a: &str, b: &str) -> bool {
3352 let da = Sha256::digest(a.as_bytes());
3353 let db = Sha256::digest(b.as_bytes());
3354 let mut acc: u8 = 0;
3355 for i in 0..32 {
3356 acc |= da[i] ^ db[i];
3357 }
3358 acc == 0
3359}
3360
3361/// Whether the request body is `application/x-www-form-urlencoded`.
3362///
3363/// Required on the verification POST as defence in depth for RFC 6749 s10.12: it is one of the
3364/// three content types a cross-origin form or a no-preflight `fetch` may send, but demanding it
3365/// still removes every JSON or text body that could otherwise be smuggled here, and it costs a
3366/// conforming browser form nothing because that is exactly what a form sends.
3367fn is_form_urlencoded(headers: &HeaderMap) -> bool {
3368 headers
3369 .get(header::CONTENT_TYPE)
3370 .and_then(|v| v.to_str().ok())
3371 // Parameters are allowed (`; charset=utf-8`), so only the media type is compared.
3372 .map(|v| v.split(';').next().unwrap_or_default().trim())
3373 .is_some_and(|mime| mime.eq_ignore_ascii_case("application/x-www-form-urlencoded"))
3374}
3375
3376/// Whether this POST demonstrably came from a document on the issuer's own origin.
3377///
3378/// RFC 6749 s10.12, RFC 9700 s4.7. A browser sends `Origin` on every POST and `Sec-Fetch-Site` on
3379/// every request it makes from a document, so a genuine submission of the form this server
3380/// rendered carries at least one of them and both say "us". A cross-site forced submission
3381/// carries the ATTACKER's origin, which is the whole signal. Absence is refused rather than
3382/// waved through: on a browser-facing endpoint absence means a client that is not a browser, and
3383/// a request that is not from a browser has no session cookie worth forging.
3384fn same_origin(headers: &HeaderMap, origin: &str) -> bool {
3385 // `Sec-Fetch-Site` is the more precise of the two where it exists, so it is decisive when
3386 // present: `same-origin` is a submission from our own page, and anything else (including
3387 // `none`, a user-typed navigation) is not.
3388 if let Some(site) = headers.get("sec-fetch-site").and_then(|v| v.to_str().ok()) {
3389 return site.eq_ignore_ascii_case("same-origin");
3390 }
3391 headers
3392 .get(header::ORIGIN)
3393 .and_then(|v| v.to_str().ok())
3394 .is_some_and(|value| value.eq_ignore_ascii_case(origin))
3395}
3396
3397/// What the RFC 8628 section 5.1 throttle has already been told about the user code a page is
3398/// about to display.
3399///
3400/// ONE CODE ENTRY IS CHARGED ONCE, however many times a single request resolves it. A wrong code
3401/// posted with `action=approve` is resolved TWICE — once by
3402/// [`crate::server::AuthorizationServer::approve_device`], and again by the re-render that reports
3403/// the failure — and charging both halved every budget a host configured: a 200-unit-per-minute
3404/// limiter documented as allowing twenty wrong entries a minute allowed ten. The error was
3405/// fail-closed, which is why nothing noticed it. This enum is how the second resolution says "that
3406/// entry is already counted" without any handler having to remember the rule.
3407#[derive(Clone, Copy)]
3408enum CodeEntry {
3409 /// Nothing on this request has counted this entry yet, so the lookup counts it. Every
3410 /// separately-attackable entry point is this: the RFC 8628 s3.3.1 deep link, and the
3411 /// stage-one POST that types a code to see what it is for. Making those free would let an
3412 /// attacker walk the code space for nothing, which is a worse defect than the double charge.
3413 Uncharged,
3414 /// A handler earlier in THIS request already counted this exact entry, so the lookup must not
3415 /// count it a second time.
3416 AlreadyCharged,
3417 /// The throttle already REFUSED this entry earlier in this request. There is nothing to look
3418 /// up: a lookup would answer, for free, the one question the refusal exists to leave
3419 /// unanswered.
3420 Refused,
3421}
3422
3423/// The throttle refused to answer for this user code (RFC 8628 section 5.1).
3424///
3425/// A distinct outcome from "nothing pending matches", and the distinction is the point: they are
3426/// the same value to the ATTACKER (see [`THROTTLED_MESSAGE`]) but they are not the same value to
3427/// this code, which must not tell a user with a perfectly good code that it was not recognised.
3428struct Throttled;
3429
3430/// The pending grant behind an entered user code, plus the client's display name, for the
3431/// consent screen. `Ok(None)` when nothing pending matches.
3432///
3433/// Read through the public storage seam rather than through a server method, and deliberately
3434/// NOT treated as authoritative: expiry and state are re-checked inside
3435/// `AuthorizationServer::approve_device` against the server's own clock when the user actually
3436/// approves. This lookup exists to DISPLAY, so being one moment stale costs nothing.
3437async fn pending_grant<S: Storage, C: Clock>(
3438 state: &Inner<S, C>,
3439 entered_user_code: &str,
3440 entry: CodeEntry,
3441) -> Result<Option<(DeviceGrant, Option<String>)>, Throttled> {
3442 // THIS LOOKUP IS A GUESSING ORACLE, so it goes through the host's throttle exactly as
3443 // AuthorizationServer::pending_grant_by_user_code does. The response distinguishes a live
3444 // pending code from an unknown one perfectly (one renders the client and the scope, the other
3445 // says the code was not recognised), so without this a host that implemented the RateLimiter
3446 // seam correctly would still see nothing while an attacker walked the code space with GETs and
3447 // spent a single throttled POST on the one that hit.
3448 //
3449 // RFC 8628 s5.1 makes the user code's entropy adequate only IN COMBINATION WITH rate limiting
3450 // of code entry, and s5.4 names this exact URL, the verification_uri_complete deep link, as
3451 // the higher-risk entry point.
3452 //
3453 // `entry` decides whether THIS resolution is the one that pays; see [`CodeEntry`].
3454 let hooks = state.server.hooks();
3455 match entry {
3456 CodeEntry::Refused => return Err(Throttled),
3457 CodeEntry::Uncharged => {
3458 if hooks.check(Attempt::DeviceUserCodeEntry) == RateLimitDecision::Deny {
3459 return Err(Throttled);
3460 }
3461 }
3462 // The check is skipped along with the charge, and deliberately: the handler that charged
3463 // this entry was ALLOWED through, so asking the limiter again could only refuse a request
3464 // it has already accepted, halfway through answering it.
3465 CodeEntry::AlreadyCharged => {}
3466 }
3467 let normalized = normalize_user_code(entered_user_code);
3468 let grant = state
3469 .server
3470 .store()
3471 .find_device_grant_by_user_code(&normalized)
3472 .await
3473 .ok()
3474 .flatten()
3475 .filter(|g| g.state == DeviceGrantState::Pending);
3476 // Report the outcome, because a guessing attack shows up in FAILURES, not in traffic volume.
3477 // Only when this resolution is the one paying for the entry: a second report of one entry is
3478 // a second charge, which is the whole defect [`CodeEntry`] exists to describe.
3479 if matches!(entry, CodeEntry::Uncharged) {
3480 hooks.record(
3481 Attempt::DeviceUserCodeEntry,
3482 if grant.is_some() {
3483 AttemptOutcome::Succeeded
3484 } else {
3485 AttemptOutcome::Failed
3486 },
3487 );
3488 }
3489 let Some(grant) = grant else {
3490 return Ok(None);
3491 };
3492 let name = state
3493 .server
3494 .store()
3495 .get_client(&grant.client_id)
3496 .await
3497 .ok()
3498 .flatten()
3499 // Cloned out of the shared `Arc<Client>` (see `Storage::get_client`): this renders a human
3500 // facing verification page, so one string copy per page view is not a cost worth shaping
3501 // the storage seam around.
3502 //
3503 // A storage FAILURE here is deliberately not fatal to the page, and the reason is what the
3504 // page actually shows: `verification_page` renders the `client_id` and the scope whatever
3505 // happens, and falls back to the `client_id` when there is no name, because a registration
3506 // is entitled to have none and because a pretty name is the part a phishing registration
3507 // chooses. So a failed lookup degrades to exactly the page a nameless registration gets,
3508 // which still identifies the client (RFC 8628 s3.3) and still requires an affirmative
3509 // click. Refusing to render at all would mean an unrelated store hiccup ended a login the
3510 // user is in the middle of, and would do it on the ONE screen where the user is watching.
3511 .and_then(|c| c.name.clone());
3512 Ok(Some((grant, name)))
3513}
3514
3515/// What a user is told when the RFC 8628 section 5.1 throttle refused their code entry.
3516///
3517/// One string for both the POST and the GET path, because they owe the user the same answer. It
3518/// says nothing about whether the code was real: that is the question the throttle exists to stop
3519/// being asked, and a page that answered it for refused attempts would hand back the oracle the
3520/// refusal just took away.
3521const THROTTLED_MESSAGE: &str = "Too many attempts. Wait and try again.";
3522
3523/// Render the verification page for whatever `entered` resolves to, with a fresh CSRF token.
3524///
3525/// `status` and `message` are what the CALLER already worked out, and they win: the submit handler
3526/// has an outcome in hand and has chosen the status to match it. They are absent on the display
3527/// paths, which is where the lookup below gets to decide.
3528async fn render_verification<S: Storage, C: Clock>(
3529 state: &Inner<S, C>,
3530 headers: &HeaderMap,
3531 entered: &str,
3532 status: StatusCode,
3533 message: Option<&str>,
3534 entry: CodeEntry,
3535) -> Response {
3536 let csrf = match &state.verification {
3537 // RFC 6749 s10.12 is the AS's obligation, so an unwired host is served an explanation
3538 // and NO form. A form that works and is forgeable is worse than no form at all.
3539 VerificationProtection::Unwired => {
3540 return html_response(
3541 StatusCode::INTERNAL_SERVER_ERROR,
3542 verification_message(
3543 "This server is not configured to accept device approvals. The host must \
3544 supply CSRF tokens (RFC 6749 s10.12).",
3545 ),
3546 )
3547 }
3548 VerificationProtection::Tokens { issue, .. } => match issue(headers) {
3549 Some(token) => Some(token),
3550 // No session means no token can be bound to one, so there is nothing to render.
3551 None => {
3552 return html_response(
3553 StatusCode::FORBIDDEN,
3554 verification_message("You are not signed in."),
3555 )
3556 }
3557 },
3558 VerificationProtection::Disabled => None,
3559 };
3560
3561 let looked_up = match entered.is_empty() {
3562 true => Ok(None),
3563 false => pending_grant(state, entered, entry).await,
3564 };
3565 // A code that was typed but matches nothing pending is worth saying so, rather than rendering
3566 // a consent screen with nothing on it — but a code the THROTTLE refused matches nothing for a
3567 // completely different reason, and until 0.9.1 this page told those users their perfectly good
3568 // code was not recognised, at HTTP 200. The sibling POST path has always distinguished the two
3569 // (see `verification_submit_handler`); this is the deep-linked entry point RFC 8628 s5.4 warns
3570 // about, so it is the one that most wants the honest status.
3571 let (status, message) = match (message, &looked_up) {
3572 (Some(m), _) => (status, Some(m)),
3573 (None, Err(Throttled)) => (StatusCode::TOO_MANY_REQUESTS, Some(THROTTLED_MESSAGE)),
3574 (None, Ok(None)) if !entered.is_empty() => (status, Some("That code was not recognised.")),
3575 (None, _) => (status, None),
3576 };
3577 let grant = looked_up.unwrap_or(None);
3578 html_response(
3579 status,
3580 verification_page(entered, message, grant.as_ref(), csrf.as_deref()),
3581 )
3582}
3583
3584/// RFC 8628 s3.3: the page a user visits to enter the code shown on the device.
3585async fn verification_page_handler<S: Storage, C: Clock>(
3586 state: &Inner<S, C>,
3587 headers: &HeaderMap,
3588 uri: &Uri,
3589) -> Response {
3590 // `verification_uri_complete` (RFC 8628 s3.3.1) carries the code in the query so the user
3591 // does not retype it; prefilling is the entire point of that member. Prefilling is ALL it
3592 // does: RFC 8628 s5.4 (Remote Phishing) is explicit that this deep link removes the one
3593 // friction point that made the attack harder, so what it lands on has to be a page naming
3594 // the client and the scope, with the approval still one deliberate click away.
3595 let pairs = match parse_pairs(uri.query().unwrap_or_default()) {
3596 Ok(pairs) => pairs,
3597 Err(TooManyParameters) => return too_many_parameters(),
3598 };
3599 let prefill = param(&pairs, "user_code").unwrap_or_default();
3600 // A separately-attackable entry point, and the one RFC 8628 s5.4 singles out: this request has
3601 // charged nothing yet, so the lookup charges it.
3602 render_verification(
3603 state,
3604 headers,
3605 prefill,
3606 StatusCode::OK,
3607 None,
3608 CodeEntry::Uncharged,
3609 )
3610 .await
3611}
3612
3613/// The verification form's submission: the user has entered the code shown on their device.
3614async fn verification_submit_handler<S: Storage, C: Clock>(
3615 state: &Inner<S, C>,
3616 headers: &HeaderMap,
3617 body: &Bytes,
3618) -> Response {
3619 // Checked before the body is even parsed, and before the unprotected escape hatch is
3620 // consulted, because it is the one guard that costs a conforming browser nothing.
3621 if !is_form_urlencoded(headers) {
3622 return html_response(
3623 StatusCode::UNSUPPORTED_MEDIA_TYPE,
3624 verification_message("Expected an application/x-www-form-urlencoded submission."),
3625 );
3626 }
3627
3628 let text = String::from_utf8_lossy(body);
3629 let form = match parse_pairs(&text) {
3630 Ok(form) => form,
3631 Err(TooManyParameters) => return too_many_parameters(),
3632 };
3633 let user_code = param(&form, "user_code").unwrap_or_default();
3634
3635 // RFC 6749 s10.12, defence in depth, in the order that refuses soonest. A host that has
3636 // explicitly disabled these is not browser-facing and is answering for that itself.
3637 let protected = !matches!(state.verification, VerificationProtection::Disabled);
3638 if protected {
3639 if !same_origin(headers, &state.origin) {
3640 return html_response(
3641 StatusCode::FORBIDDEN,
3642 verification_message("That request did not come from this site."),
3643 );
3644 }
3645 let expected =
3646 match &state.verification {
3647 VerificationProtection::Tokens { consume, .. } => consume(headers),
3648 // No CSRF seam: nothing to compare against, so nothing is approved. The GET above
3649 // already refused to render a form, so this is the direct-POST path.
3650 VerificationProtection::Unwired => return html_response(
3651 StatusCode::INTERNAL_SERVER_ERROR,
3652 verification_message(
3653 "This server is not configured to accept device approvals. The host must \
3654 supply CSRF tokens (RFC 6749 s10.12).",
3655 ),
3656 ),
3657 VerificationProtection::Disabled => None,
3658 };
3659 let presented = param(&form, "csrf_token").unwrap_or_default();
3660 // Constant time, and `expected` was consumed above, so a token works exactly once.
3661 let ok = expected.is_some_and(|e| constant_time_eq(&e, presented));
3662 if !ok {
3663 return html_response(
3664 StatusCode::FORBIDDEN,
3665 verification_message("That form has expired. Start again."),
3666 );
3667 }
3668 }
3669
3670 if user_code.is_empty() {
3671 // No code was entered, so there is nothing to look up and nothing to charge either way.
3672 return render_verification(
3673 state,
3674 headers,
3675 "",
3676 StatusCode::BAD_REQUEST,
3677 Some("Enter the code shown on your device."),
3678 CodeEntry::Uncharged,
3679 )
3680 .await;
3681 }
3682
3683 // RFC 8628 s3.3 requires an explicit confirmation step, so approval needs an affirmative
3684 // action and nothing else does it. A submission with no action re-renders the consent screen
3685 // for the code just entered, which is the second stage of the two-stage form: type a code,
3686 // see what it is for, THEN decide. The escape hatch keeps the old approve-by-default shape,
3687 // because the non-browser harness that needs it posts a bare user_code.
3688 let action = param(&form, "action").unwrap_or_default();
3689 let denied = action == "deny";
3690 let approved = action == "approve" || !protected;
3691 if !denied && !approved {
3692 // Stage one of the two-stage form: this request has resolved the code nowhere else, and a
3693 // POST of a guessed code is exactly what the throttle counts, so it pays here.
3694 return render_verification(
3695 state,
3696 headers,
3697 user_code,
3698 StatusCode::OK,
3699 None,
3700 CodeEntry::Uncharged,
3701 )
3702 .await;
3703 }
3704
3705 // Approval binds the grant to a USER, so the same rule as the authorization endpoint applies:
3706 // without an authenticated resource owner there is nobody to bind it to.
3707 let subject = match state.subject(headers) {
3708 Some(s) => s,
3709 None => {
3710 return html_response(
3711 StatusCode::FORBIDDEN,
3712 verification_message("You are not signed in."),
3713 )
3714 }
3715 };
3716
3717 // RFC 8628 s3.3 leaves the deny path to the implementation; offering it matters, because a
3718 // user who did not start the flow needs a way to say so.
3719 let outcome = if denied {
3720 state.server.deny_device(user_code).await
3721 } else {
3722 state.server.approve_device(user_code, subject).await
3723 };
3724
3725 match outcome {
3726 Ok(()) if denied => html_response(StatusCode::OK, verification_message("Request denied.")),
3727 Ok(()) => html_response(
3728 StatusCode::OK,
3729 verification_message("Approved. You can return to your device."),
3730 ),
3731 // These are NOT OAuth wire errors: RFC 8628 leaves the verification interaction to the
3732 // implementation, and the audience here is a human, not a client library.
3733 Err(e) => {
3734 let message = match e {
3735 DeviceApprovalError::UnknownUserCode => "That code was not recognised.",
3736 DeviceApprovalError::Expired => "That code has expired. Start again on the device.",
3737 DeviceApprovalError::NotPending => "That code has already been used.",
3738 DeviceApprovalError::Storage(_) => "Something went wrong. Try again.",
3739 // The host's own limiter refused this before the code was even looked up
3740 // (RFC 8628 section 5.1). Deliberately says nothing about whether the code was
3741 // real: that is the question the throttle exists to stop being asked.
3742 DeviceApprovalError::RateLimited => THROTTLED_MESSAGE,
3743 };
3744 let status = match e {
3745 DeviceApprovalError::Storage(_) => StatusCode::INTERNAL_SERVER_ERROR,
3746 // 429 is the honest status and the one a host's own reverse proxy metrics will
3747 // already be counting.
3748 DeviceApprovalError::RateLimited => StatusCode::TOO_MANY_REQUESTS,
3749 _ => StatusCode::BAD_REQUEST,
3750 };
3751 // The CSRF token was consumed above, so this re-render mints a fresh one; without
3752 // that a mistyped code would leave the user with a form that can never be submitted.
3753 //
3754 // `approve_device`/`deny_device` resolved this exact code entry a few lines up, and
3755 // charged the throttle for it. The re-render must not charge it again — that is the
3756 // double charge `CodeEntry` documents — and when the throttle REFUSED, it must not
3757 // look the code up at all, because a refused attempt that still rendered the client
3758 // and the scope would be the oracle handed back.
3759 let entry = match e {
3760 DeviceApprovalError::RateLimited => CodeEntry::Refused,
3761 _ => CodeEntry::AlreadyCharged,
3762 };
3763 render_verification(state, headers, user_code, status, Some(message), entry).await
3764 }
3765 }
3766}
3767
3768/// Escape a value for an RFC 9110 s5.6.4 quoted-string: `\` and `"` become `\\` and `\"`.
3769///
3770/// Borrows when there is nothing to escape, which is every well-formed issuer, so the ordinary 401
3771/// costs no allocation. See the `challenge` construction for why a value this crate treats as a URL
3772/// is nonetheless escaped.
3773fn escape_quoted_string(value: &str) -> Cow<'_, str> {
3774 if !value.contains(['"', '\\']) {
3775 return Cow::Borrowed(value);
3776 }
3777 let mut out = String::with_capacity(value.len() + 8);
3778 for c in value.chars() {
3779 if c == '"' || c == '\\' {
3780 out.push('\\');
3781 }
3782 out.push(c);
3783 }
3784 Cow::Owned(out)
3785}
3786
3787/// Escape the five characters that can break out of HTML text or an attribute value. The user
3788/// code is echoed back into the form, and it arrived from the network.
3789fn escape_html(value: &str, out: &mut String) {
3790 for c in value.chars() {
3791 match c {
3792 '&' => out.push_str("&"),
3793 '<' => out.push_str("<"),
3794 '>' => out.push_str(">"),
3795 '"' => out.push_str("""),
3796 '\'' => out.push_str("'"),
3797 other => out.push(other),
3798 }
3799 }
3800}
3801
3802const PAGE_HEAD: &str = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\">\
3803 <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\
3804 <title>Device authorization</title></head><body><h1>Device authorization</h1>";
3805
3806/// The minimal verification form. Deliberately unstyled and dependency-free: a host that wants a
3807/// branded page serves its own route and calls [`AuthorizationServer::approve_device`] directly.
3808///
3809/// Two stages, and the second is not optional. With no pending grant behind the entered code the
3810/// page can only ask for the code, so its single button says CONTINUE and approves nothing. With
3811/// a grant it names the client and the exact scope being handed over, and only then offers
3812/// Approve and Deny. RFC 8628 s3.3: "the authorization server SHOULD display information about
3813/// the device"; s5.4 is why the deep link may not skip it.
3814///
3815/// Everything variable here is escaped. The user code came off the network, and a client name is
3816/// registration data, which under RFC 7591 dynamic client registration is chosen by whoever
3817/// registered the client.
3818fn verification_page(
3819 prefill: &str,
3820 message: Option<&str>,
3821 grant: Option<&(DeviceGrant, Option<String>)>,
3822 csrf: Option<&str>,
3823) -> String {
3824 let mut html = String::with_capacity(768);
3825 html.push_str(PAGE_HEAD);
3826 if let Some(message) = message {
3827 html.push_str("<p>");
3828 escape_html(message, &mut html);
3829 html.push_str("</p>");
3830 }
3831 html.push_str("<form method=\"post\">");
3832 if let Some(token) = csrf {
3833 html.push_str("<input type=\"hidden\" name=\"csrf_token\" value=\"");
3834 escape_html(token, &mut html);
3835 html.push_str("\">");
3836 }
3837 match grant {
3838 None => {
3839 html.push_str(
3840 "<label for=\"user_code\">Code shown on your device</label>\
3841 <input id=\"user_code\" name=\"user_code\" autocomplete=\"off\" \
3842 spellcheck=\"false\" value=\"",
3843 );
3844 escape_html(prefill, &mut html);
3845 html.push_str("\"><button type=\"submit\">Continue</button>");
3846 }
3847 Some((grant, name)) => {
3848 // The name is a display convenience; the client_id is the identity, so it is shown
3849 // either way. A pretty name is exactly what a phishing registration would choose.
3850 html.push_str("<p>The application <strong>");
3851 escape_html(
3852 name.as_deref().unwrap_or_else(|| grant.client_id.as_str()),
3853 &mut html,
3854 );
3855 html.push_str("</strong> (<code>");
3856 escape_html(grant.client_id.as_str(), &mut html);
3857 html.push_str("</code>) is asking to access your account.</p><p>It will be allowed: ");
3858 let scope = grant.scope.to_string();
3859 if scope.is_empty() {
3860 html.push_str("no scopes");
3861 } else {
3862 escape_html(&scope, &mut html);
3863 }
3864 html.push_str("</p><p>Code on your device: <code>");
3865 escape_html(&grant.user_code, &mut html);
3866 html.push_str("</code></p><input type=\"hidden\" name=\"user_code\" value=\"");
3867 escape_html(&grant.user_code, &mut html);
3868 html.push_str(
3869 "\"><button type=\"submit\" name=\"action\" value=\"approve\">Approve</button>\
3870 <button type=\"submit\" name=\"action\" value=\"deny\">Deny</button>",
3871 );
3872 }
3873 }
3874 html.push_str("</form></body></html>");
3875 html
3876}
3877
3878/// A page that is only a message: an outcome, or a refusal with no form to offer.
3879fn verification_message(message: &str) -> String {
3880 let mut html = String::with_capacity(256);
3881 html.push_str(
3882 "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\">\
3883 <title>Device authorization</title></head><body><p>",
3884 );
3885 escape_html(message, &mut html);
3886 html.push_str("</p></body></html>");
3887 html
3888}
3889
3890#[cfg(test)]
3891#[path = "tests/http.rs"]
3892mod tests;