Skip to main content

ClientAuthFailure

Enum ClientAuthFailure 

Source
#[non_exhaustive]
pub enum ClientAuthFailure { UnknownClient, SecretMismatch, RateLimited, SecretExpired, NotConfidential, NoDynamicRegistration, NoCertificatePresented, CertificateMismatch, AssertionInvalid { reason: AssertionFailure, }, }
Expand description

Why a client failed to authenticate (RFC 6749 section 5.2 invalid_client).

The WIRE collapses all of these into one invalid_client, deliberately, so an attacker cannot probe which client ids exist. The AUDIT channel separates them just as deliberately: the host is not the attacker, and “a thousand unknown client ids” and “a thousand wrong secrets for one real client” are different incidents with different responses.

Shared by BOTH planes: it is the reason carried by Event::ClientAuthenticationFailed (the token plane) and by Event::ClientRegistrationAuthenticationFailed (the RFC 7592 management plane). One vocabulary rather than two, because a host counting credential guesses wants to count the same shapes wherever they happen; WHICH plane an attempt arrived on is the event variant, not this enum, so a sink can separate them without having to learn a second set of names.

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

UnknownClient

No registration exists for the presented client_id.

§

SecretMismatch

The registration exists and the presented credential did not verify (or none was presented for a confidential client, or one was presented for a public one).

§

RateLimited

The host’s own RateLimiter refused the attempt before it was evaluated.

§

SecretExpired

The registration is dynamic (RFC 7591) and its client_secret_expires_at has passed, so the secret is no longer a credential however correct it is.

Separated from ClientAuthFailure::SecretMismatch because the response differs and the urgency differs. This is not an attack: it is a client that missed a rotation window this server announced when it registered them, and the fix is to re-register rather than to investigate. A run of these after a rotation deadline is expected; a run of them before one means the deployment’s clock or its issued lifetimes are wrong.

§

NotConfidential

The registration is PUBLIC and the endpoint the caller reached admits confidential clients only: RFC 7662 section 2.1 introspection, and the RFC 6749 section 4.4 client credentials grant.

Not a wrong credential — no credential was ever in play. A public registration has no secret, so “authenticated as a public client” is a sentence true of every caller on the internet, and naming a client id is not authentication.

IT EXISTS SO THAT THE WIRE DOES NOT HAVE TO SAY IT. Through 0.9.1 both endpoints answered this case with an invalid_client CARRYING A DESCRIPTION (“introspection requires a confidential client”), while an unknown client id and a confidential client with the wrong secret both got a BARE invalid_client — so the description sorted “this id is registered, and it is public” from everything else, which is the enumeration ClientAuthFailure::UnknownClient and ClientAuthFailure::SecretMismatch are collapsed on the wire to prevent. The description is gone; the fact is here instead, in the channel where the reader is not the attacker. It is also the sentence an operator actually needs, because the usual cause is a resource server registered with the wrong token_endpoint_auth_method rather than an attack.

§

NoDynamicRegistration

MANAGEMENT PLANE ONLY. The client_id names a client the HOST provisioned itself, which carries no RFC 7591 registration record and therefore no registration access token that could ever verify.

Separated from ClientAuthFailure::UnknownClient because it says something that one does not: the client id was REAL. A run of these is somebody walking a deployment’s static client ids looking for one that happens to be dynamically registered and therefore rewritable through RFC 7592 section 2.2; a run of UnknownClient is somebody who has not found a live id yet. The wire tells the caller neither (both are the same 401).

§

NoCertificatePresented

Available on crate feature mtls only.

The registration authenticates with RFC 8705 mutual TLS and NO certificate reached this crate. Worth separating from a mismatch: in practice it usually means the TLS terminator is not configured to request, verify or forward a client certificate, which is an operational fault affecting every mutual-TLS client at once rather than an attack on one of them.

§

CertificateMismatch

Available on crate feature mtls only.

A certificate was presented and did not match the registration (RFC 8705 section 2.1 subject values, or section 2.2 thumbprints). This one IS the attack shape: a caller holding some valid certificate trying to be a client it is not.

§

AssertionInvalid

Available on crate feature client-assertion only.

The registration exists and an RFC 7523 client assertion was presented that did not verify: a bad signature, an alg the registration does not use, an audience naming another server, an expired assertion, or a jti that had already been spent.

Separated from ClientAuthFailure::SecretMismatch because the responses differ. A run of wrong secrets is credential stuffing; a run of REPLAYED assertions is somebody who has captured a client’s traffic, which is a different incident and a much worse one.

CARRIES THE REASON, because collapsing the nine into one told the operator nothing they could act on. AssertionFailure documents itself as existing “for the host’s audit channel, where the reader is not the attacker”, and until 0.9.1 the server discarded it here, so a burst of these was indistinguishable between clock skew on the client (Expired/NotYetValid, fix NTP), a key rotation the registration did not follow (BadSignature, fix the registration), and assertions captured at another authorization server and replayed here (WrongAudience, an incident). The mutual-TLS arm beside it already forwarded its failure verbatim, which is how the omission was found.

Fields

§reason: AssertionFailure

Which of the RFC 7523 section 3 checks the assertion failed. Never reaches the wire: every one of them is the same invalid_client there.

Trait Implementations§

Source§

impl Clone for ClientAuthFailure

Source§

fn clone(&self) -> ClientAuthFailure

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for ClientAuthFailure

Source§

impl Debug for ClientAuthFailure

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for ClientAuthFailure

The OPERATOR’s sentence, never the client’s. Everything here is what the wire deliberately refuses to distinguish (see the type’s docs), so these strings must not reach a response body.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for ClientAuthFailure

Source§

impl Error for ClientAuthFailure

It is the Err payload of crate::mtls::authenticate_via_mtls, so a host handling that with ? or collecting it into a Box<dyn Error> needs this, exactly as DpopFailure and AssertionFailure do for theirs. (Plain text rather than intra-doc links: those two types are behind features this one is not, so a link would dangle in a default build.)

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl Hash for ClientAuthFailure

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for ClientAuthFailure

Source§

fn eq(&self, other: &ClientAuthFailure) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for ClientAuthFailure

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.