Errors & Troubleshooting
Error formats
| Endpoints | Format |
|---|---|
/authorize, /par, /token, /device_authorization, /introspect, /revoke, /userinfo, /end_session | The OAuth error object (RFC 6749 §5.2): error and error_description. Some /authorize errors are answered directly even when the redirect URI is valid, instead of on the client's callback (#81). |
/register | The registration error object (RFC 7591 §3.2.2), for example invalid_redirect_uri or invalid_client_metadata |
| Everything else, and any rate-limited request | A problem document (RFC 9457), Content-Type: application/problem+json |
A problem document looks like this:
{
"type": "urn:problem-type:invalid_credentials",
"title": "Unauthorized",
"status": 401,
"code": "invalid_credentials",
"detail": "invalid email or password",
"instance": "/v1/auth/sign-in",
"request_id": "…"
}Branch on code, not on detail. Validation failures add an errors array with one
{field, code, message} entry per field, using codes such as required, too_short,
too_long, pattern, invalid_format and not_allowed. Details are kept generic on
purpose: database errors, stack traces and secrets are logged on the server and never
returned.
Every response carries X-Request-ID, and X-Trace-Id when the request was traced.
Quote the request id when you report a problem; it finds the matching log lines and
audit events.
Problem codes
| Code | Status | Meaning |
|---|---|---|
validation_error | 400 | The request failed schema validation. See errors. |
invalid_request | 400 | The request is malformed, for example a repeated header |
invalid_credentials | 401 | Wrong username or password |
unauthorized | 401 | No usable credential |
forbidden | 403 | Not allowed, including a sign-in POST that looks cross-site |
invalid_code | 401 | A wrong MFA or recovery code |
not_found, unknown_org | 404 | No such resource, or no such organization in the path |
conflict | 409 | A concurrent change, or a duplicate |
registration_conflict | 409 | Self-registration with an email that already exists |
registration_disabled | 403 | Self-registration is off |
already_enrolled | 409 | The user already has an active TOTP factor |
too_many_requests | 429 | A sign-in throttle tripped. Retry later. |
too_many_attempts | 429 | An attempt budget is spent, for example five wrong device codes. Retrying will not help; start over. |
account_locked | 429 | The account is locked (only with reveal_lockout on) |
rate_limited | 429 | The per-IP rate limit. Honour Retry-After. |
unavailable | 503 | The feature is not available in this deployment |
internal_error | 500 | The server failed. Check its logs by request id. |
The server refuses to start
The server checks its configuration before opening any listener and names every problem it finds. The common ones:
| Message mentions | Fix |
|---|---|
issuer.url must use https | Every issuer is https, including local ones. just certs, which just up and just dev run for you, creates a local certificate. A plaintext listener behind a TLS-terminating proxy is fine; the issuer names what clients reach. |
issuer.url must not contain a path | Give the server its own host or subdomain. Every route is served from the origin root. |
cache.enabled is false | A non-loopback issuer needs Redis. Set cache.enabled and cache.address. |
keys.provider or mfa.provider is memory | A non-loopback issuer needs file providers, so keys and TOTP seeds survive restarts. |
trusted_proxies is empty | List your ingress or load balancer CIDRs in security.trusted_proxies. |
mfa.drift … is out of range | Use 0, 1 or 2. |
rate_limit.backend=redis requires cache.enabled=true | Turn on the cache, or use the memory backend. |
An audit.exclude pattern matches no action | Fix the typo. See Audit Log. |
fips.required | Run a binary built with GOFIPS140=v1.0.0 under GODEBUG=fips140=on. |
The Helm chart runs the same checks when it renders, so most of these fail
helm install with the fix attached.
Two settings fail quietly rather than at startup:
- A
--configpath that does not exist is ignored, and the server starts on defaults and environment variables. Check the startup log for the configuration it loaded. - An unset
keys.encryption_keyonly logs a warning. See Keys & Secrets.
Common problems
Sign-in is throttled for everyone at once. The server sees every request coming from
your proxy's address, so all users share one rate-limit budget and one brute-force
throttle. List the proxy in security.trusted_proxies.
Every request answers 429 rate_limited. With rate_limit.backend: redis, the
limiter fails closed while Redis is unreachable. Check Redis.
redirect_uri is rejected. Redirect URIs are compared exactly, including the scheme,
port, path and trailing slash. Loopback redirects on any port are not supported yet (#84).
/orgs/default/… answers 404 unknown_org. The main organization is served at the
root only. Use /authorize, not /orgs/default/authorize.
A refresh answers invalid_grant. The refresh token was used before (refresh tokens
rotate, and reuse revokes the whole family), it expired, or its session was ended by a
logout or a password reset. The description is the same for all of these on purpose.
Send the user through /authorize again, and make sure your application never sends two
refreshes with the same token. See
Connect an Application.
A sign-in or registration POST answers 403 forbidden, "cross-site request
rejected". The hosted UI's JSON endpoints accept only Content-Type: application/json
from the issuer's own origin. Browsers send Sec-Fetch-Site: same-origin; same-site
from a sibling subdomain is refused, and without that header Origin must equal the
issuer. Serve the UI from the issuer, and don't let a proxy rewrite these headers.
A request answers 400 invalid_request about a repeated header. Authorization,
DPoP, DPoP-Nonce, Cookie, Origin, Sec-Fetch-Site and Content-Type may
appear only once. A proxy that appends instead of replacing one of them causes this.
Each occurrence is recorded as an http.header.repeated audit event.
Users of another organization cannot sign in after a restart. The KEK was not set,
so the organization's signing keys were sealed with a key that no longer exists. Set
keys.encryption_key before creating organizations. Keys sealed under a lost KEK cannot
be recovered.
Users must re-enroll TOTP after a restart. The server ran with mfa.provider: memory,
which the startup checks allow only on a loopback issuer.
Native apps show the consent screen on every sign-in. A public client whose redirect
URI is not https is always asked, because another app could claim the same redirect.
See Sessions, Consent & Logout.
The operator reports Unreachable after certificates were renewed. The server reads
its gRPC certificate and client CA only at startup (#443). Restart it.
The admin API answers 403 for a correct token. The token needs both the
operation's scope and the role in the target organization. See
Admin API & Users.
A migration Job never finishes. The binary has no migrate-and-exit mode. Set
database.migrate: true on the Deployment instead. See
Backups & Upgrades.
The audit log's session_id filter returns nothing. No event records a session id
yet (#520).