Skip to content

Preserve OAuth context through the MFA step#79

Merged
eswan18 merged 1 commit into
mainfrom
claude/vigilant-thompson-VZWRg
May 30, 2026
Merged

Preserve OAuth context through the MFA step#79
eswan18 merged 1 commit into
mainfrom
claude/vigilant-thompson-VZWRg

Conversation

@eswan18

@eswan18 eswan18 commented May 30, 2026

Copy link
Copy Markdown
Owner

When MFA verification couldn't find the pending row, the handlers redirected
to a bare /oauth/login, discarding the OAuth authorization request (client_id,
redirect_uri, state, scope, PKCE challenge, nonce) — which during MFA lives only
in the auth_mfa_pending row. That produced a confusing two-stage failure:

  1. The pending row is gone at OTP-submit time — it expired during code entry
    (5-min window), or a duplicate/replayed submit already consumed it — so the
    user is bounced back to the username/password page despite completing the
    full flow.
  2. The bare login has no client_id, so the next successful login is treated as
    a direct login and lands on the account-settings page instead of redirecting
    back to the originating app.

Fix: carry the OAuth parameters through the MFA page as hidden fields (sourced
from the pending row, which stays authoritative when present), and on a missing/
expired/replayed pending row resume the flow via /oauth/authorize instead of a
context-free login. Routing through authorize re-validates client_id/redirect_uri/
scope and transparently completes the flow when a session already exists (the
duplicate-submit case) or re-prompts login with context preserved otherwise.

The single-use pending row and 5-minute window are unchanged — the OTP gate is
not weakened; only the non-secret navigation context is preserved.

Also extract shared setSessionCookie / buildAuthorizeURL / redirectAfterAuth
helpers so the login and MFA success paths can't drift apart again.

Adds integration tests covering: the MFA page rendering the OAuth context,
an expired/missing pending row preserving context, and a duplicate submit not
dropping to the account page.

When MFA verification couldn't find the pending row, the handlers redirected
to a bare /oauth/login, discarding the OAuth authorization request (client_id,
redirect_uri, state, scope, PKCE challenge, nonce) — which during MFA lives only
in the auth_mfa_pending row. That produced a confusing two-stage failure:

  1. The pending row is gone at OTP-submit time — it expired during code entry
     (5-min window), or a duplicate/replayed submit already consumed it — so the
     user is bounced back to the username/password page despite completing the
     full flow.
  2. The bare login has no client_id, so the next successful login is treated as
     a direct login and lands on the account-settings page instead of redirecting
     back to the originating app.

Fix: carry the OAuth parameters through the MFA page as hidden fields (sourced
from the pending row, which stays authoritative when present), and on a missing/
expired/replayed pending row resume the flow via /oauth/authorize instead of a
context-free login. Routing through authorize re-validates client_id/redirect_uri/
scope and transparently completes the flow when a session already exists (the
duplicate-submit case) or re-prompts login with context preserved otherwise.

The single-use pending row and 5-minute window are unchanged — the OTP gate is
not weakened; only the non-secret navigation context is preserved.

Also extract shared setSessionCookie / buildAuthorizeURL / redirectAfterAuth
helpers so the login and MFA success paths can't drift apart again.

Adds integration tests covering: the MFA page rendering the OAuth context,
an expired/missing pending row preserving context, and a duplicate submit not
dropping to the account page.
@eswan18 eswan18 merged commit 17b7d26 into main May 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants