Known incompatibilities with common OIDC libraries
authreads' /oauth/authorize accepts an exact, closed set of parameters — anything outside it is rejected, not ignored. Most generic OIDC client libraries were built against providers that silently ignore parameters they don't recognize, so they send extras by default. This page maps the errors you'll hit to the cause and the fix.
/oauth/authorize deserializes its query string with unknown fields denied, not ignored. A conformant, minimal request succeeds; a request carrying one extra parameter your library added on your behalf fails with 400 invalid_request, and the failure looks identical whether the extra parameter is harmless or not. When in doubt, log the exact query string your library sent and compare it parameter-by-parameter against the example on the Sign in with authreads page.If you see this error, it's because…
| Symptom | Cause | Fix |
|---|---|---|
| 400 invalid_request on /oauth/authorize, no explanation | Your library added a query parameter /oauth/authorizedoesn't recognize — response_mode, audience, ui_locales, login_hint, acr_values, a vendor-specific extension, and so on are all rejected. Unknown fields fail closed rather than being dropped. | Configure the library for a minimal request: client_id, redirect_uri, response_type=code, scope, state, nonce, code_challenge, code_challenge_method=S256— nothing else. Most libraries let you build the authorize URL yourself instead of trusting their auto-generated one; that's the most reliable fix. |
| Silent-renew / “check session” iframe fails, or a background refresh throws invalid_request | Session-management and silent-renew features (common in browser SPA libraries) reauthorize with prompt=none in an invisible iframe. prompt in any form — none, login, consent, select_account — is rejected. | Turn off silent-renew / automatic session checking for this provider. Use refresh_token grants for silent renewal instead, and the session re-verification API (below) for anything that needs the user re-present. |
| invalid_request mentioning “prompt and max_age” | max_age (forcing reauthentication after N seconds) is rejected the same way as prompt. Some libraries send max_age=0whenever a caller asks for a “fresh” login. | Don't pass max_age. To force fresh re-authentication for a sensitive action, use the session re-verification (“step-up”) API in Sessions & tokens after the user already has a session, rather than round-tripping through /authorize again. |
| 401 invalid_client | Check the client ID and secret first. authreads supports both client_secret_basic and client_secret_post for confidential clients. Sending the secret both ways at once (a Basic header and client_secret in the body) is also rejected, even if both values are correct. | Configure exactly one advertised method. Prefer token_endpoint_auth_method: "client_secret_basic" when your library supports it because the secret stays out of the form body. Public clients send neither an Authorization header nor a client_secret — PKCE is the only client authentication. |
| invalid_request only in production / only over a real network, not from localhost over plain HTTP | Non-loopback redirect_uri and post_logout_redirect_uri values must be exact absolute HTTPS URLs. Both fields may use HTTP only on localhost, 127.0.0.1, or [::1]; their runtime port may differ from registration, but scheme, host, path, and query may not. The token exchange must repeat the exact URI used at /authorize. | Register the exact web URI, or the stable scheme/host/path shape for a native loopback listener. See the registration guidance in Register an OIDC application. |
| invalid_request — missing nonce, or your library never generated one | Some minimal or hand-rolled OAuth2 (not OIDC-aware) clients omit nonce entirely because it's optional in plain OAuth2. authreads requires it on every authorization request. | Use an OIDC-aware client (not a bare OAuth2 client) so nonce is generated, sent, and verified against the id_token automatically. If you construct the request by hand, generate a fresh opaque value per attempt the same way you do for state. |
Why authreads works this way
A generic OIDC provider tends to accept a wide surface and quietly ignore what it doesn't implement, which makes integration easy but makes it hard to know what your client is actually relying on. authreads' /oauth/authorize instead validates a fixed, small contract and fails loudly on anything else — including parameters that are perfectly valid OIDC, like prompt, but that authreads doesn't implement server-side re-authentication semantics for. The trade-off is deliberate: a rejected request is easier to debug than a request that was silently only half-honored.
A minimal, known-good request
Copy this instead of trusting a library's default request-builder:
GET /oauth/authorize
?client_id=<client_id>
&redirect_uri=<exact registered redirect_uri>
&response_type=code
&scope=openid%20profile%20email
&state=<opaque, unique per attempt>
&nonce=<opaque, unique per attempt>
&code_challenge=<base64url-sha256 of code_verifier>
&code_challenge_method=S256Full walkthrough, token exchange for both client types, and scope reference: Sign in with authreads. Error-shape and status-code reference: Errors & rate limits.