# invalid_client

Client authentication failed at an endpoint that authenticates the client (RFC 6749 §5.2).

## Meaning

Sent with `401` and a `WWW-Authenticate: Basic` challenge by `/token`, `/introspect`,
`/revoke`, `/par` and `/device_authorization`. The causes are an unknown `client_id`, a
missing or wrong secret, a credential of another method than the one registered, a secret
for a client that has none, and a `private_key_jwt` assertion that fails a check (its
algorithm, `kid`, signature, `iss`/`sub`, `aud`, `exp` or lifetime, a reused `jti`, or a
key set that cannot be fetched or holds a private key, whether it is published at a
`jwks_uri` or registered by value as `jwks`). A public client calling
`/introspect`, and an `/introspect` or `/revoke` request with no body, get it too.
The answer and its description are the same for every cause, so it never says whether
the client exists.

## Impact

Every request the client makes with that credential fails. Users can still sign in at the
server, but the application cannot redeem the code, so they end on the application's own
error after signing in. Usually a misconfiguration: a mistyped or rotated secret, the
wrong authentication method, or a key set the server cannot read.

## Diagnosis

1. The response does not say which rule refused the credential; the audit record does.
   Find `oauth.client_authentication.attempted` by the request's `X-Request-ID` on the
   dashboard's Troubleshooting page, or filter the Audit log by client.
2. Its cause names the rule: `client_unknown`, `secret_missing`, `secret_mismatch`,
   `secret_not_registered`, `auth_method_mismatch`, `auth_method_unsupported`,
   `confidential_client_required`, `jwks_fetch_failed`, `jwks_private_key_published`, or
   one of the `assertion_*` causes.
3. Compare `token_endpoint_auth_method` in the client's registration (the dashboard's
   client page, or `spec.tokenEndpointAuthMethod` on its `OIDCClient`).

## Mitigation

- As a client developer: authenticate with the registered method and only that one. For
  `private_key_jwt`, set `iss` and `sub` to the client id, use a fresh `jti` and a short
  `exp` for each request, and sign with a key your `jwks_uri` publishes or your
  registered `jwks` holds.
- As an operator: if the secret was lost, issue a new one (for an `OIDCClient`, delete
  its Secret and the operator rotates it). A `jwks_private_key_published` cause means the
  client's signing key is public: treat it as compromised and have the owner replace it.
- As a user: nothing to do on your side; tell the application's owner.

## Escalation

A run of refusals for one client from unfamiliar addresses is someone guessing a secret;
handle it as a security incident. If a credential that should verify is refused with a
cause that does not fit, open an issue with the request id, the server version and the
recorded cause. Never attach the secret or the assertion.

---

[OAuth error runbooks](https://www.nauthera.io/docs/v1/runbooks/oauth) · Generated from [`internal/runbook/oauth.yaml`](https://github.com/nauthera/nauthera-server/blob/main/internal/runbook/oauth.yaml) in nauthera-server.
