pub struct OAuthValidatorBuilder { /* private fields */ }Expand description
Builds an OAuthValidator with options for its metadata and JWKS
fetches; get one from OAuthValidator::builder.
With no option set, OAuthValidatorBuilder::build behaves exactly like
OAuthValidator::new (which is implemented as that). Every option is
checked by build, never earlier, and a refused one is a
ValidatorError — nothing is silently ignored. Calling an option twice
replaces the earlier value, except
OAuthValidatorBuilder::add_root_certificate_pem, which adds.
Every protection a fetched key set gets still applies whatever is set
here: the redirect policy, the https and allow_insecure_http rules,
the response size cap, the key-count cap and per-key algorithm narrowing.
Debug never prints the proxy URL (it may carry a credential) or the
initial key set, only whether they are set.
§Examples
An authorization server behind a private CA, reached through a proxy:
use std::sync::Arc;
use std::time::Duration;
use oauth_resource_server::{KeyNaming, OAuthConfig, OAuthValidator};
let resolved = OAuthConfig {
enabled: true,
issuer: "https://idp.internal.example.com/".into(),
audience: "example-api".into(),
resource: "https://api.example.com/".into(),
required_scope: Some("api:read".into()),
..OAuthConfig::default()
}
.resolve(KeyNaming::Dotted("oauth"))?
.expect("OAuth is enabled");
let ca = std::fs::read("/etc/ssl/private-ca.pem")?;
let validator = Arc::new(
OAuthValidator::builder(&resolved)
.add_root_certificate_pem(&ca)
.proxy("http://proxy.example.com:3128")
.fetch_timeout(Duration::from_secs(5))
.build()?,
);
validator.spawn_background_refresh();Implementations§
Source§impl OAuthValidatorBuilder
impl OAuthValidatorBuilder
Sourcepub fn add_root_certificate_pem(self, pem: &[u8]) -> Self
pub fn add_root_certificate_pem(self, pem: &[u8]) -> Self
Trust the certificate(s) in pem as TLS root certificates for the
metadata and JWKS fetches, in addition to the roots the TLS
feature already trusts (the Mozilla set for rustls-tls, the OS store
for rustls-tls-native-roots and native-tls) — never instead of
them. For an authorization server behind a private or internal CA.
When native-tls and a rustls feature are both enabled, reqwest uses
native-tls, so the OS store is what these are added to.
pem may hold one certificate or a bundle of several (every
CERTIFICATE block is added). Call it again to add another file. It
works with every TLS feature.
§Security
A root added here can vouch for ANY host name, not only the authorization server’s, for this validator’s fetches: whoever holds the CA’s private key can serve signing keys this validator trusts. Add the CA that actually issues the authorization server’s certificate, and nothing broader.
Pass only real CA certificates. Under rustls a trust anchor keeps
only its subject, public key and name constraints — its
basicConstraints and keyUsage are ignored — so a CA:FALSE leaf
certificate passed here becomes an anchor that can issue for any
host.
§Errors
None here; OAuthValidatorBuilder::build returns
ValidatorError::InvalidRootCertificate when pem holds no
certificate, one the TLS backend cannot parse or use, or any
private-key block (-----BEGIN … PRIVATE KEY-----): a resource server
never needs a private key, so one here is a mistake, not ignored.
§Examples
use oauth_resource_server::OAuthValidator;
let validator = OAuthValidator::builder(resolved)
.add_root_certificate_pem(&std::fs::read("/etc/ssl/private-ca.pem")?)
.build()?;Sourcepub fn proxy(self, url: impl Into<String>) -> Self
pub fn proxy(self, url: impl Into<String>) -> Self
Send the metadata and JWKS fetches through the proxy at url
(http://host:port, or https:// for a TLS connection to the proxy
itself; user:password@ in it is sent as proxy Basic auth). https
fetches are tunnelled with CONNECT, so TLS still runs end to end to
the authorization server and its certificate is still checked.
Without this, non-loopback fetches use reqwest’s own proxy handling,
untouched: the HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/NO_PROXY
environment variables and, on macOS and Windows when reqwest’s
system-proxy feature is on in the build, the system settings.
Setting a proxy here turns all of those off, NO_PROXY included.
Either way, a fetch of a loopback URL (localhost, *.localhost,
127.0.0.0/8, ::1) uses a separate client with no proxy at all: a
loopback URL names this host, which through a proxy it would not, and
a plain-http loopback fetch would cross the network in cleartext. That
client resolves every name to ::1/127.0.0.1 itself, never through
DNS, and a fetch it starts may not be redirected off loopback, so
this proxy is never skipped for a non-loopback host. (A redirect from
a non-loopback URL to a loopback one stays in the client the fetch
started with; see the README’s security model.)
§Security
The URL may carry a credential, so it never appears in a log line or
Debug other than redacted (***@), and a refused one never appears
in the error at all. A plain-http proxy on a non-loopback host needs
no opt-in: an https fetch through it is a CONNECT tunnel it cannot
read or alter, and a plain-http fetch already needed
allow_insecure_http for its own URL. A credential in such a proxy
URL, though, would cross the network in cleartext, so that is refused
unless allow_insecure_http is set (and then logged as a warn) —
use an https:// proxy URL for a proxy that authenticates.
§Errors
None here; OAuthValidatorBuilder::build returns
ValidatorError::InvalidProxy unless url is an absolute http or
https URL with a host and nothing after the port (no path, query or
fragment), with no space, control or non-ASCII character, and — when
plain http on a non-loopback host — no credential unless
allow_insecure_http is set. SOCKS proxies are not supported.
§Examples
use oauth_resource_server::OAuthValidator;
let validator = OAuthValidator::builder(&resolved)
.proxy("http://proxy.example.com:3128")
.build()
.unwrap();Sourcepub fn fetch_timeout(self, timeout: Duration) -> Self
pub fn fetch_timeout(self, timeout: Duration) -> Self
How long one metadata or JWKS request may take, connection through
the last byte of the body; DEFAULT_FETCH_TIMEOUT (10 s) unless
set.
A refresh holds the refresh lock for as long as its requests take (discovery can chain three), and a request whose key is not cached waits behind it, so this is also how long a stalled authorization server can hold up such a request.
§Errors
None here; OAuthValidatorBuilder::build returns
ValidatorError::FetchTimeoutOutOfRange outside
MIN_FETCH_TIMEOUT..=MAX_FETCH_TIMEOUT (1 s to 60 s), zero
included.
§Examples
use std::time::Duration;
use oauth_resource_server::OAuthValidator;
let validator = OAuthValidator::builder(&resolved)
.fetch_timeout(Duration::from_secs(3))
.build()
.unwrap();Sourcepub fn initial_jwks(self, json: &str) -> Self
pub fn initial_jwks(self, json: &str) -> Self
Start with the keys in json, a JWK Set ({"keys": [...]}), already
loaded: tokens they signed validate before — or without — any fetch.
For a warm start through an authorization-server outage, or a key set
shipped alongside the application. Read one from a file with
std::fs::read_to_string and pass the text.
The set goes through exactly the checks a fetched one does: the
256 KiB size cap, at most 64 keys considered, and per-key narrowing
(no HMAC oct key, no use: enc or key_ops-without-verify key,
each key limited to the configured algorithms its own type and alg
allow; an unusable entry is skipped).
The seeded keys are refreshed like fetched ones: the first refresh
that succeeds (from OAuthValidator::spawn_background_refresh,
OAuthValidator::refresh_now or an unknown kid) replaces them
with the authorization server’s set, and a failed one keeps them.
Until then, OAuthValidator::key_set_status reports them in keys
(so OAuthValidator::is_ready is true from the start), with
last_attempt and last_success still None: no fetch has happened,
and last_success only ever means the authorization server answered.
keys > 0 with last_success == None is how to tell seeded keys
apart. Seeded keys count as held, so a failed background pass retries
on the held-keys schedule (a minute, backing off to an hour), not the
5-second keyless one.
§Security
Seeded keys are trusted exactly like keys the authorization server served — until a refresh succeeds, even one it has since withdrawn. Seed only a key set taken from the authorization server itself.
§Errors
None here; OAuthValidatorBuilder::build returns
ValidatorError::InvalidInitialJwks when json is over the size
cap, not JSON, not a JWK Set, or holds no key usable under the
configured algorithms.
§Examples
use oauth_resource_server::OAuthValidator;
// `jwks`: a JWK Set saved from the authorization server's jwks_uri.
let validator = OAuthValidator::builder(&resolved)
.initial_jwks(jwks)
.build()
.unwrap();
assert!(validator.is_ready());
assert_eq!(validator.key_set_status().last_success, None);Sourcepub fn build(self) -> Result<OAuthValidator, ValidatorError>
pub fn build(self) -> Result<OAuthValidator, ValidatorError>
Build the validator.
Does no network I/O, like OAuthValidator::new: keys are fetched on
first use or by OAuthValidator::spawn_background_refresh (unless
seeded). Logs the same startup warnings new does.
§Errors
Every error OAuthValidator::new returns, checked first; then
ValidatorError::FetchTimeoutOutOfRange,
ValidatorError::InvalidRootCertificate,
ValidatorError::InvalidProxy or
ValidatorError::InvalidInitialJwks for a refused option (the
first one found, in that order).