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

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions