Skip to content
TDACorpIDENTITYSign in

Error handling

Where identity's errors appear, what each OAuth error means and what to do, how the SDK reports them, and what to log.

On this page

Most integration problems are visible in an error and invisible in a log. This page says where each kind of error arrives, what it means and what your code should do, so that your application fails clearly and your logs say why.

Where errors arrive#

WhereWhat you get
The redirect back from sign-inQuery or form parameters: error, error_description, state, iss.
The pushed authorization, token, revocation and introspection endpointsA JSON body { "error": "...", "error_description": "..." }, and a status.
The authorization endpoint, for a request that cannot be sent back to youIn a browser, a redirect to identity's own error page. For any other caller, the same JSON body with status 400.
/userinfoA status with a WWW-Authenticate header, and a JSON body.
Your own APIWhatever you return. See Protect your API.

Statuses on the JSON endpoints: 400 for most errors, 401 for invalid_client at the token and revocation endpoints, 500 for server_error, and 429 for a rate limit. The pushed authorization endpoint answers invalid_client with 400, not 401. So read error, never the status alone.

The error codes#

errorMeaningWhat to do
invalid_requestA parameter is missing, malformed, repeated, or not one identity accepts. A redirect address that is not registered exactly is this too. The description names the parameter.Fix the request. Do not retry.
invalid_clientClient authentication failed: unknown or suspended client, no live key for the kid, a bad or reused assertion, or no DPoP key proved where one is required.Check the key, the assertion and your clock. See Client authentication.
invalid_grantA code, refresh token or subject token is unknown, expired, spent, bound to another key, or its session has ended (this includes a session that was pinned to a single sign-on connection which is no longer linked to the organization).Do not retry the same value. Send the person through sign-in again, or fetch a new service token.
invalid_scopeA scope the client is not registered for, or a refresh asking for more than the family holds.Ask for fewer scopes, or have the registration changed.
invalid_targetA resource or audience that is malformed, unregistered or not allowed to you, or a resource where none is allowed. A resource whose product is not installed in the organization is refused the same way.See Resource indicators.
invalid_request_uriThe request_uri is unknown, expired or already used.Start again with a new pushed request.
unauthorized_clientThe client does not hold the grant it used, or has no service identity.Have the registration changed.
unsupported_response_type, unsupported_grant_typeYou asked for something identity does not offer. Only code and four grant types exist.See Choosing an integration.
access_deniedThe person refused consent, or the account is not a member of the organization you asked for, or your product is not installed there. The answer is the same for every cause, on purpose.An ordinary outcome. Show a page that explains it.
login_required, consent_required, interaction_requiredYou sent prompt=none and the request needs a page. interaction_required is what an organization that enforces single sign-on answers.Retry without prompt=none, or show your own sign-in button.
insufficient_user_authenticationThe organization enforces single sign-on (the sso_enforcement flag, which defaults OFF) and the person's session holds no recent enough assurance from one of its connections. Status 400 at the token endpoint, with acr_values and max_age in the body (RFC 9470).Send the person through a new authorization that asks for that max_age. The SDK's rp.stepUpUrl does this. See Relying-party module.
invalid_dpop_proofThe proof is malformed, for the wrong request or key, too old or too new, or reused.See DPoP and Clock skew.
use_dpop_nonceThe proof needs the server's current nonce, in the DPoP-Nonce header.Sign a new proof with it and retry once. This is routine.
temporarily_unavailableStatus 429. Too many requests.Wait for Retry-After. See Rate limits and retries.
server_errorStatus 500. Identity failed.Retry later with a back-off.

The generated list of error codes is in Error codes. It does not include temporarily_unavailable, which is the 429 answer above.

From userinfo and resource servers#

Bearer endpoints use the WWW-Authenticate header.

StatuserrorMeaning
401none, body {"error":"unauthorized"}No credentials, or an unknown scheme. The header offers both Bearer and DPoP.
401invalid_tokenThe token is unknown, expired, revoked, for the wrong audience, or its session ended.
401use_dpop_nonceRetry with the DPoP-Nonce header's value.
403insufficient_scopeThe token is valid and does not carry the scope the endpoint needs.
400invalid_requestA malformed Authorization header, or an access token in the query string, which is refused.

A resource server you build with the SDK answers every refusal with 401 and {"error":"invalid_token"} or {"error":"invalid_dpop_proof"}, and never names a claim. When you set requireAuthentication it also answers {"error":"insufficient_user_authentication"} with the acr_values and max_age the token fell short of, in the WWW-Authenticate challenge (RFC 9470). See Resource-server verification.

What the SDK reports#

The SDK throws typed errors. All extend one base class, so a single catch can handle the family.

ClassMeaningUseful fields
ConfigurationErrorYour code or configuration is wrong, as opposed to the network.reason, such as invalid_options, invalid_key, insecure_url, endpoint_unavailable, alg_unsupported
DiscoveryErrorDiscovery could not be read or does not match the issuer.reason, such as unavailable, malformed, issuer_mismatch
OAuthEndpointErrorA pushed authorization, token or revocation request failed.reason, status, oauthError, description, retryAfterSeconds, attempts
AuthorizationResponseErrorThe redirect back is not acceptable: state or iss wrong, duplicated, transaction expired, or the server reported an error.reason, oauthError, description
IdTokenErrorThe ID token failed validation.reason, such as nonce, signature, exp
LogoutTokenErrorA back-channel logout token failed validation.reason, such as replay, events

OAuthEndpointError.reason is one of oauth_error (the server returned an OAuth error body), rate_limited, server_error, malformed_response or transport (no response). Treat description as untrusted text from the network. A rate_limited error is what is left after the SDK's own bounded retry: attempts says how many requests it made. See Rate limits and retries.

The resource-server verifier and the decision client check their options when you create them and throw a plain RangeError for an algorithm other than EdDSA or ES256 and for a clockToleranceSeconds outside 0 to 300. The relying party and the service client report the same mistakes as ConfigurationError with reason invalid_options.

The route helpers turn a failed callback into a fixed answer: 400 with one of invalid_tenant (from the login route, when getTenant returns something that is not a tenant id), provider_error, missing_state, missing_transaction, invalid_transaction, state_mismatch, invalid_response, token_exchange_failed or token_verification_failed, and 500 for internal_error. The body repeats no text from identity. Pass onError to render your own page.

What to show the person#

Almost never the error. A person cannot act on invalid_grant. Show a short message that says sign-in did not finish, a button to try again, and a way to get help. Put the code in the log and, if you like, a request reference on the page.

What to log#

Log the error code, the endpoint, the client id, the status, Retry-After if present, the description, and a correlation id of your own. The error page identity shows in a browser carries a request reference. Quote it when you ask TDACorp for help.

Never log tokens, refresh tokens, authorization codes, client assertions, DPoP proofs or private keys. Treat every error_description as text from outside: log it, and do not render it as HTML or parse it.

Retry or not#

ErrorRetry?
use_dpop_nonceOnce, at once, with a new proof and a new assertion.
temporarily_unavailable, server_error, transportWith back-off, within limits. The SDK itself retries only a 429, a bounded number of times, and never a server_error or transport failure.
Everything elseNo. The request or the registration needs to change.

Verified against commit 5d85077