Skip to main content

oauth_resource_server/
lib.rs

1// The crate documentation IS the README, so its Rust snippets run as doctests
2// and the two cannot drift. Those snippets use the `serde`, `env` and `axum`
3// features, so the README is included only when all three are on (as with
4// `--all-features`, which CI and docs.rs use); any narrower build gets the
5// short pointer below instead, rather than doctests that cannot compile.
6#![cfg_attr(
7    all(feature = "serde", feature = "env", feature = "axum"),
8    doc = include_str!("../README.md")
9)]
10// Linked for rustdoc readers; the README itself has no intra-doc links, since
11// GitHub and crates.io would render them as literal brackets.
12#![cfg_attr(
13    all(feature = "serde", feature = "env", feature = "axum"),
14    doc = "
15## API map
16
17| Item | Role |
18|---|---|
19| [`OAuthConfig`], [`OAuthConfig::resolve`] | The unvalidated settings, and their all-or-nothing validation into a [`ResolvedOAuthConfig`] or a [`ConfigError`]. [`KeyNaming`] decides how problems name settings. |
20| [`OAuthValidator`] | Validates one token ([`OAuthValidator::validate`]), renders the challenges and the metadata document, and keeps the signing keys fresh ([`OAuthValidator::spawn_background_refresh`]). |
21| [`OAuthValidator::builder`], [`OAuthValidatorBuilder`] | How the validator fetches its keys: extra TLS root certificates, a proxy, the fetch timeout, a key set to start from. |
22| [`OAuthValidator::key_set_status`], [`KeySetStatus`], [`RefreshError`] | A passive, no-I/O view of the signing keys held, for readiness probes and status pages ([`OAuthValidator::is_ready`]). |
23| [`AuthorizedToken`], [`TokenRejection`] | The two outcomes of a validation. |
24| [`authenticate`], [`Credential`] | Framework-free checking of several candidate credentials against a static token and OAuth. |
25| [`authenticate_with_static_tokens`], [`StaticTokens`], [`StaticTokenMatch`], [`StaticTokensError`] | The same check against several labeled static tokens (zero-downtime key rotation, one key per client), reporting which one matched. |
26| [`refusal`], [`refusal_with_static_challenge`], [`Refusal`], [`DEFAULT_STATIC_CHALLENGE`] | Framework-free mapping of a [`TokenRejection`] to its status (401/403) and `WWW-Authenticate` challenge — the same decision both layers make. |
27| [`AuthorizedToken::require_scopes`], [`MissingScopes`], [`refusal_for_scopes`], [`OAuthValidator::insufficient_scope_challenge_for`] | Per-route and per-operation scopes on top of the validator's: the all-of check, and the 403 whose challenge names the scopes that request needs. |
28| [`Algorithm`], [`parse_algorithm`], [`AlgorithmError`] | The JWS algorithms a config may allow (never HMAC or `none`). |
29| [`static_token_policy`], [`StaticTokenDecision`] | The startup decision about a static API key alongside OAuth. |
30| [`axum::AuthLayer`], [`axum::require_auth`], [`axum::metadata_router`] | The axum integration (feature `axum`), including extractors for [`Credential`], [`AuthorizedToken`] and [`StaticTokenMatch`], and the per-handler scope extractor [`axum::Scoped`]. |
31| [`http_layer::HttpAuthLayer`], [`http_layer::HttpAuthLayerBuilder`], [`http_layer::RequireScopes`] | A `tower` layer for any `http::Request<B>` service, whatever its body types (feature `tower`, implied by `axum`), and the per-route scope layer both layers share. |
32| [`env::oauth_config_from_env`], [`env::secret_from_env`], [`env::static_tokens_from_env`] | Configuration from environment variables (feature `env`), including a current and a next static key for rotation. |"
33)]
34#![cfg_attr(
35    all(feature = "serde", feature = "env", feature = "axum", feature = "mcp"),
36    doc = "| [`mcp::McpToolScopes`] | Per-tool scopes for an MCP server's JSON-RPC endpoint (feature `mcp`). |"
37)]
38#![cfg_attr(
39    all(
40        feature = "serde",
41        feature = "env",
42        feature = "axum",
43        not(feature = "mcp")
44    ),
45    doc = "| `mcp::McpToolScopes` | Per-tool scopes for an MCP server's JSON-RPC endpoint (feature `mcp`, not enabled in this build). |"
46)]
47#![cfg_attr(
48    all(
49        feature = "serde",
50        feature = "env",
51        feature = "axum",
52        feature = "metrics"
53    ),
54    doc = "| [`observability`] | The metric names the `metrics` feature reports through the `metrics` facade, and their descriptions (feature `metrics`). |"
55)]
56#![cfg_attr(
57    all(
58        feature = "serde",
59        feature = "env",
60        feature = "axum",
61        not(feature = "metrics")
62    ),
63    doc = "| `observability` | The metric names the `metrics` feature reports through the `metrics` facade (feature `metrics`, not enabled in this build). |"
64)]
65// The last row links the `testing` module, which exists only with that feature;
66// without it the row is rendered with no link, so a `serde,env,axum` doc build
67// has no unresolved intra-doc link.
68#![cfg_attr(
69    all(
70        feature = "serde",
71        feature = "env",
72        feature = "axum",
73        feature = "testing"
74    ),
75    doc = "| [`testing`] | Fixtures for your tests (feature `testing`). |"
76)]
77#![cfg_attr(
78    all(
79        feature = "serde",
80        feature = "env",
81        feature = "axum",
82        not(feature = "testing")
83    ),
84    doc = "| `testing` | Fixtures for your tests (feature `testing`, not enabled in this build). |"
85)]
86#![cfg_attr(
87    not(all(feature = "serde", feature = "env", feature = "axum")),
88    doc = "OAuth 2.0 bearer-token resource server for Rust HTTP services: JWT \
89           access-token validation against a JWKS (RFC 9068), RFC 9728 \
90           protected-resource metadata, RFC 6750 `WWW-Authenticate` challenges, an \
91           optional static API key alongside OAuth, and axum integration.\n\n\
92           The full guide is this crate's README, which becomes the crate \
93           documentation when it is built with the `serde`, `env` and `axum` \
94           features (as on docs.rs): <https://docs.rs/oauth-resource-server>."
95)]
96#![cfg_attr(docsrs, feature(doc_cfg))]
97#![warn(missing_docs)]
98#![forbid(unsafe_code)]
99
100// Without a TLS backend reqwest cannot fetch an https JWKS, and every real
101// authorization server serves its keys over https — the validator would build,
102// then fail closed on every token. Refuse at compile time instead. No cfg(test)
103// or docs exemption: `cargo test` and `cargo doc` build with the default
104// feature set, which includes `rustls-tls`.
105#[cfg(not(any(
106    feature = "rustls-tls",
107    feature = "rustls-tls-native-roots",
108    feature = "native-tls"
109)))]
110compile_error!(
111    "oauth-resource-server needs a TLS backend for JWKS fetches: enable the `rustls-tls` \
112     (default), `rustls-tls-native-roots` or `native-tls` feature"
113);
114
115mod algorithms;
116mod builder;
117mod challenge;
118pub mod config;
119mod jwks;
120mod token;
121mod validator;
122
123mod authenticate;
124mod observe;
125mod policy;
126mod refusal;
127
128#[cfg(feature = "env")]
129#[cfg_attr(docsrs, doc(cfg(feature = "env")))]
130pub mod env;
131
132#[cfg(feature = "tower")]
133#[cfg_attr(docsrs, doc(cfg(feature = "tower")))]
134pub mod http_layer;
135
136#[cfg(feature = "axum")]
137#[cfg_attr(docsrs, doc(cfg(feature = "axum")))]
138pub mod axum;
139
140#[cfg(feature = "mcp")]
141#[cfg_attr(docsrs, doc(cfg(feature = "mcp")))]
142pub mod mcp;
143
144#[cfg(feature = "metrics")]
145#[cfg_attr(docsrs, doc(cfg(feature = "metrics")))]
146pub mod observability;
147
148// Entry points for the `fuzz/` cargo-fuzz crate into internals that are not
149// public API. cargo-fuzz sets `--cfg fuzzing`; no ordinary build (including
150// docs.rs and every CI job but `fuzz.yml`) compiles this module. cargo-fuzz
151// sets the cfg for every crate in the build, so a project that fuzzes ITSELF
152// while depending on this crate compiles this one with `fuzzing` on as well.
153// That works either way: the module needs the `axum` and `testing` features
154// (`bearer_credential`, `resolved_config`), and is simply left out unless a
155// build enables both.
156#[cfg(all(fuzzing, feature = "axum", feature = "testing"))]
157#[doc(hidden)]
158pub mod __fuzz;
159
160#[cfg(any(test, feature = "testing"))]
161#[cfg_attr(docsrs, doc(cfg(feature = "testing")))]
162pub mod testing;
163
164pub use algorithms::{Algorithm, AlgorithmError, DEFAULT_ALGORITHMS, parse_algorithm};
165pub use authenticate::{
166    Credential, StaticTokenMatch, StaticTokens, StaticTokensError, authenticate,
167    authenticate_with_static_tokens,
168};
169pub use builder::OAuthValidatorBuilder;
170pub use challenge::PROTECTED_RESOURCE_METADATA_PREFIX;
171pub use config::{
172    ConfigError, ConfigProblem, DEFAULT_LEEWAY_SECS, DEFAULT_PRINCIPAL_CLAIMS,
173    DEFAULT_SCOPE_CLAIMS, KeyNaming, KeyNamingBuf, MAX_LEEWAY_SECS, MAX_TOKEN_AGE_SECS,
174    OAuthConfig, ProblemKind, ResolvedOAuthConfig,
175};
176pub use jwks::{
177    DEFAULT_FETCH_TIMEOUT, KeySetStatus, MAX_FETCH_TIMEOUT, MIN_FETCH_TIMEOUT, RefreshError,
178    RefreshErrorKind,
179};
180pub use policy::{NoAuthConfigured, StaticTokenDecision, static_token_policy};
181pub use refusal::{
182    DEFAULT_STATIC_CHALLENGE, Refusal, refusal, refusal_for_scopes, refusal_with_static_challenge,
183};
184pub use token::{AuthorizedToken, InvalidToken, InvalidTokenKind, MissingScopes, TokenRejection};
185pub use validator::{OAuthValidator, ValidatorError};