Skip to content

Redirect when a sign-in flow completes on its first step#4

Merged
madurangasiriwardena merged 1 commit into
thunder-id:mainfrom
madurangasiriwardena:feature/session-poc-2
Jul 8, 2026
Merged

Redirect when a sign-in flow completes on its first step#4
madurangasiriwardena merged 1 commit into
thunder-id:mainfrom
madurangasiriwardena:feature/session-poc-2

Conversation

@madurangasiriwardena

@madurangasiriwardena madurangasiriwardena commented Jun 23, 2026

Copy link
Copy Markdown
Member

Purpose

Fix the login gate hanging on a perpetual loading spinner when an authentication flow completes on its very first /flow/execute step — i.e. when the backend returns flowStatus: COMPLETE immediately, before any UI components are rendered (for example, when a reused browser SSO session lets the flow finish without a credential prompt).

In the SignIn component (presentation/auth/SignIn/SignIn.tsx), terminal flow statuses (Complete / Error) were handled only in the submission path (handleSubmit), not on the initial flow load (initializeFlow). On an initial COMPLETE response there are no components to render, so the initialize path fell through and the UI stayed on CircularProgress forever instead of redirecting to the callback.

This is a general client-side correctness fix: any flow that completes (or errors) on its first step is now finished properly. It is self-contained — no dependency on any backend feature, and no behavior change for existing flows that begin with a prompt (the new branch simply isn't hit until a flow returns COMPLETE on the first step).

Approach

  • Extracted the existing terminal-response handling (the inline Complete and Error blocks in handleSubmit) into a shared async helper, handleTerminalResponse(response):
    • Error → clear flow state, surface the error, throw so the caller's catch propagates it to BaseSignIn.
    • Complete → resolve the redirect target (redirectUrl / redirect_uri, falling back to afterSignInUrl), clear OAuth/flow storage, fire onSuccess, and redirect. Returns true so callers stop processing.
  • Called the helper in initializeFlow right after handleRedirection (the actual bug fix), and replaced the duplicated inline blocks in handleSubmit with the same call (no behavior change to that path).
  • Behavior, storage cleanup, and redirect semantics are preserved exactly; only the call site coverage and de-duplication changed.

Related Issues

  • N/A

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • Bug Fixes
    • Improved sign-in flow handling when an authentication session ends immediately with success or error.
    • Fixed cases where users could be sent into the normal sign-in UI even though the login process had already completed.
    • Made redirects and error handling more reliable during sign-in, including better cleanup of temporary sign-in state.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Terminal flow-status handling (Error and Complete) in SignIn.tsx is refactored into a new shared async helper, handleTerminalResponse. Both initializeFlow and handleSubmit now call this helper instead of containing inline branches for handling these statuses.

Changes

Terminal Response Handling Refactor

Layer / File(s) Summary
Shared terminal response helper
packages/react/src/components/presentation/auth/SignIn/SignIn.tsx
New handleTerminalResponse helper centralizes Error handling (clear flow state, translate error, clean URL params, throw) and Complete handling (stop submission, clear storage, clean OAuth params, invoke onSuccess, redirect).
Wire helper into initializeFlow and handleSubmit
packages/react/src/components/presentation/auth/SignIn/SignIn.tsx
initializeFlow invokes the helper immediately after the initial flow response to handle early COMPLETE/ERROR cases; handleSubmit replaces its inline Error/Complete branches with a call to the same helper.

Estimated code review effort: 2 (Simple) | ~10 minutes

Related PRs: None specified.

Suggested labels: refactor, react

Suggested reviewers: None specified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: handling sign-in completion on the first flow step by redirecting instead of hanging.
Description check ✅ Passed The description follows the required template with Purpose, Approach, Related Issues/PRs, Checklist, and Security checks filled out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The v2 SignIn handled terminal flow statuses (Complete/Error) only after a
submission, not on the initial flow load. When a flow completes on its first
step — e.g. a reused browser SSO session lets the backend return COMPLETE
immediately with no UI components — the gate fell through with no components
and spun on a loader forever.

Extract the terminal-response handling into a shared helper and run it on the
initial load (initializeFlow) as well as after a submission (handleSubmit), so
the flow redirects to the callback instead of hanging.
@madurangasiriwardena madurangasiriwardena marked this pull request as ready for review July 7, 2026 10:43

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/react/src/components/presentation/auth/SignIn/SignIn.tsx (2)

453-491: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Complete cleanup largely duplicates clearFlowState; consider reusing it.

Lines 462-471 re-implement most of clearFlowState (execution id reset, challenge-token clear, isFlowInitialized=false, hybrid authId removal). The explicit sessionStorage.removeItem('thunderid_execution_id') (Line 465) is redundant since setExecutionId(null) already removes it (Line 267). Delegating to clearFlowState() would reduce duplication and prevent the two cleanup paths from diverging over time. Note that clearFlowState additionally resets oauthCodeProcessedRef/isTimeoutDisabled, so verify that difference is acceptable on the Complete path before consolidating.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/components/presentation/auth/SignIn/SignIn.tsx` around
lines 453 - 491, The completion cleanup in SignIn’s success branch is
duplicating the logic already centralized in clearFlowState, so refactor it to
reuse that helper instead of repeating execution-id, challenge-token,
isFlowInitialized, and hybrid authId cleanup. Remove the redundant
sessionStorage deletion for thunderid_execution_id since setExecutionId(null)
already handles it, and verify whether the extra reset of oauthCodeProcessedRef
and isTimeoutDisabled in clearFlowState is acceptable for the Complete flow
before consolidating.

455-456: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Lint: || here is intentional, but new any/nullish findings may block CI.

The || on Lines 455-456 is correct for URL fallback (an empty string should fall through), so @typescript-eslint/prefer-nullish-coalescing is a false positive here — add a targeted eslint-disable-next-line if the rule is enforced. The surrounding no-explicit-any/no-unsafe-* findings mirror the file's existing style; please confirm they don't fail the pipeline for the newly added lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/components/presentation/auth/SignIn/SignIn.tsx` around
lines 455 - 456, The fallback logic in SignIn.tsx uses intentional || behavior
for redirectUrl and finalRedirectUrl, so keep that behavior and add a targeted
eslint-disable-next-line for the nullish-coalescing rule if needed around the
redirectUrl/afterSignInUrl assignment. Also review the new any casts on response
and redirectUrl in SignIn to ensure they do not introduce fresh no-explicit-any
or no-unsafe-* CI failures, while keeping the existing style consistent.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react/src/components/presentation/auth/SignIn/SignIn.tsx`:
- Around line 458-485: The no-redirect completion path in SignIn leaves
BaseSignIn stuck in a loading state because setIsFlowInitialized(false) is
called without ever surfacing a terminal outcome when finalRedirectUrl is empty.
Update the completion logic in SignIn so that, after clearing OAuth state, it
explicitly handles the empty finalRedirectUrl case by setting a terminal error
or completed state and returning early instead of falling through to the
redirect path. Use the SignIn component flow around setIsFlowInitialized,
cleanupOAuthUrlParams, and onSuccess to ensure BaseSignIn no longer stays
blocked on isLoading || !isInitialized || !isFlowInitialized.

---

Nitpick comments:
In `@packages/react/src/components/presentation/auth/SignIn/SignIn.tsx`:
- Around line 453-491: The completion cleanup in SignIn’s success branch is
duplicating the logic already centralized in clearFlowState, so refactor it to
reuse that helper instead of repeating execution-id, challenge-token,
isFlowInitialized, and hybrid authId cleanup. Remove the redundant
sessionStorage deletion for thunderid_execution_id since setExecutionId(null)
already handles it, and verify whether the extra reset of oauthCodeProcessedRef
and isTimeoutDisabled in clearFlowState is acceptable for the Complete flow
before consolidating.
- Around line 455-456: The fallback logic in SignIn.tsx uses intentional ||
behavior for redirectUrl and finalRedirectUrl, so keep that behavior and add a
targeted eslint-disable-next-line for the nullish-coalescing rule if needed
around the redirectUrl/afterSignInUrl assignment. Also review the new any casts
on response and redirectUrl in SignIn to ensure they do not introduce fresh
no-explicit-any or no-unsafe-* CI failures, while keeping the existing style
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bce4b354-a878-439e-a45d-9143c4bdda47

📥 Commits

Reviewing files that changed from the base of the PR and between d61ce76 and 184ca6f.

📒 Files selected for processing (1)
  • packages/react/src/components/presentation/auth/SignIn/SignIn.tsx

Comment on lines +458 to +485
// Clear submitting state before redirect
setIsSubmitting(false);

// Clear all OAuth-related storage on successful completion
setExecutionId(null);
await setChallengeToken(null);
setIsFlowInitialized(false);
sessionStorage.removeItem('thunderid_execution_id');
try {
const storageManager: any = await getStorageManager();
await storageManager?.removeHybridDataParameter?.('authId');
} catch {
logger.warn('Failed to clear authId from hybrid storage after completion.');
}

// Clean up OAuth URL params before redirect
cleanupOAuthUrlParams(true);

if (onSuccess) {
onSuccess({
redirectUrl: finalRedirectUrl,
...(response.data || {}),
});
}

if (finalRedirectUrl && window?.location) {
window.location.href = finalRedirectUrl;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant slices of SignIn.tsx and the loading condition in BaseSignIn.
sed -n '430,500p' packages/react/src/components/presentation/auth/SignIn/SignIn.tsx
printf '\n---\n'
sed -n '970,1020p' packages/react/src/components/presentation/auth/SignIn/SignIn.tsx
printf '\n---\n'
rg -n "afterSignInUrl|redirectUrl|redirect_uri|isFlowInitialized|isLoading =" packages/react/src/components/presentation/auth/SignIn/SignIn.tsx

Repository: thunder-id/javascript-sdks

Length of output: 4720


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '640,680p' packages/react/src/components/presentation/auth/SignIn/SignIn.tsx
printf '\n---\n'
sed -n '680,720p' packages/react/src/components/presentation/auth/SignIn/SignIn.tsx

Repository: thunder-id/javascript-sdks

Length of output: 3131


Handle the no-redirect Complete path explicitly.
setIsFlowInitialized(false) leaves BaseSignIn stuck loading when finalRedirectUrl is empty (isLoading || !isInitialized || !isFlowInitialized stays true). Surface a terminal error/state before returning if there’s no redirect target.

🧰 Tools
🪛 ESLint

[error] 467-467: Unsafe assignment of an any value.

(@typescript-eslint/no-unsafe-assignment)


[error] 467-467: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 468-468: Unsafe call of an any typed value.

(@typescript-eslint/no-unsafe-call)


[error] 468-468: Unsafe member access .removeHybridDataParameter on an any value.

(@typescript-eslint/no-unsafe-member-access)


[error] 478-478: Unsafe assignment of an any value.

(@typescript-eslint/no-unsafe-assignment)


[error] 484-484: Unsafe assignment of an any value.

(@typescript-eslint/no-unsafe-assignment)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/components/presentation/auth/SignIn/SignIn.tsx` around
lines 458 - 485, The no-redirect completion path in SignIn leaves BaseSignIn
stuck in a loading state because setIsFlowInitialized(false) is called without
ever surfacing a terminal outcome when finalRedirectUrl is empty. Update the
completion logic in SignIn so that, after clearing OAuth state, it explicitly
handles the empty finalRedirectUrl case by setting a terminal error or completed
state and returning early instead of falling through to the redirect path. Use
the SignIn component flow around setIsFlowInitialized, cleanupOAuthUrlParams,
and onSuccess to ensure BaseSignIn no longer stays blocked on isLoading ||
!isInitialized || !isFlowInitialized.

@madurangasiriwardena madurangasiriwardena merged commit 1aa0560 into thunder-id:main Jul 8, 2026
3 checks 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