Skip to content
Authreads Docs

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.

The root cause is the same every time: /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…

SymptomCauseFix
400 invalid_request on
/oauth/authorize, no explanation
Your library added a query parameter /oauth/authorize doesn'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_requestSession-management and silent-renew features (common in browser SPA libraries) reauthorize with prompt=none in an invisible iframe. Authreads supports that value, but modern cookie partitioning can prevent the iframe from sending the auth-host cookie.Prefer refresh_token grants for background token renewal. Use top-levelprompt=none only for session checks; handle login_required by starting an interactive authorization request.
invalid_request mentioning an unsupported prompt valueAuthreads supports prompt=none, prompt=login, and max_age, but rejects other prompt values such as consent or select_account instead of silently ignoring them.Configure the library to send prompt=login or max_age=0 when the RP needs fresh credentials. Remove unsupported prompt values; do not replace them with a plain authorize request, which may complete through SSO without human input.
401 invalid_clientCheck 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 HTTPNon-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.

This also means upgrading a library version can break a previously-working integration if the new version starts sending a parameter it didn't before (a new default, a new feature flag it turns on automatically). If a previously-passing flow starts failing after a dependency bump, diff the authorize request first.

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=S256

Full walkthrough, token exchange for both client types, and scope reference: Sign in with Authreads. Error-shape and status-code reference: Errors & rate limits.