OIDC login fails against FAPI 2.0-compliant providers: nonce always exceeds 64 characters
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 431
- Forks
- 179
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 71
Description
Describe the bug
The UI's OIDC login fails against any authorization server that enforces the FAPI 2.0 Security Profile recommendation on nonce length. The authorization request is rejected before the user ever sees a login screen:
/auth/sso/callback?error=invalid_request
&error_description=nonce+exceeds+maximum+allowed+length
FAPI 2.0 states that authorization servers shall support nonce values up to 64 characters and may reject longer ones, and that clients should not use a nonce longer than 64 characters, for interoperability.
The UI's nonce is never shorter than 67 characters, and in practice is 130–180.
Root cause
server/route/auth.go packs a JSON envelope into the nonce in order to carry the return URL through the round trip:
func randNonce(c echo.Context, allowedOrigins []string) (string, error) {
v, err := randString() // base64(16 random bytes) = 22 chars
returnURL := c.QueryParam("returnUrl")
n := &Nonce{Nonce: v, ReturnURL: returnURL}
bytes, err := json.Marshal(n)
return base64.RawURLEncoding.EncodeToString(bytes), nil
}
Resulting lengths:
returnUrl |
JSON bytes | nonce sent |
vs 64 |
|---|---|---|---|
| empty | 50 | 67 | exceeds |
| 52 chars (site root) | 102 | 136 | exceeds |
| 80 chars (a workflow page) | 130 | 174 | exceeds |
There is no configuration that avoids this — even an empty returnUrl overflows, because the 22-char random component plus the JSON envelope already clears 64.
Notably, the same request already carries a spec-friendly value: state is generated by randString() and is 22 characters. Observed cookies from one failed attempt:
| cookie | value | length |
|---|---|---|
nonce |
base64 of {"nonce":"…22 chars…","return_url":"https://…"} |
136 |
state |
j2aaMWBD27FgrLZ2j0nvIQ |
22 |
To Reproduce
- Configure
authagainst an OIDC provider that enforces the FAPI 2.0 nonce limit (reproduced against ThunderID 1.0.0, which caps at 64 with the rationale documented in-code as "Aligned with FAPI 2.0 Security Profile recommendation"). - Visit the UI and start the login flow.
- The provider redirects straight back to
/auth/sso/callbackwitherror=invalid_requestanderror_description=nonce exceeds maximum allowed length.
The boundary is easy to confirm directly against such a provider: a 64-character nonce is accepted, 65 is rejected, while a 100-character state is accepted — i.e. the limit is nonce-specific, not a general parameter cap.
Expected behaviour
The UI should send a nonce within the 64-character interoperability limit, so login works against FAPI 2.0-aligned providers.
Suggested fix
Carry the return URL in state rather than nonce. state is the conventional parameter for round-trip application state, it is not subject to the same interoperability recommendation, and the UI already generates and cookie-validates one. The nonce could then be the plain 22-character randString() value it wraps today.
A smaller alternative — omitting return_url from the JSON when it is empty — is not sufficient on its own, since the floor would still be 67.
Additional context
- This is not provider-specific. Any authorization server applying the FAPI 2.0 profile is expected to behave the same way, so this likely affects Keycloak, Curity, Authlete and Duende deployments with that profile enabled.
AUTHENTICATION.mddoes not mentionnonceor any provider-side length constraint, so there is no way for an operator to anticipate this from the docs. The failure surfaces only at runtime, as a provider error.- The error is easily masked.
golang.org/x/oauth2usesAuthStyleAutoDetect, so after the failed authorization the token exchange retries with a different client authentication method and can surfaceunauthorized_client - Client is not allowed to use the specified authentication methodinstead, which points investigation in the wrong direction. - Possibly related: #1376 (Improve OIDC support/compatibility), which describes the same general class of hardcoded behaviour limiting provider compatibility.
Environment
temporalio/ui2.52.0 (also present onmainat the time of writing)- Deployed via the
temporalHelm chart 1.6.0 - Provider: ThunderID 1.0.0
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in server/route/auth.go at randNonce and trace how state and nonce are stored and validated through the SSO callback. Verify the change against an OIDC provider or existing authentication checks: the authorization request should use a nonce of 64 characters or fewer, preserve the return URL, and complete login successfully.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100