pub enum AuthError {
Show 16 variants
NonceMissing,
NonceMismatch,
NonceConfigEmpty,
AtHashMissing,
AtHashMismatch,
CHashMissing,
CHashMismatch,
AzpMissing,
AzpMismatch,
AuthTimeMissing,
AuthTimeStale,
AcrMissing,
AcrNotAllowed,
UnknownClaim(String),
CatMismatch(String),
Jose(SharedAuthError),
}Expand description
Verification errors specific to the OIDC id_token profile.
Shared JOSE errors (M01-M16a algorithm/header, M17-M30 registered
claims) reach this enum via the Jose carrier variant — the engine
submodules emit access_token::AuthError, the id_token verify entry
re-wraps. This is a deliberate seam: it lets the id_token surface
stay narrow (only profile-specific variants enumerated here) while
reusing the JOSE checks structurally.
Variants§
NonceMissing
M66: nonce claim is absent from the id_token payload. RFC says
nonce is conditionally required (only when the RP sent one in the
Auth Request) — but the engine’s VerifyConfig::id_token
constructor requires expected_nonce, so reaching this error
means the RP requested nonce binding and the IdP failed to honor
it. Treat as suspect: either an issuer drift or a token forged
against an older VerifyConfig.
NonceMismatch
M66: payload nonce is present but does not match the
expected_nonce the RP stored at the auth request boundary.
Canonical replay-attack signal — the attacker is presenting an
id_token issued for a different session.
NonceConfigEmpty
Nonce::new("") was called. Construction-time invariant guard;
reaching this at runtime means consumer code violated the
non-empty contract. Surfaced as a verification error rather than
a panic so the consumer gets a structured rejection it can audit.
AtHashMissing
M67: at_hash claim absent from payload while the verifier was
configured with an expected access_token binding (i.e.
VerifyConfig::with_access_token_binding was called). RFC says
at_hash is conditionally required (only when the response_type
includes token — hybrid + implicit flows); reaching this error
means the RP told the engine to expect at_hash and the IdP failed
to honor it. Either issuer drift or a substitution attempt.
AtHashMismatch
M67: payload at_hash is present but does not match the SHA-256
leftmost-128b base64url of the access_token the RP supplied via
with_access_token_binding. Canonical access-token-substitution
signal: an attacker is presenting an id_token issued for a
different access_token than the one the RP just received.
CHashMissing
M68: c_hash claim absent from payload while the verifier was
configured with an expected authorization-code binding. Mirror of
M67 for the authorization-code flow (OIDC Core §3.3.2.11) — fires
only when with_authorization_code_binding was called.
CHashMismatch
M68: payload c_hash is present but does not match the SHA-256
leftmost-128b base64url of the authorization code the RP received
at the redirect_uri. Canonical code-substitution signal: an
attacker is presenting an id_token issued for a different code
than the one the RP is about to exchange at the token endpoint.
AzpMissing
M69: azp (authorized party) claim absent while the id_token has
multiple audiences. OIDC Core §2 SHOULD requires azp on multi-aud
tokens; Phase 7 elevates to MUST. Reaching this error means the
IdP issued a multi-aud id_token without naming the authorized
party — either an issuer drift or a substitution attempt.
AzpMismatch
M69: payload azp is present but does not equal the RP’s
client_id (sourced from cfg.shared.audience). Fires regardless
of aud cardinality — §2 mandates the equality unconditionally
when azp is present. Canonical client-substitution signal: the
id_token was authorized for a sibling client and is being replayed
against this RP.
AuthTimeMissing
M70: auth_time claim absent from payload while the verifier
was configured with a max_age window. OIDC Core §3.1.3.7 says
auth_time is REQUIRED when max_age was requested; reaching this
error means the RP told the engine to gate freshness and the
IdP failed to honor it — IdP misconfiguration. Operator
response: investigate the IdP, not the user session.
AuthTimeStale
M70: now - auth_time > max_age. The user authenticated too
long ago for this RP’s freshness policy. Distinct from
AuthTimeMissing because the operator response differs:
Stale = re-authenticate the user (force OIDC prompt=login);
Missing = IdP misconfig.
AcrMissing
M71: acr claim absent from payload while the verifier was
configured with acr_values. OIDC Core §3.1.3.7 SHOULD elevated
to MUST per Phase 7 strictness — RPs that requested a specific
authentication context refuse tokens that don’t assert one.
AcrNotAllowed
M71: payload acr is present but not in the RP’s acr_values
allowlist. Canonical step-up bypass signal: the IdP authenticated
the user at a weaker level than this RP requires for the
requested operation. Comparison is case-sensitive (URN values
are case-sensitive by spec); case-folding would silently admit
downgrades.
UnknownClaim(String)
M72: id_token payload contains a claim outside the per-scope
allowlist (S::names()). Structurally mirrors M45’s
access_token::AuthError::UnknownClaim but is profile-aware: the
permitted set is derived from the type-level scope witness S,
so the same wire payload is accepted at Claims<EmailProfile>
and refused at Claims<Openid>.
Strict-refuse from day 1 (β1): no leniency flag, no silent
stripping. The carried name is the first offending claim — audit
logs distinguish a forgery (backdoor) from issuer drift
(email at Openid scope) by reading the variant payload.
Operator response depends on the name: investigate IdP scope
emission policy or refuse the consumer’s scope drift.
CatMismatch(String)
M29-mirror (Phase 10.10): id_token payload carries a cat claim
whose value is not "id". Structurally mirrors access-token’s
M29 TokenTypeMismatch (engine::check_claims::run line 137-140
— refuses anything other than cat="access"); closes the
asymmetry where id_token::verify previously had no profile-
routing assertion and relied on M72 BASE_CLAIMS-omission to
implicitly forbid cat. With cat now in BASE_CLAIMS (so
self-issued tokens round-trip via M72), the value gate moves to
this dedicated check.
Carries the offending value so audit logs distinguish:
CatMismatch("access")— an attacker presenting an id_token with a forgedcatto make it look like an access token (the substitution attack M73 also defends against from the other side).CatMismatch("")— payload missingcatentirely, either issuer drift (a non-PAS OIDC IdP that doesn’t emit ppoppo’s profile-routing claim) or a stripped-claims forgery attempt.CatMismatch("<other>")— bespoke forgery; the variant payload is itself the audit signal.
Jose(SharedAuthError)
JOSE wire-format error from the shared engine pipeline.
Algorithm whitelist, header attack surface, serialization shape,
and structural rejections (oversize, JWE, JSON-form) all surface
here. RFC 9068-specific errors (M17-M30 registered claims, M35-M45)
stay on access_token::AuthError and never reach this enum.
Trait Implementations§
Source§impl Error for AuthError
impl Error for AuthError
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()