Skip to content

fix: reuse the popup for the interactive retry — no more blocked second window / 'Open new window' dialog#13

Open
jeswr wants to merge 2 commits into
mainfrom
fix/popup-retry-window-reuse
Open

fix: reuse the popup for the interactive retry — no more blocked second window / 'Open new window' dialog#13
jeswr wants to merge 2 commits into
mainfrom
fix/popup-retry-window-reuse

Conversation

@jeswr

@jeswr jeswr commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Problem

DPoPTokenProvider always tries prompt=none first, so a fresh login is: silent attempt → login_required → interactive retry. AuthorizationCodeFlow closed the popup on every callback message, so the retry had to open() a new window — outside the original click's (already consumed) user activation. Popup blockers stop that, stranding the user in the "User interaction needed to launch authorization code flow in new window → Open new window" dialog on every first login. This is the "unnecessary accepting of prompts to redirect" users report.

Change

Keep the popup open when the callback message carries one of the OIDC "user must interact" errors (login_required / interaction_required / consent_required) — exactly the errors token providers retry interactively right away. The retry's open(uri, sameWindowName) then merely navigates the existing named window, which browsers always allow without user activation.

Result: the user sees one popup that goes from the silent bounce straight to the IdP login page — no blocked window, no interstitial dialog, no extra click.

A code response or a terminal error (e.g. access_denied when the user denies consent) still closes the window exactly as before; cancel/abort paths are unchanged.

Verification

  • npm run build (tsc): clean.
  • The repo has no DOM test setup, so this is verified live: a Playwright-driven login (Chrome popup blocker enabled, --disable-popup-blocking removed) against a deployed pod manager + Solid-OIDC broker (https://app.solid-test.jeswr.org). Before: silent popup closes → second window needed. After: one popup, prompt=none bounce navigates in place to the Keycloak login, flow completes with zero interstitial dialogs.

Future work (out of scope here): running the prompt=none attempt in a hidden iframe would remove the popup flash for purely silent re-auth; that needs a callback.html contract change (parent vs opener), so it is left for a separate discussion.

🤖 Generated with Claude Code

The DPoP provider always tries `prompt=none` first, so a fresh login is:
silent attempt → `login_required` → interactive retry. The popup element
closed the window on EVERY callback message, so the retry had to open a
NEW window — outside the original click's (already consumed) user
activation. Popup blockers stop that, stranding the user in the "User
interaction needed … Open new window" dialog on every first login (and
on slower connections the first open can be blocked too).

Now the popup is kept open when the callback carries one of the OIDC
"user must interact" errors (login_required / interaction_required /
consent_required) — exactly the errors token providers retry right away.
The retry's `open()` then merely NAVIGATES the existing named window,
which browsers always allow. Users see one popup that goes from the
silent bounce straight to the login page, with no interstitial dialog.

A code response or a terminal error (e.g. access_denied when the user
denies consent) still closes the window as before, as do cancel/abort.

Verified live with a Playwright-driven login (Chrome popup blocker
enabled) against a deployed app + Solid-OIDC broker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 11, 2026 20:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the AuthorizationCodeFlow popup lifecycle so that when a silent prompt=none attempt returns an OIDC “user must interact” error (login_required, interaction_required, consent_required), the popup is kept open. This enables the subsequent interactive retry to reuse (navigate) the same named popup window, avoiding popup-blocker failures that occur when trying to open a second window without user activation.

Changes:

  • Keep the authorization popup open on OIDC “interaction needed” error callbacks to allow interactive retry to navigate the existing named window.
  • Add a helper (needsInteraction) to detect the relevant OIDC error responses from the callback URL.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 189 to +193
const onMessage = (message: MessageEvent) => {
signal.removeEventListener("abort", onAbort)
this.#switchModal.close()
this.#authorizationWindow?.close()

// When the server answered a silent (`prompt=none`) attempt with a "user

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1ce81c1: the listener now ignores any message whose source is not the authorization popup, and removes itself on the matching message instead of using { once: true }.

Review follow-up: filter the window message listener by
message.source === the popup, so unrelated (or hostile) postMessage
traffic can neither resolve the flow early nor spoof an authorization
response. The listener now removes itself on the matching message
instead of using { once: true }.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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