OIDC sign-in fails
redirect_uri mismatch, invalid_client, state, nonce and PKCE — in both directions, with the verbatim OAuth error codes for each.
OpenID Connect — OIDC — is the modern way to hand sign-in to another system. It runs in two directions here and the failures are completely different in each, so establish which one you are debugging before you read any further.
- Qorionix as the identity provider. Another application sends its users here to sign in. You registered that application as an OIDC client. Errors surface in the other application, or as a redirect carrying
error=in the address bar. - Qorionix as the relying party. Your people sign in here using Microsoft Entra ID, Google Workspace, Okta or similar. You registered us at that provider. Errors surface in our own sign-in page.
Both halves use the same vocabulary — redirect_uri, state, nonce, PKCE — which is exactly why they get confused. Every string below is verbatim.
Qorionix as the relying party: your people sign in with your provider
The more common direction. Four public endpoints are involved, and one matters more than the rest:
GET /api/v1/federation/discover email domain -> which provider
GET /api/v1/federation/{orgID}/providers the sign-in buttons for a tenant
GET /api/v1/federation/{orgID}/{slug}/start redirects to your provider
GET /api/v1/federation/callback THE redirect URI, for the whole platformThe state, the nonce and the PKCE verifier are all minted on our side when the sign-in starts. You do not configure them and cannot supply them. What you can get wrong is everything around them.
The sign-in never leaves Qorionix
If /start refuses you get 404 no such identity provider, and you get it for every reason: an organisation identifier that is not a UUID, an unknown slug, a provider switched off, a provider missing its client secret. The reason is logged and not returned, because which check failed would tell a prober whether a tenant and provider exist. So work through the list yourself: is the provider enabled, does the slug match the record, has the client secret been set? A record with no secret cannot start a sign-in.
The provider sends the person back and the sign-in still fails
The callback also collapses most failures into 401 authentication failed, with the reason logged rather than returned. Two cases get a longer answer, because they are configuration problems rather than authentication failures:
that identity is not linked to an account in this organization. An administrator can enable just-in-time provisioning or create the account.
That covers three internal conditions: just-in-time provisioning is off, the identity resolved to nobody, or the provider did not assert that it had verified the email address. The third is worth dwelling on — we will not link an account on an unverified address, because an address a provider has not verified is one somebody may have simply typed in. Turn on verification at the provider, or link the account manually.
| Internal reason | What went wrong | Fix |
|---|---|---|
federation: authorization state is unknown, expired or already used | The state in the callback is not one we have in flight. Covers forged, expired and already-used deliberately. | Usually a stale tab, a bookmarked callback URL or a back-button re-post. Start again from the sign-in page. |
federation: id_token nonce does not match the authorization request | The token's nonce is not the one we generated. This is the replay defence: without it, a token captured from another sign-in at the same provider would be accepted here. | Rarely a configuration error. If reproducible, the provider is caching or reusing tokens. |
federation: could not read the provider's OpenID configuration | We could not fetch the discovery document. | Check the issuer URL on the record; its /.well-known/openid-configuration must be reachable from us. A trailing slash is enough to break it. |
federation: the provider refused the authorization code | The token exchange failed at the provider's end. | Wrong client ID or secret, or a redirect URI there that does not match ours exactly. |
federation: id_token validation failed | Issuer, audience, signature or expiry did not check out. | The audience must be our client ID as registered there. A signature failure usually means rotated keys and a stale published key set. |
federation: could not read the user profile from the provider | The userinfo request failed. | Usually a missing scope. Grant openid, profile and email. |
federation: provider asserted no subject | No stable identifier came back, so there is nothing to key an account link on. | Check the claim mapping at the provider. |
Two more responses you may hit: 503 the credential vault is unavailable means we could not decrypt the provider's client secret and the sign-in is refused rather than attempted — a deployment fault, not yours. 503 federated login is not fully configured on this deployment means the session issuer is not wired; also ours.
Qorionix as the identity provider: another application signs people in here
Here we issue the tokens. Errors come back as standard OAuth codes in a two-field body, {"error": ..., "error_description": ...}, or — from the authorisation endpoint — as error and error_description appended to the redirect URI along with your state.
| `error` | `error_description` | Cause and fix |
|---|---|---|
invalid_request | client_id, redirect_uri, and response_type=code are required | One of the three is missing, or response_type is not code. We support the authorisation-code flow only — implicit and hybrid are not offered. |
invalid_client | unknown client | No registered client with that client_id. Check for a copied-in whitespace character, and check you are pointed at the right Qorionix tenant. |
invalid_request | redirect_uri mismatch | The most common failure. Matched exactly against the registered list — no prefixes, no wildcards, no ignoring a trailing slash. Add the exact string your application sends. |
invalid_request | redirect_uri parse error | The value is not a parseable URL. Usually double-encoding. |
login_required | no authenticated session | The person reached /oidc/authorize without a Qorionix session. Not an error in itself — sign in first, then retry. Your application should treat this as a prompt to send the user through a fresh sign-in. |
invalid_client | (varies) | At the token endpoint: unknown client, or the client secret did not verify. Secrets are stored hashed, so we cannot tell you what yours is — rotate it and update both sides. |
invalid_grant | (varies) | The code was unknown, already used or expired; or issued to a different client; or the redirect_uri at the token endpoint differs from the one used at the authorisation endpoint; or PKCE failed. Codes are single-use, so a retried token request lands here. |
unsupported_grant_type | only authorization_code and refresh_token are supported | You sent client_credentials, password or implicit. Use an API token instead — see API tokens. |
invalid_request | malformed json | A dynamic client registration body that is not valid JSON. |
invalid_token | bearer token required | The userinfo endpoint received no bearer token. |
invalid_token | org-scoped session required | Dynamic client registration with a session that carries no organisation. See the callout below — this one has a non-obvious cause. |
PKCE, and why an old client suddenly stops working
PKCE is a short proof that the application redeeming a code is the one that asked for it. It is enforced when the client is configured to require it or whenever a code_challenge was sent at the authorisation endpoint. That second condition catches people out: if your library sends a challenge automatically — most modern ones do — it must also send the matching code_verifier at the token endpoint, or you get invalid_grant with no further explanation. The usual cause is a client that starts the flow with one library and finishes it with hand-written code. Send both halves or neither.
State and nonce when we are the provider
Be clear about whose job each is. state is yours. We echo it back on the redirect, including on an error redirect, and we do not validate it — validating it would be meaningless for us because we did not generate it. If your application is not comparing the returned state with the one it sent, it has a cross-site request forgery hole and no setting here will close it. nonce is also yours to check: if you send one it is carried into the identity token and your application should verify it matches; if you send none, no nonce claim appears. When we are the relying party, the roles reverse and we mint and check both ourselves.
The userinfo endpoint and the wrong kind of token
Two token formats live in this product and mixing them up produces a puzzling invalid_token. The access token we issue to an OIDC client is signed with an RS256 key published at /api/v1/oidc/jwks; a Qorionix console session token is HS256 and a different thing entirely. The userinfo endpoint accepts the first and rejects the second, answering WWW-Authenticate: Bearer error="invalid_token", error_description="the access token is not valid". If you are testing by hand, use the token from the token endpoint, not the one in your browser's storage.
Three closing details
No certificate material. An OIDC client is identified by a client identifier plus either a secret or PKCE; if a form is asking you for a signing certificate, that is a SAML question being asked of an OIDC object. No client-credentials grant — machine-to-machine callers use an API token. Refresh tokens rotate on every use, and a replayed one revokes its whole lineage, so a stolen refresh token is worth one use before everything from that chain answers invalid_grant. If a service loses its session after a deployment that duplicated a refresh token, that is why.
Configuration mistakes on the provider record itself are refused with their own strings, and these you do see: provider_type must be 'saml' or 'oidc', oidc provider requires issuer_url and client_id, redirect_uri is required, provider is not oidc, and SSO provider name already exists for this organization at 409.
- Single sign-on with OIDC — the setup guide for both directions.
- SAML sign-in fails — the other protocol.
- Insufficient permissions — if the sign-in works but the next call is refused.