nuts-foundation / nuts-foundation/nuts-node
OAuth auth-code flow timeout too short for interactive frontend logins (1 min)
@reinkrul is already working on this.
Since Jun 18, 2026.
- Dominant language
- Go
- Stars
- 28
- Forks
- 23
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 76
Description
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:43—oAuthFlowTimeout = time.Minuteauth/api/iam/user.go:152—oauthClientStateStore()=GetStore(oAuthFlowTimeout, ...)storage/session.go:102—Putsetsstore.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, butstateis a single-use unguessable nonce consumed on first callback — 5 min is standard for auth-code flows. - Confirm no other caller of
oAuthFlowTimeoutrelies on the 1-minute value before changing it (grep shows onlyoauthClientStateStore).
Related
- Triggering issue: #4339 (org-agnostic callback — relies on
stateas the sole binding, making this window load-bearing) - #4316 (configurable OAuth client auth — the integration that exercises interactive external login)
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.
Assessment
This issue has not been assessed yet.