temporalio / temporalio/ui

OIDC login fails against FAPI 2.0-compliant providers: nonce always exceeds 64 characters

Open
#3,844 1 comment 0 reactions 0 assignees View on GitHub

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

  1. Configure auth against 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").
  2. Visit the UI and start the login flow.
  3. The provider redirects straight back to /auth/sso/callback with error=invalid_request and error_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.md does not mention nonce or 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/oauth2 uses AuthStyleAutoDetect, so after the failed authorization the token exchange retries with a different client authentication method and can surface unauthorized_client - Client is not allowed to use the specified authentication method instead, 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/ui 2.52.0 (also present on main at the time of writing)
  • Deployed via the temporal Helm chart 1.6.0
  • Provider: ThunderID 1.0.0

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.