diff --git a/.changeset/six-spiders-say.md b/.changeset/six-spiders-say.md new file mode 100644 index 00000000..9e62ba33 --- /dev/null +++ b/.changeset/six-spiders-say.md @@ -0,0 +1,5 @@ +--- +'@asgardeo/nextjs': patch +--- + +Enhance OAuth handling with improved error logging and state management diff --git a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx index a99e03a2..2946ea7a 100644 --- a/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx @@ -119,10 +119,22 @@ const AsgardeoClientProvider: FC> // Handle OAuth callback automatically useEffect(() => { + // React 18.x Strict.Mode has a new check for `Ensuring reusable state` to facilitate an upcoming react feature. + // https://reactjs.org/docs/strict-mode.html#ensuring-reusable-state + // This will remount all the useEffects to ensure that there are no unexpected side effects. + // When react remounts the signIn hook of the AuthProvider, it will cause a race condition. Hence, we have to + // prevent the re-render of this hook as suggested in the following discussion. + // https://github.com/reactwg/react-18/discussions/18#discussioncomment-795623 + if (reRenderCheckRef.current) { + return; + } + + reRenderCheckRef.current = true; + // Don't handle callback if already signed in if (isSignedIn) return; - const processOAuthCallback = async () => { + (async () => { try { const code = searchParams.get('code'); const state = searchParams.get('state'); @@ -132,13 +144,8 @@ const AsgardeoClientProvider: FC> // Check for OAuth errors first if (error) { - console.error('[AsgardeoClientProvider] OAuth error:', error, errorDescription); - // Redirect to sign-in page with error - router.push( - `/signin?error=${encodeURIComponent(error)}&error_description=${encodeURIComponent( - errorDescription || '', - )}`, - ); + logger.error('[AsgardeoClientProvider] An error was received for the initiated sign-in request.'); + return; } @@ -157,21 +164,16 @@ const AsgardeoClientProvider: FC> window.location.reload(); } } else { - router.push( - `/signin?error=authentication_failed&error_description=${encodeURIComponent( - result.error || 'Authentication failed', - )}`, + logger.error( + `[AsgardeoClientProvider] An error occurred while signing in: ${result.error || 'Authentication failed'}`, ); } } } catch (error) { - console.error('[AsgardeoClientProvider] Failed to handle OAuth callback:', error); - router.push('/signin?error=authentication_failed'); + logger.error('[AsgardeoClientProvider] Failed to handle OAuth callback:', error); } - }; - - processOAuthCallback(); - }, [searchParams, router, isSignedIn, handleOAuthCallback]); + })(); + }, []); useEffect(() => { if (!preferences?.theme?.mode || preferences.theme.mode === 'system') { diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index b6d37266..ff673b41 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -18,5 +18,8 @@ export {default as AsgardeoNext} from './AsgardeoNextClient'; +// TODO: Remove this export once the docs are live. +export * from './server'; + // @asgardeo/nextjs exports. export * from './client'; diff --git a/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts b/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts index ecaf364d..bef27970 100644 --- a/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts +++ b/packages/nextjs/src/server/actions/handleOAuthCallbackAction.ts @@ -21,6 +21,7 @@ import {cookies} from 'next/headers'; import AsgardeoNextClient from '../../AsgardeoNextClient'; import SessionManager from '../../utils/SessionManager'; +import logger from '../../utils/logger'; /** * Server action to handle OAuth callback with authorization code. @@ -68,11 +69,15 @@ const handleOAuthCallbackAction = async ( const tempSession = await SessionManager.verifyTempSession(tempSessionToken); sessionId = tempSession.sessionId; } catch { - // TODO: Invalid temp session, throw error. + logger.error( + '[handleOAuthCallbackAction] Invalid temporary session token, falling back to session ID from cookies.', + ); } } if (!sessionId) { + logger.error('[handleOAuthCallbackAction] No session ID found in cookies or temporary session token.'); + return { success: false, error: 'No session found. Please start the authentication flow again.', @@ -92,10 +97,13 @@ const handleOAuthCallbackAction = async ( if (signInResult) { try { - const idToken = await asgardeoClient.getDecodedIdToken(sessionId); - const accessToken: string = signInResult['access_token']; + const idToken = await asgardeoClient.getDecodedIdToken( + sessionId, + signInResult['id_token'] || signInResult['idToken'], + ); + const accessToken: string = signInResult['accessToken'] || signInResult['access_token']; const userIdFromToken = idToken.sub || signInResult['sub'] || sessionId; - const scopes = idToken['scope'] ? idToken['scope'].split(' ') : []; + const scopes = signInResult['scope']; const organizationId = idToken['user_org'] || idToken['organization_id']; const sessionToken = await SessionManager.createSessionToken( @@ -110,9 +118,9 @@ const handleOAuthCallbackAction = async ( cookieStore.delete(SessionManager.getTempSessionCookieName()); } catch (error) { - console.warn( - '[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session:', - error, + logger.error( + `[handleOAuthCallbackAction] Failed to create JWT session, continuing with legacy session: + ${typeof error === 'string' ? error : JSON.stringify(error)}`, ); } }