oauth_as/lib.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (C) 2026 Matthew Jackson
3
4// docs.rs builds this crate with `--cfg docsrs` and every feature on (see the
5// `[package.metadata.docs.rs]` table in Cargo.toml), so that a reader evaluating the crate sees
6// the WHOLE of it, with a badge on each item naming the feature that turns it on. `doc_cfg` is
7// still nightly-only, which is why it is reached for through `cfg_attr`: a stable build never
8// evaluates this attribute and so never needs the unstable feature.
9#![cfg_attr(docsrs, feature(doc_cfg))]
10// There is no `unsafe` in this crate and this is what keeps it that way. An authorization server
11// parses attacker-supplied text on endpoints that take no credential, so "we are careful" is not a
12// memory-safety argument; a compiler error is. (`tests/allocation.rs` installs a counting global
13// allocator and does need `unsafe`, but a test target is a separate compilation unit and this
14// attribute does not reach it.)
15#![forbid(unsafe_code)]
16// This is a published API. An undocumented public item is a question a consumer can only answer by
17// reading the source, which is exactly the position this crate exists to spare them.
18#![warn(missing_docs)]
19
20//! An embeddable OAuth 2.1 Authorization Server library.
21//!
22//! This crate is the AUTHORIZATION SERVER half of OAuth: it registers clients, runs grant state
23//! machines, issues and introspects tokens, and produces exactly the wire shapes the RFCs define.
24//! It is a LIBRARY, not a server binary: the host owns the HTTP listener, TLS, persistence, and
25//! the rate-limiting policy (a per-process floor ships here — see [`rate_limit`] — but only the
26//! host has a caller identity to key on, and only a host installs it). The host hands request parameters to [`server::AuthorizationServer`] and
27//! serializes the returned response/error types (they carry their own `serde` shapes and HTTP
28//! status codes), or turns on the optional `http` feature and gets that wire surface written for
29//! it.
30//!
31//! # What is here
32//!
33//! ALWAYS COMPILED, no feature and no dependency beyond serde, sha2, base64 and getrandom:
34//!
35//! - Protocol types mirroring the specs in OUR OWN structs (a deliberate project rule; no third
36//! party's generated types): [`client::Client`], [`grant::GrantType`], [`token::TokenResponse`],
37//! [`scope::ScopeSet`], [`authorization::AuthorizationRequest`], and the RFC 6749 section 5.2 /
38//! RFC 8628 section 3.5 error object ([`error::ErrorResponse`]).
39//! - The authorization code grant with MANDATORY PKCE ([`authorization`]), the OAuth 2.1 stance:
40//! validation, single-use codes, exact redirect-URI matching, and replay detection that revokes
41//! the whole issued family.
42//! - The RFC 8628 device authorization grant, as a full state machine ([`device`]):
43//! `authorization_pending`, `slow_down` (with the mandated 5 second interval increase),
44//! `expired_token`, `access_denied`, single-use redemption, and user-code normalization per RFC
45//! 8628 section 6.1.
46//! - Token issuance, introspection, revocation, and refresh rotation ([`token`]): single use, with
47//! an absolute family lifetime.
48//! - The RFC 8414 metadata document ([`metadata`]) and RFC 7591 dynamic client registration
49//! ([`registration`]), the latter off unless configured and refusing every registration until a
50//! policy is installed.
51//! - PKCE S256 primitives ([`pkce`]), verified against the RFC 7636 appendix B vector.
52//! - A storage seam ([`store::Storage`]) the HOST implements, plus [`store::MemoryStorage`] for
53//! tests and single-process embedding. This crate never assumes what the host's store looks like.
54//!
55//! OPTIONAL, each its own cargo feature and every one of them off by default:
56//!
57//! - `http`, an HTTP service over all of the above, in `http` 1.x and `http-body` 1.x with no web
58//! framework and no async runtime. `axum` adds a thin `impl From<..> for axum::Router` adapter
59//! for hosts that want one; nothing else in the crate knows axum exists.
60//! - `jwt` (RFC 9068 `at+jwt` access tokens and an RFC 7517 JWKS document), `dpop` (RFC 9449
61//! sender-constrained tokens), `mtls` (RFC 8705 certificate-bound tokens and client
62//! authentication), `client-assertion` (RFC 7523 `private_key_jwt` and `client_secret_jwt`).
63//! - `jwt-p256`, THE BUILT-IN ES256 BACKEND, and the one to reach for first: `jwt` compiles the
64//! RFC 7515 machinery and the [`jwt::Es256Signer`] / [`jwt::Es256Verifier`] seams but SIGNS
65//! NOTHING BY ITSELF, because where a private key lives is the host's decision (see the
66//! [`jwt`] module docs). `jwt-p256` supplies [`jwt::EcdsaP256Key`] and installs
67//! [`jwt::P256Verifier`] as the default, which is what a host with no opinion about its key
68//! wants; a host with an HSM or a KMS installs its own signer instead and takes no `p256`.
69//! The features are ADDITIVE rather than exclusive, so both at once compiles and the installed
70//! signer wins. `jwt-pkcs8` adds PKCS#8 DER loading on top of `jwt-p256`, for a host whose key
71//! arrives as the DER its KMS or `openssl` already emits rather than as a raw scalar.
72//! - `par` and `jar` (RFC 9126 pushed authorization requests, RFC 9101 signed request objects),
73//! `rar` (RFC 9396 `authorization_details`), `token-exchange` (RFC 8693), `consent` (consent
74//! records, withdrawal with a revocation cascade, and RFC 9470 step-up authentication),
75//! `resource-metadata` (RFC 9728).
76//! - `cimd` (draft-ietf-oauth-client-id-metadata-document-01 client identifier metadata
77//! documents),
78//! which is VALIDATION ONLY: the HOST fetches the document at the client identifier URL and
79//! hands this crate the bytes, because this crate makes no outbound HTTP request. See [`cimd`]
80//! for the full list of what that leaves with the host.
81//! - `test-util`, a RUNNABLE conformance harness for the [`store::Storage`] contract that a host
82//! runs from its own test suite against its OWN store.
83//!
84//! On docs.rs every item above is rendered with the feature that turns it on. In a local build,
85//! `cargo doc --all-features` is the equivalent view.
86//!
87// The quickstart wires the optional HTTP service, so it exists only when that feature does.
88// Written as a `doc =` attribute rather than as `//!` because a doc comment cannot be
89// conditionally compiled, and a code block that does not compile in the configuration it is
90// rendered under is worse than no code block.
91#![cfg_attr(
92 feature = "http",
93 doc = r#"
94# Quick start
95
96Four things a host owns, and none of them can be defaulted: a config, a store, a sweep, and the
97two seams the interactive endpoints refuse without.
98
99```no_run
100use std::sync::Arc;
101use std::time::{Duration, SystemTime};
102
103use oauth_as::{
104 ApprovalDecision, AuthorizationServer, MemoryStorage, ServerConfig, ServiceBuilder, Storage,
105};
106
107# fn wire() -> Result<(), Box<dyn std::error::Error>> {
108// The issuer identity, and where a user goes to type an RFC 8628 device code.
109let config = ServerConfig::new("https://as.example.com", "https://as.example.com/device");
110
111// MemoryStorage is single process. A multi-node host implements `Storage` itself and proves
112// its `take_*` really is an atomic remove-and-return with the `test-util` harness.
113let server = Arc::new(AuthorizationServer::new(config, MemoryStorage::new()));
114
115// THE SWEEP. Nothing in this crate reclaims an expired record; this task is the only thing
116// that does, and the device authorization endpoint takes no credential, so an unswept store
117// grows at a rate an attacker chooses.
118let sweeper = Arc::clone(&server);
119tokio::spawn(async move {
120 loop {
121 let _ = sweeper.store().sweep_expired(SystemTime::now()).await;
122 tokio::time::sleep(Duration::from_secs(60)).await;
123 }
124});
125
126// Both seams are REQUIRED: with no consent resolver the authorization endpoint answers 403
127// rather than deciding on the user's behalf. Returning `Approve` unconditionally, as here, is
128// an AUTO-APPROVING authorization server (RFC 6749 s10.12); a real host reads its own session
129// here and returns `ApprovalDecision::Respond` with a consent screen. See
130// `examples/production_server.rs` for both done properly, plus CSRF and audit.
131let service = ServiceBuilder::new(server)
132 .with_subject_resolver(|_headers| Some("user-1".to_string()))
133 .with_approval_resolver(|_request| ApprovalDecision::Approve)
134 .build()?;
135# let _ = service;
136# Ok(())
137# }
138```
139"#
140)]
141//!
142//! # Host seams: observation, throttling, and secret storage
143//!
144//! Three things a real deployment needs that this library deliberately does not do itself, each
145//! installed on the server and each costing an uninstalled host nothing (see [`events::Hooks`]):
146//!
147//! - AUDIT EVENTS ([`events::EventSink`]). This crate logs nothing on its own. A host that wants
148//! to see issuance, refusal, or the two compromise events (authorization code replay, refresh
149//! token reuse) installs a sink. Events carry no credential of any kind; see the [`events`]
150//! module docs for the rule and for why the refresh `family_id` is safe to carry.
151//! - RATE LIMITING ([`events::RateLimiter`]). This crate never sees a request, so it has no IP,
152//! TLS peer, session or caller identity to key a counter on, and a throttle keyed on the things
153//! this crate DOES see is the only kind it can write. It writes that one:
154//! [`rate_limit::FixedWindowRateLimiter`] is a per-process, in-memory floor, and the device path
155//! already consults it — [`server::AuthorizationServer::approve_device`] asks the installed
156//! limiter on its first statement, BEFORE the user code is looked up, which is what RFC 8628
157//! section 5.1 means when it makes the user code's entropy adequate only IN COMBINATION WITH
158//! rate limiting of code entry. What remains the host's is the part a library cannot do: the
159//! limiter must be INSTALLED ([`server::AuthorizationServer::with_rate_limiter`]) — nothing is
160//! throttled until it is — and a multi-node deployment still owes a SHARED limiter behind the
161//! same trait, because a per-process count of an attacker spread over `N` nodes is `N` times too
162//! generous.
163//! - CLIENT SECRET STORAGE ([`client::SecretHash`], [`client::SecretVerifier`]). Hosts should
164//! store a one-way verifier, not the secret. The built-in scheme needs no host code; a host
165//! whose policy names argon2id or an HSM installs a verifier.
166//! - WHEN AND HOW THE USER LOGGED IN (the `consent` module's `Authentication`, behind the
167//! `consent` feature). This crate cannot authenticate anybody and will not grow a login page, so a
168//! host that wants RFC 9470 step-up authentication REPORTS when and how it authenticated
169//! the user; the library records that report and enforces `max_age` and `acr_values`
170//! against it. The report is taken at face value, because there is nothing here that
171//! could check it. See the `consent` module docs for the whole boundary.
172//! - WHO MAY REGISTER ([`registration::RegistrationPolicy`]). RFC 7591 dynamic client registration
173//! is OFF unless [`server::ServerConfig::registration`] is set, and even then every registration
174//! is REFUSED until a policy is installed. RFC 7591 section 5: an open registration endpoint
175//! lets anyone on the internet mint a client, which weakens every threat model that assumed
176//! controlling a registered client was hard. See the [`registration`] module docs before
177//! enabling it.
178//!
179//! # Zero cost until enabled
180//!
181//! A host that compiles this crate in but never turns it on must pay nothing at runtime. The crate
182//! keeps that promise structurally: there are NO global statics, NO lazy singletons, NO background
183//! tasks, and no allocation at load time. The only allocation entry point is
184//! [`server::AuthorizationServer::new`] (plus whatever `Storage` the host constructs to pass in),
185//! so "enabled by config" for a host means exactly "construct the value when config says so".
186//!
187//! # THE SWEEP: an obligation that comes with the no-background-tasks promise
188//!
189//! "No background tasks" has a price and the HOST pays it. Nothing here reclaims an expired
190//! record; [`store::Storage::sweep_expired`] does, and it runs when the host calls it and at no
191//! other time. A host that never calls it has a store that only grows, and the growth is
192//! ATTACKER-PACED: the RFC 8628 section 3.1 device authorization endpoint takes no credential
193//! from a public client, so anyone who can open a socket can allocate a device grant per request
194//! forever. Expiry is enforced on read, so an unswept deployment is not insecure, it is unbounded,
195//! which is a process that dies rather than a grant that leaks.
196//!
197//! So: spawn one task per process, on an interval well under the shortest artifact lifetime
198//! ([`server::ServerConfig::device_code_ttl`], 600 seconds by default), log a failure and retry on
199//! the next tick rather than returning. `examples/production_server.rs` does exactly that, with
200//! the reasoning, alongside every other seam a real deployment has to wire.
201//!
202//! # A worked production wiring
203//!
204//! `examples/production_server.rs` is the one to copy: it wires the storage contract, a rate
205//! limiter, a real consent screen, the device form's CSRF tokens, a session-backed subject
206//! resolver, the sweeper, an audit sink and signing key management, and says at each site what
207//! breaks if you get it wrong. `examples/conformance_server.rs` is NOT: it is a fixture for the
208//! black-box harness, and it auto-approves consent, disables the CSRF protection and signs with a
209//! key printed in an RFC.
210//!
211//! # Concurrency contract
212//!
213//! Single-use artifacts (device codes at redemption, rotating refresh tokens) are consumed through
214//! the storage trait's atomic `take_*` operations. [`store::MemoryStorage`] satisfies the contract
215//! with a mutex; a multi-node host must back `take_*` with a genuinely atomic remove-and-return
216//! (compare-and-set, `DELETE ... RETURNING`, or equivalent) or single-use guarantees become
217//! per-node only.
218
219// MODULE DECLARATIONS carry no doc comment of their own, deliberately. Each module's summary
220// lives in its OWN file as `//!` docs, in one place, next to the code it describes; a second
221// summary here would be a second thing to keep true. The `doc(cfg)` attributes are what tell a
222// docs.rs reader which feature each one needs, and they say it on the module page too, which a
223// sentence written here never did.
224pub mod authorization;
225#[cfg(feature = "cimd")]
226#[cfg_attr(docsrs, doc(cfg(feature = "cimd")))]
227pub mod cimd;
228pub mod client;
229#[cfg(feature = "client-assertion")]
230#[cfg_attr(docsrs, doc(cfg(feature = "client-assertion")))]
231pub mod client_assertion;
232#[cfg(feature = "consent")]
233#[cfg_attr(docsrs, doc(cfg(feature = "consent")))]
234pub mod consent;
235// Ungated: the macro it exports forwards the feature-gated methods under their own `#[cfg]`, so
236// this module is meaningful in every build and costs nothing in any of them (a `macro_rules!`
237// definition compiles to no code until it is used).
238pub mod delegate;
239pub mod device;
240#[cfg(feature = "dpop")]
241#[cfg_attr(docsrs, doc(cfg(feature = "dpop")))]
242pub mod dpop;
243pub mod error;
244pub mod events;
245pub mod grant;
246// PRIVATE, and one function long: the lower-case hex encoder that `server` (device codes,
247// authorization codes, opaque tokens) and `client` (the stored secret verifier) both need. It sits
248// here for the reason `skew` does, and it was two copies of one loop until 0.9.1.
249mod hex;
250#[cfg(feature = "http")]
251#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
252pub mod http;
253#[cfg(feature = "jwt")]
254#[cfg_attr(docsrs, doc(cfg(feature = "jwt")))]
255pub mod jwt;
256pub mod metadata;
257#[cfg(feature = "mtls")]
258#[cfg_attr(docsrs, doc(cfg(feature = "mtls")))]
259pub mod mtls;
260#[cfg(any(feature = "par", feature = "jar"))]
261#[cfg_attr(docsrs, doc(cfg(any(feature = "par", feature = "jar"))))]
262pub mod par;
263pub mod pkce;
264#[cfg(feature = "rar")]
265#[cfg_attr(docsrs, doc(cfg(feature = "rar")))]
266pub mod rar;
267pub mod rate_limit;
268pub mod registration;
269#[cfg(feature = "resource-metadata")]
270#[cfg_attr(docsrs, doc(cfg(feature = "resource-metadata")))]
271pub mod resource_metadata;
272pub mod scope;
273pub mod server;
274// A RUNNABLE conformance harness for the `Es256Signer` and `Es256Verifier` contracts, for a HOST
275// to run against the ES256 backend it is about to deploy. Behind `test-util`, which adds nothing
276// to a default build. The module's own `//!` docs are the documentation.
277#[cfg(all(feature = "test-util", feature = "jwt"))]
278#[cfg_attr(docsrs, doc(cfg(all(feature = "test-util", feature = "jwt"))))]
279pub mod signer_conformance;
280// PRIVATE, and one item long: the clock-skew allowance that `client-assertion` and `dpop` both
281// PUBLISH. It lives here because those features are independent and none can own a constant the
282// others must still see; the two that published it re-export it, so the public paths are unchanged.
283//
284// `jar` joined them when RFC 9101 request objects started honouring `nbf`, which is the third
285// independent feature to need the same number. That is the argument this module was created by:
286// before it existed, `client-assertion` and `dpop` each carried their own
287// `Duration::from_secs(60)` and had drifted. A third private copy in `par.rs` would have been the
288// same mistake a third time.
289#[cfg(any(feature = "client-assertion", feature = "dpop", feature = "jar"))]
290mod skew;
291#[cfg(feature = "test-util")]
292#[cfg_attr(docsrs, doc(cfg(feature = "test-util")))]
293pub mod storage_conformance;
294pub mod store;
295pub mod token;
296#[cfg(feature = "token-exchange")]
297#[cfg_attr(docsrs, doc(cfg(feature = "token-exchange")))]
298pub mod token_exchange;
299
300pub use authorization::{
301 AuthorizationCodeRecord, AuthorizationCodeState, AuthorizationError,
302 AuthorizationErrorRedirect, AuthorizationRequest, AuthorizationResponse, CodeChallengeMethod,
303 ResponseType, ValidatedAuthorizationRequest,
304};
305#[cfg(feature = "cimd")]
306#[cfg_attr(docsrs, doc(cfg(feature = "cimd")))]
307pub use cimd::{
308 CimdError, CimdPolicy, ClientIdUrl, ValidatedClientIdDocument, MAX_CLIENT_ID_DOCUMENT_BYTES,
309 MAX_CLIENT_ID_URL_BYTES,
310};
311pub use client::{Client, ClientAuth, ClientId, DynamicRegistration, SecretHash, SecretVerifier};
312#[cfg(feature = "client-assertion")]
313#[cfg_attr(docsrs, doc(cfg(feature = "client-assertion")))]
314pub use client_assertion::{
315 AssertionFailure, AssertionKeys, VerifiedAssertion, CLIENT_ASSERTION_TYPE, CLIENT_SECRET_JWT,
316 MAX_ASSERTION_LIFETIME, MIN_CLIENT_SECRET_JWT_KEY_LENGTH, PRIVATE_KEY_JWT,
317};
318#[cfg(feature = "consent")]
319#[cfg_attr(docsrs, doc(cfg(feature = "consent")))]
320pub use consent::{
321 step_up_challenge, Authentication, AuthenticationRequirement, ConsentRecord, StepUpFailure,
322 MAX_CONSENT_RESOURCES,
323};
324pub use device::{DeviceAuthorizationResponse, DeviceGrant, DeviceGrantState};
325#[cfg(feature = "dpop")]
326#[cfg_attr(docsrs, doc(cfg(feature = "dpop")))]
327pub use dpop::{
328 DpopFailure, VerifiedProof, DPOP_HEADER, DPOP_TOKEN_TYPE, MAX_PROOF_AGE, MAX_PROOF_BYTES,
329};
330pub use error::{ErrorCode, ErrorResponse};
331pub use events::{
332 Attempt, AttemptOutcome, ClientAuthFailure, Event, EventSink, Hooks, RateLimitDecision,
333 RateLimiter,
334};
335pub use grant::GrantType;
336#[cfg(feature = "http")]
337#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
338pub use http::{
339 ApprovalDecision, ApprovalRequest, ApprovalResolver, AuthorizationService, Body, CsrfTokenHook,
340 Response, ServiceBuilder, ServiceError, SubjectResolver, MAX_BODY_BYTES, MAX_FORM_PARAMETERS,
341};
342// `AuthenticationReporter` is the RFC 9470 seam and exists only when `consent` does, so its
343// re-export carries the same PAIR of gates the item itself carries. A single `http` gate here
344// would not compile without `consent`, and an `all(...)` narrower than the item would hide it.
345#[cfg(all(feature = "http", feature = "consent"))]
346#[cfg_attr(docsrs, doc(cfg(all(feature = "http", feature = "consent"))))]
347pub use http::AuthenticationReporter;
348// THE `jwt` MODULE'S ROOT PRESENCE, added in 0.9.1. Every other module's headline types are
349// re-exported here, and this list stepped from `metadata` straight to `mtls`, so the type of a
350// `pub` field on `ServerConfig` (`AccessTokenFormat`), the type `AuthorizationServer::jwks`
351// returns (`Jwks`), and the two traits the `jwt` feature exists to publish (`Es256Signer`,
352// `Es256Verifier`) had no path from the crate root at all. The gate below is EACH ITEM'S OWN
353// `#[cfg]`, not a convenient wider one: a re-export narrower than its item is an absence rather
354// than an error, and this crate has already shipped that bug once (see `token::Confirmation`).
355#[cfg(feature = "jwt")]
356#[cfg_attr(docsrs, doc(cfg(feature = "jwt")))]
357pub use jwt::{
358 AccessTokenFormat, Audience, Es256Signer, Es256Verifier, Jwk, Jwks, JwtConfig, JwtError,
359 PublicJwk, SignerError, VerifyError,
360};
361// NARROWER on purpose: these three are the BUILT-IN backend, which `jwt` deliberately does not
362// carry (see the `jwt-p256` note in Cargo.toml). `KeyError` comes with them because it is what
363// `EcdsaP256Key`'s constructors return, and a host that cannot name it cannot match on it.
364#[cfg(feature = "jwt-p256")]
365#[cfg_attr(docsrs, doc(cfg(feature = "jwt-p256")))]
366pub use jwt::{EcdsaP256Key, KeyError, P256Verifier};
367pub use metadata::{well_known_path, AuthorizationServerMetadata, WELL_KNOWN_PATH};
368#[cfg(feature = "mtls")]
369#[cfg_attr(docsrs, doc(cfg(feature = "mtls")))]
370pub use mtls::{
371 CertificateThumbprint, ClientCertificate, ExpectedSubject, MtlsClientRegistration,
372 MtlsRegistrationError, RegisteredCertificates, SELF_SIGNED_TLS_CLIENT_AUTH, TLS_CLIENT_AUTH,
373 TLS_CLIENT_AUTH_SAN_DNS, TLS_CLIENT_AUTH_SAN_EMAIL, TLS_CLIENT_AUTH_SAN_IP,
374 TLS_CLIENT_AUTH_SAN_URI, TLS_CLIENT_AUTH_SUBJECT_DN,
375};
376#[cfg(feature = "jar")]
377#[cfg_attr(docsrs, doc(cfg(feature = "jar")))]
378pub use par::{
379 JarConfig, RegisteredRequestObjectKey, RequestObjectAlg, RequestObjectKeyError,
380 RequestObjectKeys, REQUEST_OBJECT_SIGNING_ALGS, REQUEST_OBJECT_TYP,
381};
382#[cfg(feature = "par")]
383#[cfg_attr(docsrs, doc(cfg(feature = "par")))]
384pub use par::{
385 ParConfig, PushedAuthorizationRequest, PushedAuthorizationResponse, REQUEST_URI_PREFIX,
386};
387#[cfg(feature = "rar")]
388#[cfg_attr(docsrs, doc(cfg(feature = "rar")))]
389pub use rar::{
390 AuthorizationDetail, AuthorizationDetails, MAX_AUTHORIZATION_DETAILS_BYTES,
391 MAX_AUTHORIZATION_DETAILS_DEPTH, MAX_AUTHORIZATION_DETAILS_ELEMENTS,
392};
393pub use rate_limit::{
394 FixedWindowRateLimiter, RateLimitConfig, MAX_TRACKED_CLIENT_ID_LEN, MIN_WINDOW,
395};
396pub use registration::{
397 ClientInformation, ClientMetadata, RegistrationAttempt, RegistrationConfig,
398 RegistrationDecision, RegistrationErrorCode, RegistrationErrorResponse, RegistrationFailure,
399 RegistrationPolicy, MAX_REGISTERED_REDIRECT_URIS,
400};
401#[cfg(feature = "resource-metadata")]
402#[cfg_attr(docsrs, doc(cfg(feature = "resource-metadata")))]
403pub use resource_metadata::{
404 BearerMethod, ProtectedResourceConfig, ProtectedResourceMetadata,
405 PROTECTED_RESOURCE_WELL_KNOWN_PATH,
406};
407pub use scope::{Scope, ScopeSet};
408// `DeviceApprovalError` is re-exported here as of 0.2.0: a host's verification UI has to match on
409// it to tell "unknown code" from "too many attempts", and having to reach into `server::` for the
410// error type of a re-exported method was an oversight rather than a decision.
411pub use server::{
412 AuthorizationServer, ClientCredential, Clock, DeviceApprovalError, ResourceServerRegistration,
413 ServerConfig, SystemClock, TokenRequest, TokenRequestContext, UserApproval,
414 MAX_RESOURCE_INDICATORS, MIN_USER_CODE_LENGTH,
415};
416// `RevocationBarrier` and `WriteOutcome` are here for the reason the comment above gives: a
417// re-export narrower than its item is an absence rather than an error. A host implementing
418// `Storage` MUST name `WriteOutcome`, because it is what `put_token`, `put_refresh_token` and
419// `put_pushed_authorization_request` return, and `RevocationBarrier` is what its own predicate
420// matches on. Both shipped at 0.9.1 reachable only through `oauth_as::store::`, which is exactly
421// the inconsistency this rule exists to prevent.
422//
423// `RevocationWindow` JOINED THEM, and it was the strongest case of the three while being the one
424// left out. It is a BY-VALUE parameter of `delete_client`, `revoke_token_family` and
425// `revoke_consent`, so a host cannot write those signatures at all without spelling it: a store may
426// satisfy `RevocationBarrier` entirely in SQL and never match on the type, but there is no way to
427// declare a parameter whose type you cannot name. This crate's own Postgres backend paid for the
428// omission four times over, writing `oauth_as::store::RevocationWindow` inline at each site.
429// `tests/host_api_shape.rs`'s `every_type_in_a_storage_signature_is_reexported_at_the_crate_root`
430// now derives the whole list from `Storage`'s signatures rather than leaving it to be noticed.
431pub use store::{
432 MemoryStorage, RevocationBarrier, RevocationWindow, Storage, StorageError, WriteOutcome,
433};
434// The GATE MATCHES THE TYPE's, which is `any(dpop, mtls)`: `IntrospectionResponse::cnf` is a
435// public field under that same pair, so an `mtls`-only host (RFC 8705 certificate-bound tokens,
436// which is the whole reason such a host exists) was handed a value it could not name here. It
437// could still reach `oauth_as::token::Confirmation`, which is why nothing failed to compile; a
438// re-export that is NARROWER than the item it re-exports is an absence, not an error.
439#[cfg(any(feature = "dpop", feature = "mtls"))]
440#[cfg_attr(docsrs, doc(cfg(any(feature = "dpop", feature = "mtls"))))]
441pub use token::Confirmation;
442pub use token::{
443 IntrospectionResponse, IssuedToken, RefreshTokenRecord, RefreshTokenState, TokenResponse,
444 TokenType, TokenTypeHint,
445};
446#[cfg(feature = "token-exchange")]
447#[cfg_attr(docsrs, doc(cfg(feature = "token-exchange")))]
448pub use token_exchange::{
449 ActClaim, ExchangeSemantics, ExchangedToken, TokenExchange, TokenExchangeRequest,
450 TokenExchangeResponse, TokenTypeIdentifier, MAX_AUDIENCE_VALUES, TOKEN_EXCHANGE_GRANT_URN,
451};