Skip to main content

oauth_as/
registration.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (C) 2026 Matthew Jackson
3
4//! RFC 7591 dynamic client registration and RFC 7592 registration management.
5//!
6//! # Read this before you turn it on (RFC 7591 section 5)
7//!
8//! Dynamic registration lets a caller CREATE A CLIENT. Section 5 of RFC 7591 says so plainly: an
9//! open registration endpoint is available to anyone on the internet, and the authorization server
10//! "MUST" treat what it is handed as untrusted, because a registration is an attacker-chosen
11//! `client_name` on a consent screen, an attacker-chosen redirect URI, and an identity that every
12//! later threat model quietly assumes is scarce. This crate's own security review made the same
13//! point from the other side: several residual risks here are bounded by "the attacker must
14//! control a registered client", and dynamic registration turns that from an assumption into a
15//! form submission.
16//!
17//! So the shape of this module is decided by that, not by convenience:
18//!
19//! - It is OFF unless [`crate::server::ServerConfig::registration`] is `Some`. There is no
20//!   "enabled by default", no environment variable, and no way to reach it by accident. A host
21//!   that does not set that field cannot be dynamically registered against, and pays 8 bytes and
22//!   no allocation for the privilege.
23//! - Enabling it is not enough. A [`RegistrationPolicy`] must also be installed (see
24//!   [`crate::server::AuthorizationServer::with_registration_policy`]), and with none installed
25//!   every registration is REFUSED. That is the opposite default to the rate limiter, and the
26//!   reasoning is in [`crate::events::Hooks::registration_policy`].
27//! - What a registrant may ask for is bounded by [`RegistrationConfig`]: which grants, which
28//!   scopes. The defaults are the narrow ones (no `client_credentials`, no device grant, no
29//!   scopes), because the widening should be a sentence the host wrote.
30//! - Every registration, update and deletion is an audit event
31//!   ([`crate::events::Event::ClientRegistered`] and friends), carrying no credential.
32//!
33//! # The registration access token (RFC 7592 section 2)
34//!
35//! Management is authenticated by a bearer token minted at registration. It reads, REWRITES and
36//! DELETES a registration, so it is at least as powerful as the client secret it sits next to, and
37//! it is stored exactly the same way: as a one-way [`crate::client::SecretHash`], compared in
38//! constant time, never in plaintext and never with `==`.
39//!
40//! That has one visible consequence, and it is a deliberate deviation worth stating rather than
41//! burying. RFC 7592 section 3 lists `registration_access_token` (and, for a confidential client,
42//! `client_secret`) as members of the client information response, which the read and update
43//! responses of sections 2.1 and 2.2 also use. This server cannot ECHO either one, because it does
44//! not have them: it kept a verifier, not the credential. A credential appears in a response
45//! exactly once, in the response that MINTED it, and after that the client holds the only copy. The
46//! alternative is storing two live bearer credentials in plaintext for the lifetime of every
47//! registration, which is the thing [`crate::client::SecretHash`] exists to stop.
48//!
49//! So a read (section 2.1) returns neither, and an update (section 2.2) returns no
50//! `registration_access_token` — this server never rotates that one, so there is never a new one to
51//! hand back. An update DOES return a `client_secret` in one case, and only that case: when the
52//! updated metadata moves the client from `token_endpoint_auth_method: none` to a method that needs
53//! a secret, [`AuthorizationServer::update_registration`] mints one, because a client that has just
54//! become confidential and was told nothing would be a client that can no longer authenticate at
55//! all. That is a mint, not an echo, and it obeys the same once-only rule as the rest.
56//!
57//! # What is NOT implemented, and why
58//!
59//! - `software_statement` and `software_id` (RFC 7591 sections 2.3 and 3.1.1). A software
60//!   statement is a JWT that has to be verified against a trust anchor the HOST owns, and there is
61//!   no honest default for "which issuers do you trust to vouch for a client". A request carrying
62//!   one is REFUSED with `invalid_software_statement` rather than ignored: RFC 7591 section 2
63//!   tells a server to ignore metadata it does not understand, but a software statement is an
64//!   assertion the client believes is being HONOURED, and silently dropping it would register a
65//!   client on terms nobody agreed to.
66//! - The optional human-readable metadata of RFC 7591 section 2 (`client_uri`, `logo_uri`,
67//!   `contacts`, `tos_uri`, `policy_uri`). They are ignored, as section 2 permits, because this
68//!   server renders no branded consent screen, so storing them would be storing
69//!   attacker-supplied strings for no purpose.
70//! - `jwks` and `jwks_uri` (RFC 7591 section 2), and this one is a GAP rather than a decision.
71//!   The crate does RFC 7523 client assertions under the `client-assertion` feature, so a key
72//!   registered here would be a key the token endpoint could use; modelling these two members is
73//!   most of what it would take to make `private_key_jwt` registrable, and it is not done. Until
74//!   it is, a `private_key_jwt` client is one the HOST provisions out of band, and asking to
75//!   register one is refused rather than accepted-and-ignored (see the note on the
76//!   `AUTH_METHOD_*` constants below).
77
78use serde::{Deserialize, Serialize};
79
80use crate::client::{Client, ClientAuth, ClientId, DynamicRegistration, SecretHash};
81use crate::events::ClientAuthFailure;
82use crate::grant::GrantType;
83use crate::scope::ScopeSet;
84use crate::server::{AuthorizationServer, Clock, ServerConfig};
85use crate::store::{Storage, StorageError};
86
87/// RFC 7591 section 2 client metadata: the registration request body, and the echoed half of the
88/// section 3.2.1 client information response.
89///
90/// Only the members this server actually acts on are modelled. RFC 7591 section 2 requires a
91/// server to IGNORE metadata it does not understand, which is what `serde`'s default handling of
92/// unknown fields does here, so a client that sends `logo_uri` is not refused for it.
93#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
94pub struct ClientMetadata {
95    /// Redirection URIs (RFC 6749 section 3.1.2). REQUIRED for a client registering the
96    /// authorization code grant, since that grant has nowhere to deliver a code without one.
97    #[serde(default, skip_serializing_if = "Vec::is_empty")]
98    pub redirect_uris: Vec<String>,
99    /// How the client will authenticate at the token endpoint. Omitted means
100    /// `client_secret_basic`, which is the default RFC 7591 section 2 states.
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub token_endpoint_auth_method: Option<String>,
103    /// The grants this client will use. OMITTED (`None`) means `["authorization_code"]`
104    /// (section 2), which is why this is an `Option` rather than a `Vec` that is empty when
105    /// absent: section 2 gives omission a meaning, and an explicitly empty list means the opposite
106    /// of that meaning. Collapsing the two would make `{"grant_types": []}` silently register the
107    /// authorization code grant.
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub grant_types: Option<Vec<String>>,
110    /// The authorization response types this client will use. Omitted (`None`) means `["code"]`
111    /// (section 2); see [`ClientMetadata::grant_types`] for why this is an `Option`.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub response_types: Option<Vec<String>>,
114    /// Human-readable name, shown to a resource owner. ATTACKER-CHOSEN when registration is open:
115    /// it is echoed into this crate's device verification page, which escapes it, and any host
116    /// consent screen must do the same.
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub client_name: Option<String>,
119    /// The space-delimited scope list (RFC 6749 section 3.3) this client may request.
120    #[serde(default, skip_serializing_if = "Option::is_none")]
121    pub scope: Option<String>,
122    /// RFC 7591 section 2.3. NOT evaluated by this server, and its presence is a refusal rather
123    /// than an omission: see the module docs.
124    #[serde(default, skip_serializing_if = "Option::is_none")]
125    pub software_statement: Option<String>,
126}
127
128/// The RFC 7591 section 3.2.1 client information response, which RFC 7592 section 3 reuses for
129/// read and update.
130///
131/// `Debug` is hand-written (below) because two of these fields are live bearer credentials at the
132/// moment this value exists, and this is the value a host is most likely to log: it is what it is
133/// about to serialize.
134#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
135pub struct ClientInformation {
136    /// REQUIRED (section 3.2.1). The identifier this server minted.
137    pub client_id: String,
138    /// Present only for a confidential registration, and only on the response that MINTED it: see
139    /// the module docs on why a read cannot return it.
140    #[serde(default, skip_serializing_if = "Option::is_none")]
141    pub client_secret: Option<String>,
142    /// Seconds since the Unix epoch (section 3.2.1).
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub client_id_issued_at: Option<u64>,
145    /// REQUIRED when a `client_secret` is issued (section 3.2.1). `0` means it never expires.
146    #[serde(default, skip_serializing_if = "Option::is_none")]
147    pub client_secret_expires_at: Option<u64>,
148    /// RFC 7592 section 3. Present only on the response that minted it.
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub registration_access_token: Option<String>,
151    /// RFC 7592 section 3: where this registration is read, updated and deleted. Absent when the
152    /// host did not enable management.
153    #[serde(default, skip_serializing_if = "Option::is_none")]
154    pub registration_client_uri: Option<String>,
155    /// The registered metadata, echoed as section 3.2.1 requires, INCLUDING any value this server
156    /// substituted for what was asked (section 3.2.1: the response reflects what was registered,
157    /// not what was requested).
158    #[serde(flatten)]
159    pub metadata: ClientMetadata,
160}
161
162/// Hand-written so neither credential reaches a debug format, on the same reasoning as
163/// [`crate::client::ClientAuth`]'s and [`crate::server::TokenRequest`]'s. The Some/None
164/// distinction is kept: "a secret was issued" is registration shape, not a secret.
165impl std::fmt::Debug for ClientInformation {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        fn redact_opt<T>(value: &Option<T>) -> Option<&'static str> {
168            value.as_ref().map(|_| "[redacted]")
169        }
170        f.debug_struct("ClientInformation")
171            .field("client_id", &self.client_id)
172            .field("client_secret", &redact_opt(&self.client_secret))
173            .field("client_id_issued_at", &self.client_id_issued_at)
174            .field("client_secret_expires_at", &self.client_secret_expires_at)
175            .field(
176                "registration_access_token",
177                &redact_opt(&self.registration_access_token),
178            )
179            .field("registration_client_uri", &self.registration_client_uri)
180            .field("metadata", &self.metadata)
181            .finish()
182    }
183}
184
185/// The RFC 7591 section 3.2.2 error codes.
186///
187/// A SEPARATE registry from RFC 6749 section 5.2, and modelled separately for that reason: the two
188/// share no value, they are returned by different endpoints, and collapsing them into
189/// [`crate::error::ErrorCode`] would let a token-endpoint code be emitted here (or the reverse)
190/// with nothing to catch it.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
192#[serde(rename_all = "snake_case")]
193#[non_exhaustive]
194pub enum RegistrationErrorCode {
195    /// One or more `redirect_uris` is invalid: not an absolute URI, or carrying a fragment, or
196    /// absent for a grant that needs one.
197    InvalidRedirectUri,
198    /// Some other submitted metadata value is invalid, or names something this server will not
199    /// register.
200    InvalidClientMetadata,
201    /// The software statement presented is invalid. This server evaluates none, so any is: see
202    /// the module docs.
203    InvalidSoftwareStatement,
204}
205
206impl RegistrationErrorCode {
207    /// The registered wire spelling.
208    pub fn as_str(self) -> &'static str {
209        match self {
210            RegistrationErrorCode::InvalidRedirectUri => "invalid_redirect_uri",
211            RegistrationErrorCode::InvalidClientMetadata => "invalid_client_metadata",
212            RegistrationErrorCode::InvalidSoftwareStatement => "invalid_software_statement",
213        }
214    }
215}
216
217impl std::fmt::Display for RegistrationErrorCode {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        f.write_str(self.as_str())
220    }
221}
222
223/// The RFC 7591 section 3.2.2 error response body: a 400 with `error` and an optional
224/// `error_description`.
225#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
226pub struct RegistrationErrorResponse {
227    /// The registered code.
228    pub error: RegistrationErrorCode,
229    /// Human-readable detail for the developer.
230    ///
231    /// `Cow<'static, str>` for the same reason [`crate::error::ErrorResponse`] uses one: every
232    /// refusal `validate` can produce describes a RULE rather than a value, so the description is
233    /// always a string constant, and RFC 7591 section 1.2 makes the initial access token optional,
234    /// which means a host may expose this endpoint to unauthenticated callers who then choose its
235    /// refusal rate. Size neutral: `Option<Cow<'static, str>>` is 24 bytes, exactly what
236    /// `Option<String>` was.
237    #[serde(default, skip_serializing_if = "Option::is_none")]
238    pub error_description: Option<std::borrow::Cow<'static, str>>,
239}
240
241impl RegistrationErrorResponse {
242    /// An error with a description attached.
243    pub fn new(
244        error: RegistrationErrorCode,
245        description: impl Into<std::borrow::Cow<'static, str>>,
246    ) -> Self {
247        RegistrationErrorResponse {
248            error,
249            error_description: Some(description.into()),
250        }
251    }
252}
253
254impl std::fmt::Display for RegistrationErrorResponse {
255    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256        match &self.error_description {
257            Some(d) => write!(f, "{}: {d}", self.error),
258            None => f.write_str(self.error.as_str()),
259        }
260    }
261}
262
263/// For the reason [`crate::error::ErrorResponse`] is one: this is the value a host is handed when
264/// a registration is refused, and a host that propagates it with `?` into a `Box<dyn Error>`
265/// should not have to care which of the two sibling refusal bodies it is holding. There is no
266/// `source`: the refusal describes a rule this server applied, not a failure underneath it.
267impl std::error::Error for RegistrationErrorResponse {}
268
269/// Why a registration or management request was refused.
270///
271/// One enum for RFC 7591 and RFC 7592 because the two endpoints share every failure mode they have
272/// in common, and splitting them would make a host match twice on the same four cases.
273#[derive(Debug)]
274#[non_exhaustive]
275pub enum RegistrationFailure {
276    /// This server does not offer the endpoint: the host set no
277    /// [`crate::server::ServerConfig::registration`], or set one with management turned off. A 404
278    /// on the wire, because the honest answer to "is there a registration endpoint here" is no.
279    Disabled,
280    /// The RFC 7591 section 1.2 initial access token, or the RFC 7592 section 2 registration
281    /// access token, was missing, wrong, or refused by the host's [`RegistrationPolicy`]. 401,
282    /// with an RFC 6750 section 3 `Bearer` challenge.
283    ///
284    /// Deliberately ONE answer for all of those, including "no such registration": distinguishing
285    /// them would turn this endpoint into an oracle for which client ids exist, exactly as
286    /// `invalid_client` collapses the same two cases at the token endpoint.
287    Unauthorized,
288    /// The metadata is not acceptable (RFC 7591 section 3.2.2). 400.
289    Invalid(RegistrationErrorResponse),
290    /// The storage seam failed. 500, and the wire learns nothing else.
291    Storage(StorageError),
292}
293
294impl RegistrationFailure {
295    /// The HTTP status this refusal takes.
296    pub fn http_status(&self) -> u16 {
297        match self {
298            RegistrationFailure::Disabled => 404,
299            RegistrationFailure::Unauthorized => 401,
300            RegistrationFailure::Invalid(_) => 400,
301            RegistrationFailure::Storage(_) => 500,
302        }
303    }
304}
305
306impl std::fmt::Display for RegistrationFailure {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        match self {
309            RegistrationFailure::Disabled => f.write_str("dynamic client registration is disabled"),
310            RegistrationFailure::Unauthorized => f.write_str("not authorized to register"),
311            RegistrationFailure::Invalid(e) => write!(f, "{e}"),
312            RegistrationFailure::Storage(e) => write!(f, "{e}"),
313        }
314    }
315}
316
317impl std::error::Error for RegistrationFailure {}
318
319/// The refusal a randomness failure becomes on these two endpoints.
320///
321/// RFC 7591 registration and RFC 7592 management are ORDINARY ROUTES (`POST /register`,
322/// `PUT /register/{id}`), so a `getrandom` that will not answer — an exhausted descriptor table, a
323/// seccomp policy, a container without the syscall — has to become a refusal here exactly as it
324/// does at the token and authorization endpoints. Through 0.9.0 these four draws used the
325/// panicking `random_hex`, on the strength of a comment asserting they were "outside the request
326/// path"; they never were.
327///
328/// [`RegistrationFailure::Storage`] is the crate's 500, and it is the honest answer for the same
329/// reason `crate::server::randomness_error` collapses onto RFC 6749 section 5.2 `server_error`: the
330/// caller must learn only that this server could not fulfil the request and that retrying later is
331/// the right response. The message says which internal condition it was, for the host's own logs.
332fn randomness_failure() -> RegistrationFailure {
333    RegistrationFailure::Storage(StorageError::new(
334        "the OS would not provide randomness for a registration artifact",
335    ))
336}
337
338/// What the host's [`RegistrationPolicy`] is told about one attempt.
339///
340/// Everything borrows: the policy is called inside the request the host is already driving.
341/// `#[non_exhaustive]` because later releases will have more to say about the caller.
342#[non_exhaustive]
343pub struct RegistrationAttempt<'a> {
344    /// The RFC 7591 section 1.2 initial access token the request presented, if any. This crate
345    /// does not interpret it: the host decides what an acceptable one is, because it is the host
346    /// that issued it (or that recognises an allowlisted API key, or a signed invite, or nothing
347    /// at all).
348    pub initial_access_token: Option<&'a str>,
349    /// The metadata being registered, parsed but NOT yet validated, so a policy can refuse on
350    /// content (a `client_name` impersonating the deployment, a redirect URI on a domain the host
351    /// will not serve) before this server ever writes it down.
352    pub metadata: &'a ClientMetadata,
353}
354
355/// Hand-written: the initial access token is a bearer credential, and this struct exists only
356/// inside a request path, which is precisely where a host is most likely to debug-print it.
357impl std::fmt::Debug for RegistrationAttempt<'_> {
358    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359        f.debug_struct("RegistrationAttempt")
360            .field(
361                "initial_access_token",
362                &self.initial_access_token.map(|_| "[redacted]"),
363            )
364            .field("metadata", self.metadata)
365            .finish()
366    }
367}
368
369/// What a [`RegistrationPolicy`] decided.
370#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
371pub enum RegistrationDecision {
372    /// Register the client, subject to the metadata being valid.
373    Allow,
374    /// Refuse. The wire gets 401 and nothing else; the host knows why and this crate does not
375    /// need to.
376    Deny,
377}
378
379/// Who may create a client here. THIS LIBRARY HAS NO OPINION, and cannot have one: it never sees
380/// a request, so it has no caller, no IP, no tenant and no invite list.
381///
382/// With NO policy installed, every registration is denied. That is not a safe default chosen for
383/// tidiness, it is the only reading of RFC 7591 section 5 that does not leave an anonymous
384/// client-minting endpoint on the internet because somebody set a config field and moved on. A
385/// host that genuinely wants an open endpoint writes a two-line policy that returns
386/// [`RegistrationDecision::Allow`], and that line is then something a reviewer can find.
387pub trait RegistrationPolicy: Send + Sync {
388    /// Decide whether this attempt may create a client. Called BEFORE the metadata is validated
389    /// and before anything is written, so a refusal costs one call and touches no storage.
390    fn authorize(&self, attempt: &RegistrationAttempt<'_>) -> RegistrationDecision;
391}
392
393/// What dynamic registration is allowed to produce here. Held behind
394/// [`crate::server::ServerConfig::registration`], which is `None` (registration off) by default.
395///
396/// Every bound below is a CEILING on what an anonymous, or merely policy-approved, registrant can
397/// obtain. The defaults are the narrow ones on purpose; see [`RegistrationConfig::new`].
398#[derive(Debug, Clone, PartialEq, Eq)]
399/// `#[non_exhaustive]`: this struct's field set VARIES WITH CARGO FEATURES, so a host that writes a
400/// full struct literal has a build that breaks the day anything in their dependency graph enables a
401/// feature they did not ask for. Construct with `new()` and assign the fields you want. This is the
402/// one attribute on this type that cannot be added after publication, because by then somebody's
403/// struct literal is in production.
404#[non_exhaustive]
405pub struct RegistrationConfig {
406    /// RFC 8414 section 2 `registration_endpoint`. `None` derives `{issuer}/register`.
407    pub registration_endpoint: Option<String>,
408    /// The grants a registration may ask for. Anything outside this list is
409    /// `invalid_client_metadata`.
410    pub allowed_grant_types: Vec<GrantType>,
411    /// The ceiling on a registration's `scope`. A request outside it is `invalid_client_metadata`;
412    /// the default is EMPTY, so a host must say what a registrant may reach.
413    pub allowed_scopes: ScopeSet,
414    /// How long an issued client secret lives. `None` (the default) means it never expires, which
415    /// is what `client_secret_expires_at: 0` says on the wire (RFC 7591 section 3.2.1).
416    pub client_secret_ttl: Option<std::time::Duration>,
417    /// Whether RFC 7592 read, update and delete are offered at all. `true` by default: a client
418    /// that can be created and never corrected or deleted leaves the host doing registration
419    /// lifecycle by hand.
420    pub management_enabled: bool,
421}
422
423impl Default for RegistrationConfig {
424    fn default() -> Self {
425        RegistrationConfig::new()
426    }
427}
428
429impl RegistrationConfig {
430    /// The narrow defaults: the authorization code grant with refresh, no scopes, management on.
431    ///
432    /// `client_credentials` and the device grant are deliberately absent. A `client_credentials`
433    /// registration mints tokens with NO resource owner anywhere in the picture, so an open
434    /// registration endpoint that grants it is an open token endpoint; the device grant makes the
435    /// registrant able to allocate user codes, which RFC 8628 section 5.1 says are only adequate
436    /// in combination with rate limiting. Both are one line for a host to add, with its eyes open.
437    pub fn new() -> Self {
438        RegistrationConfig {
439            registration_endpoint: None,
440            allowed_grant_types: vec![GrantType::AuthorizationCode, GrantType::RefreshToken],
441            allowed_scopes: ScopeSet::default(),
442            client_secret_ttl: None,
443            management_enabled: true,
444        }
445    }
446
447    /// The advertised registration endpoint for `issuer`.
448    pub fn endpoint(&self, issuer: &str) -> String {
449        match &self.registration_endpoint {
450            Some(url) => url.clone(),
451            None => format!("{}/register", issuer.trim_end_matches('/')),
452        }
453    }
454}
455
456/// The largest number of `redirect_uris` one dynamic registration may declare.
457///
458/// # Why there is a cap at all
459///
460/// RFC 7591 section 2 sets none, and the list is not read here: it is read on EVERY authorization
461/// request for the registered client, as a linear scan with exact string comparison, because
462/// OAuth 2.1 section 4.1.3 forbids anything cheaper than exact matching. So an unbounded list is a
463/// cost bought once, at an endpoint whose [`RegistrationPolicy`] a host may well have opened to
464/// anonymous callers, and then paid per request for as long as the registration exists. That
465/// durability is what makes this worth a constant rather than a shrug.
466///
467/// # Why 16
468///
469/// It is counted from what a real client needs: one redirect URI per deployment environment
470/// (production, staging, a review app or two) times one per platform that needs its own
471/// (web, a native custom scheme, a loopback range for a desktop app). That is a handful, and
472/// sixteen is several times a handful. A registrant that genuinely needs more has one client
473/// standing in for several, which is a modelling problem this cap makes visible rather than a
474/// limit it imposes; separate clients also give the deployment separate secrets and separate
475/// revocation, which is the better shape anyway.
476///
477/// # Why the scan stays linear
478///
479/// At sixteen it is faster than hashing, and it is the same argument as
480/// [`crate::server::MAX_RESOURCE_INDICATORS`]. The defect was the missing bound, not the loop.
481pub const MAX_REGISTERED_REDIRECT_URIS: usize = 16;
482
483// The `token_endpoint_auth_method` values one may REGISTER here, which are a strict SUBSET of the
484// ones RFC 8414 `token_endpoint_auth_methods_supported` advertises, and deliberately so. That
485// document describes the TOKEN ENDPOINT (RFC 8414 s2), which really does accept
486// `client_secret_jwt`, `private_key_jwt`, `tls_client_auth` and `self_signed_tls_client_auth` for
487// a client the host provisioned out of band; narrowing it to this list would lie to every
488// statically configured client that uses one.
489//
490// The other four are out of reach of REGISTRATION, not of the server:
491//
492// - `private_key_jwt` and `client_secret_jwt` need a key. [`ClientMetadata`] models neither `jwks`
493//   nor `jwks_uri` (RFC 7591 s2), so there is nowhere for a registrant to put one, and
494//   `client_secret_jwt` additionally needs the shared secret IN THE CLEAR at verification time
495//   while a registration keeps only a one-way [`SecretHash`].
496// - `tls_client_auth` and `self_signed_tls_client_auth` need the RFC 8705 s2.1.1 subject
497//   parameters (`tls_client_auth_subject_dn` and the four SAN forms), which are likewise not
498//   modelled.
499//
500// Accepting any of the four would mint a registration the token endpoint could never honour,
501// which is worse than refusing it: RFC 7591 s3.2.2 gives `invalid_client_metadata` for exactly
502// this, a value the server will not register. Closing the gap means modelling `jwks`/`jwks_uri`
503// and the RFC 8705 subject parameters, and that is the change to make, not a wider list here.
504const AUTH_METHOD_NONE: &str = "none";
505const AUTH_METHOD_BASIC: &str = "client_secret_basic";
506const AUTH_METHOD_POST: &str = "client_secret_post";
507
508/// RFC 7591 section 2: the `response_type` that corresponds to the authorization code grant. It is
509/// also the only one OAuth 2.1 keeps, the implicit grant's `token` having been removed.
510const RESPONSE_TYPE_CODE: &str = "code";
511
512/// Whether a redirect URI is one this server can ever match.
513///
514/// The strictness here is set by what the AUTHORIZATION endpoint does with it, not by what looks
515/// tidy. `AuthorizationServer::validate_authorization_request` compares the requested
516/// `redirect_uri` against the registered one by EXACT STRING MATCH (OAuth 2.1 section 4.1.3), so a
517/// registration this server accepts but can never match is not a lenient registration, it is a
518/// client that will be told `invalid_request` forever with no way to find out why. RFC 6749
519/// section 3.1.2 settles both halves: the URI MUST be absolute, and it MUST NOT carry a fragment.
520///
521/// Delegated to the RFC 8707 resource-indicator check rather than restated, because that function
522/// already implements exactly this rule (absolute URI, no fragment, nothing outside printable
523/// ASCII, which RFC 3986 requires of a URI anyway) and two copies of one rule drift.
524fn redirect_uri_is_registerable(value: &str) -> bool {
525    crate::authorization::is_valid_resource_indicator(value)
526}
527
528/// The metadata as this server will actually record it.
529///
530/// `pub(crate)` rather than private because [`crate::cimd`] validates a
531/// draft-ietf-oauth-client-id-metadata-document-01 client with THIS function rather than a second
532/// copy of it. The members of a client identifier metadata document come from the same OAuth
533/// Dynamic Client Registration Metadata registry (-01 section 4.1), so the rules are the same
534/// rules, and this crate has already paid once for a rule that existed twice and drifted.
535///
536/// `Clone`, `PartialEq` and `Eq` exist for [`crate::cimd::ValidatedClientIdDocument`], which is the
537/// only thing that holds one of these beyond the call that produced it. They are NOT feature gated,
538/// and that was checked rather than assumed: `scripts/size-report.sh`'s `default` row is byte for
539/// byte identical with and without them, because LTO deletes three impls a default build never
540/// reaches. Gating them would have been noise for a measured zero.
541#[derive(Debug, Clone, PartialEq, Eq)]
542pub(crate) struct Registered {
543    pub(crate) redirect_uris: Vec<String>,
544    pub(crate) grant_types: Vec<GrantType>,
545    pub(crate) response_types: Vec<String>,
546    pub(crate) token_endpoint_auth_method: String,
547    pub(crate) scope: ScopeSet,
548    pub(crate) client_name: Option<String>,
549}
550
551/// Validate one RFC 7591 section 2 metadata document against what this deployment will register.
552///
553/// Every refusal here is a registration this server would otherwise have written down and then
554/// been unable to honour. That is the standard the rules are set to: not "is this plausible" but
555/// "will the endpoints that later read this record be able to act on it".
556pub(crate) fn validate(
557    metadata: &ClientMetadata,
558    config: &RegistrationConfig,
559) -> Result<Registered, RegistrationFailure> {
560    // RFC 7591 s2.3 / s3.2.2. First, because a client that sent one is asking to be registered on
561    // terms this server has not read, and nothing after this point would be the registration it
562    // asked for. See the module docs for why this is a refusal and not an ignored member.
563    if metadata.software_statement.is_some() {
564        return Err(RegistrationFailure::Invalid(
565            RegistrationErrorResponse::new(
566                RegistrationErrorCode::InvalidSoftwareStatement,
567                "this server does not evaluate software statements (RFC 7591 s2.3)",
568            ),
569        ));
570    }
571
572    // RFC 7591 s2: absent `grant_types` defaults to `["authorization_code"]`.
573    let grant_types: Vec<GrantType> =
574        match metadata.grant_types.as_deref() {
575            None => vec![GrantType::AuthorizationCode],
576            Some(values) => {
577                let mut out = Vec::with_capacity(values.len());
578                for value in values {
579                    // An unknown grant type is refused rather than dropped: see the test in
580                    // `src/tests/registration.rs`. `implicit` and `password` land here too, which is
581                    // right, because OAuth 2.1 removes both.
582                    // `GrantType::parse` and not `value.parse()`: the refusal below discards the
583                    // value, and `FromStr`'s error would first copy the caller's string onto the
584                    // heap to carry it there. The registration document is caller-supplied text of
585                    // the caller's chosen length, same as `grant_type` at the token endpoint.
586                    let grant: GrantType = GrantType::parse(value).ok_or_else(|| {
587                        RegistrationFailure::Invalid(RegistrationErrorResponse::new(
588                            RegistrationErrorCode::InvalidClientMetadata,
589                            "grant_types names a grant this server does not implement",
590                        ))
591                    })?;
592                    if !config.allowed_grant_types.contains(&grant) {
593                        return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
594                        RegistrationErrorCode::InvalidClientMetadata,
595                        "grant_types names a grant this deployment does not offer registrants",
596                    )));
597                    }
598                    if !out.contains(&grant) {
599                        out.push(grant);
600                    }
601                }
602                out
603            }
604        };
605    let uses_code = grant_types.contains(&GrantType::AuthorizationCode);
606
607    // RFC 7591 s2 spells out the correspondence between the two lists (`authorization_code` with
608    // `code`, `implicit` with `token`). It permits a server to reject OR to substitute; rejecting
609    // is the choice here, because substituting registers a client that asked for something else
610    // and tells it so only in the echoed response it may not re-read.
611    //
612    // OAuth 2.1 has exactly one response type left, so the whole correspondence reduces to: the
613    // list is `["code"]` if and only if the authorization code grant is registered.
614    let response_types: Vec<String> =
615        match metadata.response_types.as_deref() {
616            // s2: absent defaults to `["code"]`, which is only coherent when the code grant is there.
617            None => match uses_code {
618                true => vec![RESPONSE_TYPE_CODE.to_string()],
619                false => Vec::new(),
620            },
621            // An EXPLICIT empty list falls through here and is caught by the correspondence check
622            // below when the code grant is registered: the client said it uses no response type while
623            // asking for the one grant that has one.
624            Some(values) => {
625                for value in values {
626                    if value != RESPONSE_TYPE_CODE {
627                        return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
628                        RegistrationErrorCode::InvalidClientMetadata,
629                        "this server issues authorization codes only; OAuth 2.1 removes the \
630                         implicit grant",
631                    )));
632                    }
633                }
634                let asks_for_code = !values.is_empty();
635                // The correspondence, in both directions: `code` without `authorization_code` is a
636                // response type nothing will produce, and `authorization_code` without `code` is a
637                // grant with no way to start.
638                if asks_for_code != uses_code {
639                    return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
640                    RegistrationErrorCode::InvalidClientMetadata,
641                    "grant_types and response_types must correspond: authorization_code with \
642                     code (RFC 7591 s2)",
643                )));
644                }
645                match asks_for_code {
646                    true => vec![RESPONSE_TYPE_CODE.to_string()],
647                    false => Vec::new(),
648                }
649            }
650        };
651
652    // RFC 7591 s2 makes `redirect_uris` required for a redirection-based flow, and s3.2.2 gives
653    // the missing case and the malformed case the same code, because both say the same thing: this
654    // client has no address a code can be delivered to.
655    if uses_code && metadata.redirect_uris.is_empty() {
656        return Err(RegistrationFailure::Invalid(
657            RegistrationErrorResponse::new(
658                RegistrationErrorCode::InvalidRedirectUri,
659                "the authorization_code grant requires at least one redirect_uri",
660            ),
661        ));
662    }
663    // A CAP, because a registration is durable and the cost it imposes is not paid here. Every
664    // authorization request for this client scans `redirect_uris` linearly with exact string
665    // comparison (OAuth 2.1 section 4.1.3 allows nothing cheaper), so an unbounded list bought
666    // once at an endpoint a policy may well have opened to anonymous callers is a per-request cost
667    // that lasts as long as the registration does. RFC 7591 section 2 sets no bound of its own.
668    if metadata.redirect_uris.len() > MAX_REGISTERED_REDIRECT_URIS {
669        return Err(RegistrationFailure::Invalid(
670            RegistrationErrorResponse::new(
671                RegistrationErrorCode::InvalidRedirectUri,
672                "too many redirect_uris",
673            ),
674        ));
675    }
676    for uri in &metadata.redirect_uris {
677        if !redirect_uri_is_registerable(uri) {
678            // The offending value is NOT echoed: it is attacker-supplied and this description
679            // goes into an error body and quite possibly a log line.
680            return Err(RegistrationFailure::Invalid(
681                RegistrationErrorResponse::new(
682                    RegistrationErrorCode::InvalidRedirectUri,
683                    "each redirect_uri must be an absolute URI with no fragment (RFC 6749 s3.1.2)",
684                ),
685            ));
686        }
687    }
688
689    // RFC 7591 s2: absent `token_endpoint_auth_method` defaults to `client_secret_basic`.
690    let token_endpoint_auth_method = metadata
691        .token_endpoint_auth_method
692        .clone()
693        .unwrap_or_else(|| AUTH_METHOD_BASIC.to_string());
694    if !matches!(
695        token_endpoint_auth_method.as_str(),
696        AUTH_METHOD_NONE | AUTH_METHOD_BASIC | AUTH_METHOD_POST
697    ) {
698        return Err(RegistrationFailure::Invalid(
699            RegistrationErrorResponse::new(
700                RegistrationErrorCode::InvalidClientMetadata,
701                "token_endpoint_auth_method is not one this server can REGISTER; RFC 8414 \
702                 token_endpoint_auth_methods_supported describes the token endpoint, which \
703                 accepts more",
704            ),
705        ));
706    }
707    // RFC 6749 s4.4 gives client credentials to confidential clients only, so this pair produces a
708    // registration whose only grant the token endpoint will refuse every time. Same argument as
709    // the redirect URI rule above.
710    if token_endpoint_auth_method == AUTH_METHOD_NONE
711        && grant_types.contains(&GrantType::ClientCredentials)
712    {
713        return Err(RegistrationFailure::Invalid(
714            RegistrationErrorResponse::new(
715                RegistrationErrorCode::InvalidClientMetadata,
716                "client_credentials requires a confidential client (RFC 6749 s4.4)",
717            ),
718        ));
719    }
720
721    // RFC 6749 s3.3 syntax, then the deployment's ceiling. Both are `invalid_client_metadata`:
722    // s3.2.2 has one code for a metadata value this server will not accept.
723    let scope =
724        match metadata.scope.as_deref() {
725            None => ScopeSet::empty(),
726            Some(s) => {
727                let requested = ScopeSet::parse(s).map_err(|_| {
728                    RegistrationFailure::Invalid(RegistrationErrorResponse::new(
729                        RegistrationErrorCode::InvalidClientMetadata,
730                        "scope is not a space-delimited RFC 6749 s3.3 token list",
731                    ))
732                })?;
733                if !requested.is_subset(&config.allowed_scopes) {
734                    return Err(RegistrationFailure::Invalid(RegistrationErrorResponse::new(
735                    RegistrationErrorCode::InvalidClientMetadata,
736                    "scope exceeds what this deployment offers dynamically registered clients",
737                )));
738                }
739                requested
740            }
741        };
742
743    Ok(Registered {
744        redirect_uris: metadata.redirect_uris.clone(),
745        grant_types,
746        response_types,
747        token_endpoint_auth_method,
748        scope,
749        client_name: metadata.client_name.clone(),
750    })
751}
752
753/// Rebuild the RFC 7591 section 2 view of a stored registration, for the section 3.2.1 echo.
754fn registered_metadata(client: &Client, registration: &DynamicRegistration) -> ClientMetadata {
755    ClientMetadata {
756        redirect_uris: client.redirect_uris.clone(),
757        token_endpoint_auth_method: Some(registration.token_endpoint_auth_method.clone()),
758        grant_types: Some(client.grant_types.iter().map(|g| g.to_string()).collect()),
759        // Derived rather than stored: `validate` enforces the RFC 7591 section 2 correspondence
760        // between the two lists, so the response types are a function of the grant types and
761        // storing them separately would only create a second place for them to disagree.
762        response_types: Some(match client.allows_grant(GrantType::AuthorizationCode) {
763            true => vec![RESPONSE_TYPE_CODE.to_string()],
764            false => Vec::new(),
765        }),
766        client_name: client.name.clone(),
767        scope: (!client.allowed_scopes.is_empty()).then(|| client.allowed_scopes.to_string()),
768        // Never echoed: none was accepted, so echoing one would say it had been.
769        software_statement: None,
770    }
771}
772
773impl<S: Storage, C: Clock> AuthorizationServer<S, C> {
774    /// The registration configuration, when the host enabled it.
775    fn registration_config(&self) -> Result<&RegistrationConfig, RegistrationFailure> {
776        self.config()
777            .registration
778            .as_deref()
779            .ok_or(RegistrationFailure::Disabled)
780    }
781
782    /// RFC 7591 section 3.1: register a client.
783    ///
784    /// `initial_access_token` is whatever the request presented as an RFC 6750 bearer token, and
785    /// is passed straight to the host's [`RegistrationPolicy`]; this crate does not interpret it.
786    ///
787    /// On success the returned [`ClientInformation`] carries the ONLY copy of the client secret
788    /// (for a confidential registration) and of the RFC 7592 registration access token. Neither is
789    /// recoverable afterwards, by the client or by the host: see the module docs.
790    ///
791    /// # THIS FUTURE IS NOT CANCELLATION SAFE, and what a drop costs
792    ///
793    /// A dropped future stops at whatever `await` it was suspended in and never resumes, and this
794    /// crate cannot make it finish: there is no destructor that can run an `async` store call. The
795    /// token plane states this contract on [`AuthorizationServer::token`] and
796    /// [`AuthorizationServer::revoke`]. The management plane pays something different for a drop,
797    /// and until 0.9.2 said nothing at all about it.
798    ///
799    /// What it costs here follows from the once-only rule above: the credentials are minted
800    /// BEFORE [`crate::store::Storage::put_client`] and handed back only in the return value after
801    /// it. A drop at or after that write leaves a row this server will honour whose registration
802    /// access token — and, for a confidential registration, whose client secret — existed only in
803    /// the dropped frame. Nothing recovers either one: this server kept verifiers, not
804    /// credentials, so there is nothing to re-send; RFC 7592
805    /// management of that registration needs the access token that is gone, so section 2.3 cannot
806    /// delete it either; and a [`crate::client::Client`] has no expiry and is never reclaimed by
807    /// [`crate::store::Storage::sweep_expired`]. The caller sees a request that did not answer and
808    /// retries, which registers a SECOND client. The first is permanent litter that only a host
809    /// deleting it out of band removes.
810    ///
811    /// The order is not the defect and reversing it would be worse: returning the credentials
812    /// before the write would hand a caller a live-looking secret for a registration that then
813    /// failed to persist.
814    ///
815    /// WHAT A HOST MUST DO, exactly as on the token plane: drive this from a task the connection
816    /// cannot cancel — spawn the call and await the join handle — so a disconnecting client aborts
817    /// the response and not the work. This crate's axum adapter already does that for every route,
818    /// this one included, because it spawns inside a single `fallback`; see [`crate::http`]. A
819    /// host that mounts [`crate::http::AuthorizationService::handle`] itself, or calls this method
820    /// directly, owns it.
821    pub async fn register_dynamic_client(
822        &self,
823        metadata: &ClientMetadata,
824        initial_access_token: Option<&str>,
825    ) -> Result<ClientInformation, RegistrationFailure> {
826        self.admit_registration()?;
827        self.register_admitted_client(metadata, initial_access_token)
828            .await
829    }
830
831    /// THE HOST'S THROTTLE, before the host's policy and before anything is read or written — and,
832    /// for an HTTP caller, before the request BODY is parsed.
833    ///
834    /// This endpoint is the one place in the crate where a caller's request becomes a PERMANENT
835    /// row: a `Client` has no expiry and `Storage::sweep_expired` never reclaims one, so an
836    /// unthrottled registration endpoint is not a burst a deployment rides out, it is growth that
837    /// does not come back. RFC 7591 section 5 says the same thing in prose — an open registration
838    /// endpoint is available to anyone on the internet — and a [`RegistrationPolicy`] is a decision
839    /// about CONTENT, which is the wrong instrument for volume.
840    ///
841    /// The refusal is `Unauthorized`, the same answer the policy refusal gives, and deliberately
842    /// so: those two must not be distinguishable, or a caller learns from the wire whether it was
843    /// the content or the rate that stopped them. No event is emitted, because there is no
844    /// `client_id` to name and the audit vocabulary here is about credentials.
845    ///
846    /// `pub(crate)` for ONE caller and one reason, exactly as
847    /// [`AuthorizationServer::authenticate_registration`] is: `crate::http`'s RFC 7591 `POST`
848    /// handler runs it before it parses the request body, so an anonymous caller cannot buy a
849    /// `MAX_BODY_BYTES` JSON parse per request. RFC 7591 s3.1 registration MAY be anonymous, so
850    /// unlike the RFC 7592 management plane there is no credential to look at first — but the
851    /// throttle is not a credential, it is keyed on nothing at all, and nothing stopped it being
852    /// asked first. That it was not is the whole of the defect.
853    ///
854    /// [`AuthorizationServer::register_dynamic_client`] still runs it for itself, so a host calling
855    /// that method directly is throttled on the same terms. It is NOT re-run underneath the HTTP
856    /// handler, which is the difference from the management plane's arrangement: this budget is
857    /// GLOBAL and its shipped default is 60 per window, so a second charge for one request would
858    /// quietly halve a host's configured ceiling. The split into
859    /// `register_admitted_client` is what keeps the count at one, and
860    /// `tests/http_refusal_honesty.rs` counts it.
861    ///
862    /// The `registration_config` check comes FIRST and is repeated below, so that a deployment with
863    /// registration disabled answers `Disabled` exactly as it did before rather than spending a
864    /// throttle budget on an endpoint it does not serve.
865    pub(crate) fn admit_registration(&self) -> Result<(), RegistrationFailure> {
866        self.registration_config()?;
867        if self
868            .hooks()
869            .check(crate::events::Attempt::ClientRegistration)
870            == crate::events::RateLimitDecision::Deny
871        {
872            return Err(RegistrationFailure::Unauthorized);
873        }
874        Ok(())
875    }
876
877    /// RFC 7591 section 3.1 from the policy check onwards, for a caller
878    /// [`AuthorizationServer::admit_registration`] has already admitted.
879    ///
880    /// Split out so the throttle can be asked before an HTTP body is parsed and asked ONCE; see
881    /// that method. Every branch here is the one it was when this was the back half of
882    /// `register_dynamic_client`.
883    pub(crate) async fn register_admitted_client(
884        &self,
885        metadata: &ClientMetadata,
886        initial_access_token: Option<&str>,
887    ) -> Result<ClientInformation, RegistrationFailure> {
888        let config = self.registration_config()?;
889        let limited = crate::events::Attempt::ClientRegistration;
890
891        // The host decides, FIRST, before anything is validated or written. With no policy
892        // installed the answer is no: see [`RegistrationPolicy`] and RFC 7591 section 5. The
893        // refusal is deliberately indistinguishable from a bad initial access token, because a
894        // policy that refuses on content should not confirm what content it dislikes.
895        let attempt = RegistrationAttempt {
896            initial_access_token,
897            metadata,
898        };
899        match self.hooks().registration_policy() {
900            Some(policy) if policy.authorize(&attempt) == RegistrationDecision::Allow => {}
901            _ => {
902                // `None`: no client id has been minted yet, and none is minted for a refusal.
903                self.hooks()
904                    .emit(|| crate::events::Event::ClientRegistrationRefusedByPolicy {
905                        client_id: None,
906                    });
907                self.hooks()
908                    .record(limited, crate::events::AttemptOutcome::Failed);
909                return Err(RegistrationFailure::Unauthorized);
910            }
911        }
912
913        // Reported as a FAILURE, which is what a limiter counting abuse rather than traffic wants:
914        // a caller submitting documents this server refuses is the shape of somebody probing for
915        // what the policy and the validator will accept.
916        let registered = match validate(metadata, config) {
917            Ok(registered) => registered,
918            Err(refusal) => {
919                self.hooks()
920                    .record(limited, crate::events::AttemptOutcome::Failed);
921                return Err(refusal);
922            }
923        };
924
925        let now = crate::server::unix_seconds(self.now());
926        // `?` rather than a panic, for the reason `randomness_failure` gives: this is an
927        // unauthenticated HTTP route, and a library that aborts its host's request handler because
928        // the OS would not hand over sixteen bytes is worse than one that answers 500.
929        let client_id =
930            ClientId::new(crate::server::try_random_hex(16).ok_or_else(randomness_failure)?);
931        let secret = if registered.token_endpoint_auth_method != AUTH_METHOD_NONE {
932            Some(crate::server::try_random_hex(32).ok_or_else(randomness_failure)?)
933        } else {
934            None
935        };
936        let secret_expires_at = secret.as_ref().map(|_| match config.client_secret_ttl {
937            // RFC 7591 section 3.2.1: 0 means the secret never expires.
938            None => 0,
939            // `saturating_add`: plain `+` panics in debug and, worse, WRAPS in release, which
940            // would report a freshly minted secret as already expired. A host-set TTL is not
941            // validated anywhere, so the ceiling is the honest answer.
942            Some(ttl) => now.unwrap_or_default().saturating_add(ttl.as_secs()),
943        });
944        let registration_access_token =
945            crate::server::try_random_hex(32).ok_or_else(randomness_failure)?;
946
947        let client = Client {
948            client_id: client_id.clone(),
949            auth: match &secret {
950                None => ClientAuth::Public,
951                Some(s) => ClientAuth::ConfidentialSecretHash {
952                    hash: SecretHash::sha256(s),
953                },
954            },
955            grant_types: registered.grant_types.clone(),
956            redirect_uris: registered.redirect_uris.clone(),
957            allowed_scopes: registered.scope.clone(),
958            default_scopes: registered.scope.clone(),
959            name: registered.client_name.clone(),
960            registration: Some(Box::new(DynamicRegistration {
961                registration_access_token_hash: SecretHash::sha256(&registration_access_token),
962                client_id_issued_at: now,
963                client_secret_expires_at: secret_expires_at,
964                token_endpoint_auth_method: registered.token_endpoint_auth_method.clone(),
965            })),
966        };
967        self.store()
968            .put_client(client)
969            .await
970            .map_err(RegistrationFailure::Storage)?;
971
972        // Emitted AFTER the write: a registration that failed to persist did not happen. The
973        // outcome is reported on the same terms, and for the same reason: the row exists now.
974        self.hooks()
975            .emit(|| crate::events::Event::ClientRegistered {
976                client_id: client_id.as_str(),
977            });
978        self.hooks()
979            .record(limited, crate::events::AttemptOutcome::Succeeded);
980
981        Ok(ClientInformation {
982            client_id: client_id.as_str().to_string(),
983            client_secret: secret,
984            client_id_issued_at: now,
985            client_secret_expires_at: secret_expires_at,
986            registration_access_token: config
987                .management_enabled
988                .then_some(registration_access_token),
989            registration_client_uri: config
990                .management_enabled
991                .then(|| registration_client_uri(config, self.config(), client_id.as_str())),
992            metadata: ClientMetadata {
993                redirect_uris: registered.redirect_uris,
994                token_endpoint_auth_method: Some(registered.token_endpoint_auth_method),
995                grant_types: Some(
996                    registered
997                        .grant_types
998                        .iter()
999                        .map(|g| g.to_string())
1000                        .collect(),
1001                ),
1002                response_types: Some(registered.response_types),
1003                client_name: registered.client_name,
1004                scope: (!registered.scope.is_empty()).then(|| registered.scope.to_string()),
1005                software_statement: None,
1006            },
1007        })
1008    }
1009
1010    /// Authenticate an RFC 7592 section 2 management request.
1011    ///
1012    /// Every failure is [`RegistrationFailure::Unauthorized`]: an attempt the host's rate limiter
1013    /// denied, an unknown client, a statically provisioned client that has no registration access
1014    /// token, and a wrong token are one answer on the wire, because telling them apart is an
1015    /// enumeration oracle over the client table — and, for the limiter's own refusal, because a
1016    /// distinct answer would tell an attacker they had found a live registration and merely hit
1017    /// the ceiling.
1018    ///
1019    /// The same timing caveat that `AuthorizationServer::authenticate_client` documents applies:
1020    /// this returns before any hashing when the client is unknown, so an unknown client and a
1021    /// known client with the wrong token are distinguishable by wall time. The comparison itself
1022    /// leaks nothing (see [`SecretHash::verify`]); equalising the two paths is the host's
1023    /// business.
1024    ///
1025    /// THE HOST'S [`crate::events::RateLimiter`] IS ASKED FIRST, before the store is touched, and
1026    /// the attempt is [`crate::events::Attempt::ClientAuthentication`] keyed on this `client_id`:
1027    /// the same budget the token endpoint spends, because this is the same client's other
1028    /// credential and the more powerful one. Until 0.9.2 this said the host "is expected to
1029    /// throttle anyway", which was advice the host could not take through this crate: no `Attempt`
1030    /// reached the seam from any of the three management verbs. See the body for why the budget is
1031    /// shared rather than new.
1032    ///
1033    /// What the WIRE will not say, the AUDIT CHANNEL does. Each of the four refusals emits
1034    /// [`crate::events::Event::ClientRegistrationAuthenticationFailed`] naming which one it was,
1035    /// for the same reason the token plane separates its refusals: the host is not the attacker,
1036    /// and it cannot notice somebody guessing registration access tokens if the only record of the
1037    /// guess is a `401` that looks like every other `401`. That matters more here than on the token
1038    /// plane, because RFC 7592 section 2.2 lets a landed guess rewrite `redirect_uris`.
1039    /// `pub(crate)` for ONE caller and one reason: `crate::http`'s RFC 7592 `PUT` handler runs it
1040    /// before it parses the request body, so an unauthenticated caller cannot buy a
1041    /// `MAX_BODY_BYTES` JSON parse per request. `update_registration` below still authenticates for
1042    /// itself, because a check performed by a caller is not a check the method may assume — so an
1043    /// HTTP `PUT` spends TWO units of the throttle's budget rather than one. That is stated rather
1044    /// than tuned away: at the shipped default of 6000 per client per minute it is not a ceiling
1045    /// any real management traffic reaches, and removing the second check to save a unit would
1046    /// trade a bound for an assumption.
1047    pub(crate) async fn authenticate_registration(
1048        &self,
1049        client_id: &ClientId,
1050        registration_access_token: &str,
1051    ) -> Result<(std::sync::Arc<Client>, DynamicRegistration), RegistrationFailure> {
1052        let config = self.registration_config()?;
1053        if !config.management_enabled {
1054            return Err(RegistrationFailure::Disabled);
1055        }
1056        // THE HOST'S THROTTLE, before the store is touched, exactly as
1057        // `AuthorizationServer::authenticate_client` asks before it looks a client up.
1058        //
1059        // This is a bearer credential being GUESSED AT, which is the thing RFC 9700 section 4.13
1060        // is about, and the credential here is the more powerful of the two a dynamic registration
1061        // holds: the client secret authenticates as the client, while this one REWRITES (RFC 7592
1062        // s2.2) and DELETES (s2.3) the registration, cascading through every token and refresh
1063        // chain it was issued. Through 0.9.1 the three management verbs took no `Attempt` at all,
1064        // so a host that installed a limiter had this plane open while believing otherwise, and
1065        // the note on this function told that host it "is expected to throttle anyway" without
1066        // giving it anywhere to do so through this crate's own seam.
1067        //
1068        // `Attempt::ClientAuthentication` rather than a variant of its own, and rather than
1069        // `Attempt::ClientRegistration` (which names RFC 7591 and the permanent row it writes).
1070        // Two reasons. It is the same question — may this caller keep presenting credentials as
1071        // this `client_id` — and one client's two credentials sharing one budget is the answer
1072        // that cannot be walked around by moving from one endpoint to the other. And it adds NO
1073        // new denial of service: the budget is already keyed on a `client_id` RFC 6749 s2.2 makes
1074        // public, so anybody who could exhaust it here could already exhaust it by spraying wrong
1075        // secrets at the token endpoint.
1076        let attempt = crate::events::Attempt::ClientAuthentication {
1077            client_id: client_id.as_str(),
1078        };
1079        if self.hooks().check(attempt) == crate::events::RateLimitDecision::Deny {
1080            // No `record`: the attempt never happened, so there is no outcome to report. The
1081            // refusal is the SAME `Unauthorized` a wrong token gets (see below), because a
1082            // distinct answer would tell an attacker they had found a live registration and
1083            // merely hit the ceiling.
1084            return Err(self.registration_auth_failed(
1085                client_id,
1086                ClientAuthFailure::RateLimited,
1087                false,
1088            ));
1089        }
1090        let found = self
1091            .store()
1092            .get_client(client_id)
1093            .await
1094            .map_err(RegistrationFailure::Storage)?;
1095        let client = match found {
1096            Some(client) => client,
1097            None => {
1098                return Err(self.registration_auth_failed(
1099                    client_id,
1100                    ClientAuthFailure::UnknownClient,
1101                    true,
1102                ))
1103            }
1104        };
1105        let registration = match client.registration.as_deref() {
1106            Some(registration) => registration.clone(),
1107            // A client the host provisioned itself. Reported apart from an unknown id because it
1108            // says the id was real, which is what an operator needs to see the probe for.
1109            None => {
1110                return Err(self.registration_auth_failed(
1111                    client_id,
1112                    ClientAuthFailure::NoDynamicRegistration,
1113                    true,
1114                ))
1115            }
1116        };
1117        if !registration
1118            .registration_access_token_hash
1119            .verify(registration_access_token, self.hooks().secret_verifier())
1120        {
1121            return Err(self.registration_auth_failed(
1122                client_id,
1123                ClientAuthFailure::SecretMismatch,
1124                true,
1125            ));
1126        }
1127        // The limiter counts FAILURES rather than traffic (see `crate::rate_limit`), so a
1128        // management call that authenticated has to say so or a legitimate client's own polling
1129        // would be charged at the rate a guessing attack is.
1130        self.hooks()
1131            .record(attempt, crate::events::AttemptOutcome::Succeeded);
1132        Ok((client, registration))
1133    }
1134
1135    /// Report one refused management authentication and answer with the single wire failure all
1136    /// four share.
1137    ///
1138    /// The presented token is NOT a parameter, and that is deliberate rather than incidental: it
1139    /// cannot be logged by a later edit to this function because it is not here to log. See the
1140    /// rule in the [`crate::events`] module docs.
1141    ///
1142    /// `attempted` says whether the limiter ALLOWED this attempt and it then failed, which is the
1143    /// only case there is an outcome to report: a refusal by the limiter itself never happened, so
1144    /// reporting it would charge the caller twice for one attempt and, on a failure-weighted
1145    /// budget like [`crate::rate_limit::FixedWindowRateLimiter`], charge the heavier of the two
1146    /// prices for work nobody did.
1147    fn registration_auth_failed(
1148        &self,
1149        client_id: &ClientId,
1150        failure: ClientAuthFailure,
1151        attempted: bool,
1152    ) -> RegistrationFailure {
1153        self.hooks().emit(
1154            || crate::events::Event::ClientRegistrationAuthenticationFailed {
1155                client_id: client_id.as_str(),
1156                failure,
1157            },
1158        );
1159        if attempted {
1160            self.hooks().record(
1161                crate::events::Attempt::ClientAuthentication {
1162                    client_id: client_id.as_str(),
1163                },
1164                crate::events::AttemptOutcome::Failed,
1165            );
1166        }
1167        RegistrationFailure::Unauthorized
1168    }
1169
1170    /// RFC 7592 section 2.1: read a registration.
1171    ///
1172    /// The response carries no `client_secret` and no `registration_access_token`, because this
1173    /// server stores neither: see the module docs.
1174    pub async fn read_registration(
1175        &self,
1176        client_id: &ClientId,
1177        registration_access_token: &str,
1178    ) -> Result<ClientInformation, RegistrationFailure> {
1179        let (client, registration) = self
1180            .authenticate_registration(client_id, registration_access_token)
1181            .await?;
1182        let config = self.registration_config()?;
1183        Ok(ClientInformation {
1184            client_id: client.client_id.as_str().to_string(),
1185            client_secret: None,
1186            client_id_issued_at: registration.client_id_issued_at,
1187            client_secret_expires_at: registration.client_secret_expires_at,
1188            registration_access_token: None,
1189            registration_client_uri: Some(registration_client_uri(
1190                config,
1191                self.config(),
1192                client.client_id.as_str(),
1193            )),
1194            metadata: registered_metadata(&client, &registration),
1195        })
1196    }
1197
1198    /// RFC 7592 section 2.2: replace a registration's metadata.
1199    ///
1200    /// The whole document is replaced, not merged: section 2.2 says the client sends its full
1201    /// metadata and that any omitted member is treated as absent. Merging would make a client that
1202    /// dropped a redirect URI keep it, which is precisely backwards for the one member that
1203    /// decides where a code may be delivered.
1204    ///
1205    /// `client_id` cannot be changed (section 2.2), and the grant and scope ceilings of
1206    /// [`RegistrationConfig`] apply again, so an update cannot reach anything a fresh registration
1207    /// could not.
1208    ///
1209    /// The response carries a `client_secret` in exactly one case: an update that moves the client
1210    /// from `token_endpoint_auth_method: none` to a method that needs one MINTS a secret, and this
1211    /// is the only response other than the original registration that ever carries a live
1212    /// credential. It is never an ECHO of an existing secret, which this server does not hold; see
1213    /// the module docs.
1214    ///
1215    /// # THIS FUTURE IS NOT CANCELLATION SAFE, and this is the drop a client cannot retry around
1216    ///
1217    /// See [`AuthorizationServer::register_dynamic_client`] for why a dropped future cannot be
1218    /// finished by this crate. The expensive drop point here is the one case above: the mint.
1219    ///
1220    /// When this call mints a secret it writes the [`crate::client::SecretHash`] of it through
1221    /// [`crate::store::Storage::compare_and_swap_client`], and returns the secret itself only in
1222    /// the value at the end of this function. A drop after that swap RESOLVES and before the
1223    /// response reaches the client leaves the store holding a verifier for a string that exists
1224    /// nowhere. The client's retry does not repair it and cannot: the stored registration is now
1225    /// confidential, so on the second pass `had_secret` is true and the arm that KEEPS THE
1226    /// EXISTING VERIFIER is taken rather than the mint. That arm is right for what it was written
1227    /// for — a metadata edit must not log a client out of the token endpoint — and nothing on the
1228    /// wire distinguishes that case from this one. The registration can never authenticate at the
1229    /// token endpoint again.
1230    ///
1231    /// The way out is RFC 7592 section 2.3, and it is the only one: this call never rotates the
1232    /// registration access token, so the client still holds it and can DELETE the registration and
1233    /// register afresh. A host whose [`RegistrationPolicy`] admits an initial access token only
1234    /// once has to provision the client again itself.
1235    ///
1236    /// Reversing the order would be worse rather than better: handing the secret back before the
1237    /// swap would give a client a credential for a write that a concurrent section 2.3 delete is
1238    /// entitled to refuse — which is exactly what the compare-and-swap exists to allow. So the
1239    /// order stands and the contract is stated instead.
1240    ///
1241    /// The cheaper drop points, for completeness: anywhere before the swap costs nothing, because
1242    /// nothing has been written; between the swap and the return, an update with no mint loses
1243    /// only the [`crate::events::Event::ClientRegistrationUpdated`], so an audit trail can miss an
1244    /// update that did happen.
1245    ///
1246    /// WHAT A HOST MUST DO is what [`AuthorizationServer::register_dynamic_client`] says: spawn
1247    /// this and await the join handle. The axum adapter does. A host driving this future from the
1248    /// connection is choosing the cost above, at whatever rate its clients disconnect.
1249    pub async fn update_registration(
1250        &self,
1251        client_id: &ClientId,
1252        registration_access_token: &str,
1253        metadata: &ClientMetadata,
1254    ) -> Result<ClientInformation, RegistrationFailure> {
1255        let (client, registration) = self
1256            .authenticate_registration(client_id, registration_access_token)
1257            .await?;
1258        let config = self.registration_config()?;
1259
1260        // The host decides on an UPDATE too, and this is not a formality. RFC 7592 section 2.2
1261        // has the client send a complete replacement metadata document, so every content control
1262        // a policy applied at registration (a `client_name` impersonating the deployment, a
1263        // `redirect_uris` entry on a domain the host will not serve) is exactly what this call can
1264        // rewrite. Consulting the policy only on the way in would leave every one of those
1265        // controls one PUT away from being void, and the registration access token is long lived
1266        // where an initial access token is typically single use.
1267        //
1268        // `initial_access_token` is None because there is no second one to present: RFC 7592
1269        // section 2 authenticates this request with the registration access token, which
1270        // `authenticate_registration` above has already verified.
1271        let attempt = RegistrationAttempt {
1272            initial_access_token: None,
1273            metadata,
1274        };
1275        match self.hooks().registration_policy() {
1276            Some(policy) if policy.authorize(&attempt) == RegistrationDecision::Allow => {}
1277            _ => {
1278                // The caller HAS authenticated here, with a registration access token this server
1279                // just verified, and is being refused anyway. See the event's own docs on why that
1280                // is a different thing from a failed authentication.
1281                self.hooks()
1282                    .emit(|| crate::events::Event::ClientRegistrationRefusedByPolicy {
1283                        client_id: Some(client_id.as_str()),
1284                    });
1285                return Err(RegistrationFailure::Unauthorized);
1286            }
1287        }
1288
1289        let registered = validate(metadata, config)?;
1290
1291        // A change of authentication method that needs a secret the registration does not have
1292        // mints one; this is the only path other than registration itself that can produce one.
1293        let wants_secret = registered.token_endpoint_auth_method != AUTH_METHOD_NONE;
1294        let had_secret = client.auth.is_confidential();
1295        // `?` for the reason `randomness_failure` gives; RFC 7592 s2.2 is a routed `PUT` and this
1296        // is the only path other than registration itself that mints a secret.
1297        let new_secret = if wants_secret && !had_secret {
1298            Some(crate::server::try_random_hex(32).ok_or_else(randomness_failure)?)
1299        } else {
1300            None
1301        };
1302        let auth = match (&new_secret, wants_secret) {
1303            (Some(s), _) => ClientAuth::ConfidentialSecretHash {
1304                hash: SecretHash::sha256(s),
1305            },
1306            // Keeps the existing verifier: this server cannot re-issue a secret it does not hold,
1307            // and silently rotating one on every metadata edit would log the client out of the
1308            // token endpoint for changing its name.
1309            (None, true) => client.auth.clone(),
1310            (None, false) => ClientAuth::Public,
1311        };
1312        let client_secret_expires_at = match (&new_secret, wants_secret) {
1313            (Some(_), _) => Some(match config.client_secret_ttl {
1314                None => 0,
1315                Some(ttl) => {
1316                    // Same saturating add as the mint path above, for the same reason.
1317                    crate::server::unix_seconds(self.now())
1318                        .unwrap_or_default()
1319                        .saturating_add(ttl.as_secs())
1320                }
1321            }),
1322            (None, true) => registration.client_secret_expires_at,
1323            (None, false) => None,
1324        };
1325
1326        let updated_registration = DynamicRegistration {
1327            registration_access_token_hash: registration.registration_access_token_hash.clone(),
1328            client_id_issued_at: registration.client_id_issued_at,
1329            client_secret_expires_at,
1330            token_endpoint_auth_method: registered.token_endpoint_auth_method.clone(),
1331        };
1332        let updated = Client {
1333            client_id: client.client_id.clone(),
1334            auth,
1335            grant_types: registered.grant_types.clone(),
1336            redirect_uris: registered.redirect_uris.clone(),
1337            allowed_scopes: registered.scope.clone(),
1338            default_scopes: registered.scope.clone(),
1339            name: registered.client_name.clone(),
1340            registration: Some(Box::new(updated_registration.clone())),
1341        };
1342        // COMPARE-AND-SWAP, not a blind put. This function read the registration at its top, then
1343        // awaited a policy decision and a validation pass before arriving here, and a concurrent
1344        // RFC 7592 section 2.3 delete anywhere in that window used to be UNDONE by this write: the
1345        // client came back with its old credential and its old `registration_access_token_hash`,
1346        // which makes deleting a compromised registration defeatable by whoever holds the stolen
1347        // token. `client` is exactly what was read, so it is the expectation; a store that no
1348        // longer holds it refuses, and the deletion stands.
1349        //
1350        // The refusal is `Unauthorized` rather than a storage error, because by the time this
1351        // returns false the caller's registration access token names a registration that no
1352        // longer exists. That is the same answer `authenticate_registration` gives for an unknown
1353        // client, which is what this now is.
1354        //
1355        // NO AUDIT EVENT, and the absence is the fix rather than an omission. This used to report
1356        // `ClientAuthFailure::UnknownClient` on the management plane, which is a claim that an
1357        // AUTHENTICATION FAILED — and the authentication did not fail: reaching this line requires
1358        // `authenticate_registration` above to have SUCCEEDED, so the caller presented a
1359        // registration access token this server verified. The only ways here are a concurrent
1360        // section 2.3 delete and a second concurrent update, neither of which is a credential
1361        // guess. `Event::ClientRegistrationAuthenticationFailed` is the one signal a host has for
1362        // somebody guessing registration access tokens (see its docs on what a landed guess buys an
1363        // attacker), and a deployment whose own racing clients emit it is a deployment that has
1364        // learned to ignore it. What an operator sees instead is exactly what happened: a
1365        // `ClientRegistrationDeleted` (or another `ClientRegistrationUpdated`) from the request that
1366        // won, and no `ClientRegistrationUpdated` from this one.
1367        let applied = self
1368            .store()
1369            .compare_and_swap_client(&client, updated.clone())
1370            .await
1371            .map_err(RegistrationFailure::Storage)?;
1372        if !applied {
1373            return Err(RegistrationFailure::Unauthorized);
1374        }
1375        self.hooks()
1376            .emit(|| crate::events::Event::ClientRegistrationUpdated {
1377                client_id: client_id.as_str(),
1378            });
1379
1380        Ok(ClientInformation {
1381            client_id: client_id.as_str().to_string(),
1382            client_secret: new_secret,
1383            client_id_issued_at: updated_registration.client_id_issued_at,
1384            client_secret_expires_at: updated_registration.client_secret_expires_at,
1385            registration_access_token: None,
1386            registration_client_uri: Some(registration_client_uri(
1387                config,
1388                self.config(),
1389                client_id.as_str(),
1390            )),
1391            metadata: registered_metadata(&updated, &updated_registration),
1392        })
1393    }
1394
1395    /// RFC 7592 section 2.3: delete a registration.
1396    ///
1397    /// Deletion takes everything the registration was issued with it, through
1398    /// [`Storage::delete_client`]: a client that no longer exists must not still have live access
1399    /// tokens, refresh chains or outstanding authorization codes. Section 2.3 requires exactly
1400    /// that, and it is the half of deletion that is easy to skip and impossible to notice.
1401    ///
1402    /// # THIS FUTURE IS NOT CANCELLATION SAFE, and what a drop costs
1403    ///
1404    /// This is the cheap one of the three; see [`AuthorizationServer::register_dynamic_client`]
1405    /// for the contract and [`AuthorizationServer::update_registration`] for the expensive one. A
1406    /// drop before [`crate::store::Storage::delete_client`] leaves the registration standing, and
1407    /// the client still holds the registration access token, so the request is simply repeatable.
1408    /// A drop after it loses the [`crate::events::Event::ClientRegistrationDeleted`], so a host
1409    /// can find a registration gone with no audit record of who removed it. A retry after that
1410    /// answers `Unauthorized`, because the token now names a registration that does not exist —
1411    /// and pays what an unknown id pays: a
1412    /// [`crate::events::Event::ClientRegistrationAuthenticationFailed`] carrying
1413    /// [`crate::events::ClientAuthFailure::UnknownClient`], charged to the limiter as a failure.
1414    /// So a client retrying a deletion that in fact completed reads, to an operator, exactly like
1415    /// somebody guessing at a registration access token. That is the audit cost of a drop here,
1416    /// and it is the whole of it: nothing is left half-written.
1417    pub async fn delete_registration(
1418        &self,
1419        client_id: &ClientId,
1420        registration_access_token: &str,
1421    ) -> Result<(), RegistrationFailure> {
1422        self.authenticate_registration(client_id, registration_access_token)
1423            .await?;
1424        // The barrier deadline comes from the server's own token lifetimes: a deletion has to
1425        // refuse not just the records that exist now but anything an issuance already in flight
1426        // for this client is about to write. See `revocation_window`.
1427        self.store()
1428            .delete_client(client_id, self.revocation_window())
1429            .await
1430            .map_err(RegistrationFailure::Storage)?;
1431        self.hooks()
1432            .emit(|| crate::events::Event::ClientRegistrationDeleted {
1433                client_id: client_id.as_str(),
1434            });
1435        Ok(())
1436    }
1437}
1438
1439/// RFC 7592 section 3 `registration_client_uri`: `{registration_endpoint}/{client_id}`.
1440///
1441/// THE ID IS ESCAPED, and until the 0.9.1 audit this was a bare `format!` justified by the ids this
1442/// server MINTS being 32 hex characters. That justification only ever covered the dynamic path:
1443/// `read_registration` and `update_registration` mint this URL for whatever client the caller
1444/// authenticated as, and a host that provisioned a client through `register_client` chose its own
1445/// id. A space, a `?` or a `#` in one produced a `registration_client_uri` that is not a URL, and a
1446/// slash produced one that names a different resource; the client uses the value verbatim, so the
1447/// escaping has to happen where the URL is built.
1448///
1449/// RFC 3986 section 3.3: everything outside `unreserved` is percent-encoded, which is stricter than
1450/// `pchar` allows and is the safe direction. `crate::http`'s `decode_path_segment` decodes the
1451/// segment on the way back in, so the round trip is exact.
1452fn registration_client_uri(
1453    config: &RegistrationConfig,
1454    server: &ServerConfig,
1455    client_id: &str,
1456) -> String {
1457    let endpoint = config.endpoint(&server.issuer);
1458    let mut url = String::with_capacity(endpoint.len() + 1 + client_id.len());
1459    url.push_str(&endpoint);
1460    url.push('/');
1461    for byte in client_id.bytes() {
1462        match byte {
1463            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
1464                url.push(byte as char)
1465            }
1466            other => url.push_str(&format!("%{other:02X}")),
1467        }
1468    }
1469    url
1470}
1471
1472#[cfg(test)]
1473#[path = "tests/registration.rs"]
1474mod tests;