pub async fn authenticate<'a>(
candidates: impl IntoIterator<Item = &'a str>,
static_token: Option<&str>,
oauth: Option<&OAuthValidator>,
) -> Result<Credential, TokenRejection>Expand description
Check every candidate credential against every configured mechanism, and accept if ANY candidate satisfies ANY mechanism.
candidates is every value that could carry a credential — typically the
token from Authorization: Bearer <token> and, for an application that also
takes one, the value of a raw API-key header — in any order, however many are
present. The presence of one candidate never decides whether another is
looked at, so an invalid credential in one place cannot hide a valid one in
another.
Candidates are used verbatim; an empty or whitespace-only candidate counts as
absent. static_token of None or Some("") means no static token is
configured (a blank secret must never be matchable); oauth of None means
OAuth is off. With neither configured nothing can succeed: this function
never passes a request through. Deciding to run without authentication is the
caller’s explicit choice, made before calling it (see
crate::static_token_policy).
§Order
Every candidate is first compared with the static token, in constant time
(subtle; the lengths are not hidden — see StaticTokens’ “Security”,
the same comparison over a one-entry set). A match is decisive and needs no
network, so the common static-token request never reaches the JWT machinery
or depends on the authorization server being up. Only then is every
candidate validated through OAuth, in order, until one is accepted — first
against the signing keys already held, and only if that accepts none of them
is a candidate whose kid is not held allowed to trigger a key refetch. A
foreign JWT in one source therefore never makes a request wait on the
authorization server when another candidate’s key is already cached.
§Result
Okas soon as any candidate is accepted.- Otherwise
TokenRejection::InsufficientScopeif any candidate was a valid OAuth token lacking a required scope: “this credential is fine but not sufficient” (RFC 6750’s 403) is the more useful answer when it is true of any of them. - Otherwise
TokenRejection::Missingif there was no non-blank candidate. - Otherwise
TokenRejection::Invalidcarrying the first candidate’sInvalidToken— the validator’s, when OAuth is configured, kind included; without OAuth,InvalidTokenKind::StaticTokenMismatch(orInvalidTokenKind::NoMechanismwith nothing configured). Its detail is for logs only; never send it to the caller.
Map a refusal to a response the same way the axum layer does: 403 with
OAuthValidator::insufficient_scope_challenge for InsufficientScope,
401 with OAuthValidator::invalid_token_challenge for everything else,
and (with OAuth configured) the challenge in WWW-Authenticate on both.
§Errors
A TokenRejection, chosen as described under “Result” above. This
function logs nothing; logging the refusal is the caller’s job.
§Panics
Outside a Tokio 1.x runtime, when an OAuth candidate’s signing key has to
be fetched (see OAuthValidator’s “Runtime” section). A static-token
match, or a key already held, needs no runtime.
§Security
The static token is compared in constant time, but its length is not hidden. Candidates are never trimmed or normalized, so a static token is matched only byte for byte.
To accept several static tokens (key rotation, one key per client) and
learn which one matched, use authenticate_with_static_tokens: this
function is that one over a one-entry set, the same code.
§Examples
use oauth_resource_server::{Credential, TokenRejection, authenticate};
// Static token only (no OAuth validator): a junk value in one header does
// not stop the key in another from being accepted.
let key = Some("example-static-key");
let ok = authenticate(["junk", "example-static-key"], key, None).await;
assert_eq!(ok, Ok(Credential::StaticToken));
assert_eq!(authenticate([" ", ""], key, None).await, Err(TokenRejection::Missing));
assert!(matches!(
authenticate(["guess"], key, None).await,
Err(TokenRejection::Invalid(_))
));