@@ -198,13 +198,15 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
198198 setFlowId ( null ) ;
199199 setIsFlowInitialized ( false ) ;
200200 sessionStorage . removeItem ( 'asgardeo_session_data_key' ) ;
201+ // Reset refs to allow new flows to start properly
202+ oauthCodeProcessedRef . current = false ;
201203 } ;
202204
203205 /**
204206 * Parse URL parameters used in flows.
205207 */
206208 const getUrlParams = ( ) => {
207- const urlParams = new URL ( window . location . href ) . searchParams ;
209+ const urlParams = new URL ( window ? .location ? .href ?? '' ) . searchParams ;
208210 return {
209211 code : urlParams . get ( 'code' ) ,
210212 error : urlParams . get ( 'error' ) ,
@@ -242,6 +244,7 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
242244 * Clean up OAuth-related URL parameters from the browser URL.
243245 */
244246 const cleanupOAuthUrlParams = ( includeNonce = false ) : void => {
247+ if ( ! window ?. location ?. href ) return ;
245248 const url = new URL ( window . location . href ) ;
246249 url . searchParams . delete ( 'error' ) ;
247250 url . searchParams . delete ( 'error_description' ) ;
@@ -250,7 +253,19 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
250253 if ( includeNonce ) {
251254 url . searchParams . delete ( 'nonce' ) ;
252255 }
253- window . history . replaceState ( { } , '' , url . toString ( ) ) ;
256+ window ?. history ?. replaceState ( { } , '' , url . toString ( ) ) ;
257+ } ;
258+
259+ /**
260+ * Clean up flow-related URL parameters (flowId, sessionDataKey) from the browser URL.
261+ * Used after flowId is set in state to prevent using invalidated flowId from URL.
262+ */
263+ const cleanupFlowUrlParams = ( ) : void => {
264+ if ( ! window ?. location ?. href ) return ;
265+ const url = new URL ( window . location . href ) ;
266+ url . searchParams . delete ( 'flowId' ) ;
267+ url . searchParams . delete ( 'sessionDataKey' ) ;
268+ window ?. history ?. replaceState ( { } , '' , url . toString ( ) ) ;
254269 } ;
255270
256271 /**
@@ -266,16 +281,17 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
266281 'SIGN_IN_ERROR' ,
267282 'react' ,
268283 ) ;
269- setFlowError ( err ) ;
270- onError ?.( err ) ;
284+ setError ( err ) ;
271285 cleanupOAuthUrlParams ( true ) ;
272286 } ;
273287
274288 /**
275289 * Set error state and call onError callback.
290+ * Ensures isFlowInitialized is true so errors can be displayed in the UI.
276291 */
277292 const setError = ( error : Error ) : void => {
278293 setFlowError ( error ) ;
294+ setIsFlowInitialized ( true ) ;
279295 onError ?.( error ) ;
280296 } ;
281297
@@ -286,7 +302,7 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
286302 if ( response . type === EmbeddedSignInFlowTypeV2 . Redirection ) {
287303 const redirectURL = ( response . data as any ) ?. redirectURL || ( response as any ) ?. redirectURL ;
288304
289- if ( redirectURL ) {
305+ if ( redirectURL && window ?. location ) {
290306 if ( response . flowId ) {
291307 setFlowId ( response . flowId ) ;
292308 }
@@ -321,21 +337,44 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
321337 storedFlowId ,
322338 ) ;
323339
340+ // Only process code if we have a valid flowId to use
324341 if ( flowIdFromState ) {
325342 setFlowId ( flowIdFromState ) ;
326343 setIsFlowInitialized ( true ) ;
327344 initializationAttemptedRef . current = true ;
345+ // Clean up flowId from URL after setting it in state
346+ cleanupFlowUrlParams ( ) ;
347+ } else {
348+ console . warn ( '[SignIn] OAuth code in URL but no valid flowId found. Cleaning up stale OAuth parameters.' ) ;
349+ cleanupOAuthUrlParams ( true ) ;
328350 }
329351 return ;
330352 }
331353
354+ // If flowId is in URL or sessionStorage but no code and no active flow state
355+ if ( ( urlParams . flowId || storedFlowId ) && ! urlParams . code && ! currentFlowId ) {
356+ console . warn (
357+ '[SignIn] FlowId in URL/sessionStorage but no active flow state detected. '
358+ ) ;
359+ setFlowId ( null ) ;
360+ sessionStorage . removeItem ( 'asgardeo_flow_id' ) ;
361+ cleanupFlowUrlParams ( ) ;
362+ // Continue to initialize with applicationId instead
363+ }
364+
332365 if (
333366 isInitialized &&
334367 ! isLoading &&
335368 ! isFlowInitialized &&
336369 ! initializationAttemptedRef . current &&
337370 ! currentFlowId
338371 ) {
372+ // Clean up any stale OAuth parameters before starting a new flow
373+ const urlParams = getUrlParams ( ) ;
374+ if ( urlParams . code || urlParams . state ) {
375+ console . debug ( '[SignIn] Cleaning up stale OAuth parameters before starting new flow' ) ;
376+ cleanupOAuthUrlParams ( true ) ;
377+ }
339378 initializationAttemptedRef . current = true ;
340379 initializeFlow ( ) ;
341380 }
@@ -348,6 +387,9 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
348387 const initializeFlow = async ( ) : Promise < void > => {
349388 const urlParams = getUrlParams ( ) ;
350389
390+ // Reset OAuth code processed ref when starting a new flow
391+ oauthCodeProcessedRef . current = false ;
392+
351393 handleSessionDataKey ( urlParams . sessionDataKey ) ;
352394
353395 const effectiveApplicationId = applicationId || urlParams . applicationId ;
@@ -388,18 +430,25 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
388430 setFlowId ( flowId ) ;
389431 setComponents ( components ) ;
390432 setIsFlowInitialized ( true ) ;
433+ // Clean up flowId from URL after setting it in state
434+ cleanupFlowUrlParams ( ) ;
391435 }
392436 } catch ( error ) {
393437 const err = error as Error ;
394- setError ( err ) ;
395438 clearFlowState ( ) ;
396- initializationAttemptedRef . current = false ;
397439
398- throw new AsgardeoRuntimeError (
399- `Failed to initialize authentication flow: ${ error instanceof Error ? error . message : String ( error ) } ` ,
440+ // Extract error message
441+ const errorMessage = err instanceof Error ? err . message : String ( err ) ;
442+
443+ // Create error with backend message
444+ const displayError = new AsgardeoRuntimeError (
445+ errorMessage ,
400446 'SIGN_IN_ERROR' ,
401447 'react' ,
402448 ) ;
449+ setError ( displayError ) ;
450+ initializationAttemptedRef . current = false ;
451+ return ;
403452 }
404453 } ;
405454
@@ -422,7 +471,6 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
422471 setIsSubmitting ( true ) ;
423472 setFlowError ( null ) ;
424473
425- // Use effectiveFlowId - either from payload or currentFlowId
426474 const response : EmbeddedSignInFlowResponseV2 = await signIn ( {
427475 flowId : effectiveFlowId ,
428476 ...payload ,
@@ -438,38 +486,46 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
438486 if ( response . flowStatus === EmbeddedSignInFlowStatusV2 . Error ) {
439487 console . error ( '[SignIn] Flow returned Error status, clearing flow state' ) ;
440488 clearFlowState ( ) ;
489+ // Extract failureReason from response if available
490+ const failureReason = ( response as any ) ?. failureReason ;
491+ const errorMessage = failureReason || 'Authentication flow failed. Please try again.' ;
441492 const err = new AsgardeoRuntimeError (
442- 'Authentication flow failed. Please try again.' ,
493+ errorMessage ,
443494 'SIGN_IN_ERROR' ,
444495 'react' ,
445496 ) ;
446497 setError ( err ) ;
498+ cleanupFlowUrlParams ( ) ;
447499 return ;
448500 }
449501
450502 if ( response . flowStatus === EmbeddedSignInFlowStatusV2 . Complete ) {
451503 // Get redirectUrl from response (from /oauth2/authorize) or fall back to afterSignInUrl
452- const redirectUrl = ( response as any ) . redirectUrl || ( response as any ) . redirect_uri ;
504+ const redirectUrl = ( response as any ) ?. redirectUrl || ( response as any ) ?. redirect_uri ;
505+ const finalRedirectUrl = redirectUrl || afterSignInUrl ;
506+
507+ // Clear submitting state before redirect
508+ setIsSubmitting ( false ) ;
453509
454510 // Clear all OAuth-related storage on successful completion
455511 setFlowId ( null ) ;
456512 setIsFlowInitialized ( false ) ;
457- if ( redirectUrl ) {
458- sessionStorage . removeItem ( 'asgardeo_session_data_key' ) ;
459- }
513+ sessionStorage . removeItem ( 'asgardeo_flow_id' ) ;
514+ sessionStorage . removeItem ( 'asgardeo_session_data_key' ) ;
460515
516+ // Clean up OAuth URL params before redirect
461517 cleanupOAuthUrlParams ( true ) ;
462518
463- const finalRedirectUrl = redirectUrl || afterSignInUrl ;
464-
465519 onSuccess &&
466520 onSuccess ( {
467521 redirectUrl : finalRedirectUrl ,
468- ...response . data ,
522+ ...( response . data || { } ) ,
469523 } ) ;
470524
471- if ( finalRedirectUrl ) {
525+ if ( finalRedirectUrl && window ?. location ) {
472526 window . location . href = finalRedirectUrl ;
527+ } else {
528+ console . warn ( '[SignIn] Flow completed but no redirect URL available' ) ;
473529 }
474530
475531 return ;
@@ -479,16 +535,23 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
479535 if ( flowId && components ) {
480536 setFlowId ( flowId ) ;
481537 setComponents ( components ) ;
538+ // Clean up flowId from URL after setting it in state
539+ cleanupFlowUrlParams ( ) ;
482540 }
483541 } catch ( error ) {
484542 const err = error as Error ;
485- setError ( err ) ;
486543 clearFlowState ( ) ;
487- throw new AsgardeoRuntimeError (
488- `Failed to submit authentication flow: ${ error instanceof Error ? error . message : String ( error ) } ` ,
544+
545+ // Extract error message
546+ const errorMessage = err instanceof Error ? err . message : String ( err ) ;
547+
548+ const displayError = new AsgardeoRuntimeError (
549+ errorMessage ,
489550 'SIGN_IN_ERROR' ,
490551 'react' ,
491552 ) ;
553+ setError ( displayError ) ;
554+ return ;
492555 } finally {
493556 setIsSubmitting ( false ) ;
494557 }
@@ -502,6 +565,9 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
502565 setError ( error ) ;
503566 } ;
504567
568+ /**
569+ * Handle OAuth code processing from external OAuth providers.
570+ */
505571 useEffect ( ( ) => {
506572 const urlParams = getUrlParams ( ) ;
507573 const storedFlowId = sessionStorage . getItem ( 'asgardeo_flow_id' ) ;
0 commit comments