Skip to content

OAuth auth-code flow timeout too short for interactive frontend logins (1 min) #4345

Description

@reinkrul

Context

Surfaced while drafting #4339 (org-agnostic /oauth2/callback). Not part of that change — filed separately.

Problem

The state to OAuthSession mapping for the OAuth authorization-code callback flows lives in oauthClientStateStore with a hard 1-minute TTL:

  • auth/api/iam/user.go:43oAuthFlowTimeout = time.Minute
  • auth/api/iam/user.go:152oauthClientStateStore() = GetStore(oAuthFlowTimeout, ...)
  • storage/session.go:102Put sets store.WithExpiration(opts.ttl) once; no refresh on read, so it is an absolute expiry from the moment the node redirects the user out.

That 1 minute has to cover the entire interactive round-trip: redirect to the external IdP, the user authenticates (password, possibly MFA, consent), then redirect back to the callback. For a human login (e.g. AET / OpenIddict) that is far too tight — the callback then fails with invalid_request "invalid or expired state" (api.go:293).

Both auth-code callback flows write to this same store, so both inherit the 1-minute window:

Flow Store write Interactive?
OpenID4VCI auth-code openid4vci.go:105 oauthClientStateStore().Put(state, ...) yes (user authorizes at external issuer)
OpenID4VP authorization-code (user) user.go:121 oauthClientStateStore().Put(ClientState, ...) yes (user login)

Proposal

Use a timeout that matches the flow type:

  • Frontend / interactive flows (the two above): ~5 minutes.
  • Back-end / service-to-service flows (e.g. RFC021 VP-based access tokens): keep ~1 minute.

If a single store can't cleanly distinguish the two, split into two named constants/stores (e.g. oauthFrontendFlowTimeout = 5 min vs the existing oAuthFlowTimeout = 1 min) and pick per flow.

Considerations

  • A longer TTL widens the replay window for a leaked state, but state is a single-use unguessable nonce consumed on first callback — 5 min is standard for auth-code flows.
  • Confirm no other caller of oAuthFlowTimeout relies on the 1-minute value before changing it (grep shows only oauthClientStateStore).

Related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions