Skip to content

Commit 61f51a1

Browse files
authored
Merge pull request #209 from brionmario/thunder
fix(nextjs): avoid race condition in oauth callback and route protection
2 parents 3dab8c0 + 3b87244 commit 61f51a1

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

.changeset/afraid-pans-own.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@asgardeo/nextjs': patch
3+
'@asgardeo/react': patch
4+
---
5+
6+
- Avoid race condition in oauth callback and route protection
7+
- Handle empty payload in sign-in action

packages/nextjs/src/server/actions/signInAction.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020

2121
import {cookies} from 'next/headers';
2222
import {
23-
CookieConfig,
2423
generateSessionId,
2524
EmbeddedSignInFlowStatus,
2625
EmbeddedSignInFlowHandleRequestPayload,
2726
EmbeddedFlowExecuteRequestConfig,
2827
EmbeddedSignInFlowInitiateResponse,
28+
isEmpty,
2929
} from '@asgardeo/node';
3030
import AsgardeoNextClient from '../../AsgardeoNextClient';
3131
import SessionManager from '../../utils/SessionManager';
@@ -96,7 +96,7 @@ const signInAction = async (
9696
}
9797

9898
// If no payload provided, redirect to sign-in URL for redirect-based sign-in.
99-
if (!payload) {
99+
if (!payload || isEmpty(payload)) {
100100
const defaultSignInUrl = await client.getAuthorizeRequestUrl({}, sessionId);
101101
return {success: true, data: {signInUrl: String(defaultSignInUrl)}};
102102
}

packages/nextjs/src/server/middleware/asgardeoMiddleware.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,43 @@ const asgardeoMiddleware = (
140140
return async (request: NextRequest): Promise<NextResponse> => {
141141
const resolvedOptions = typeof options === 'function' ? options(request) : options || {};
142142

143+
const url: URL = new URL(request.url);
144+
const hasCallbackParams: boolean = url.searchParams.has('code') && url.searchParams.has('state');
145+
146+
let isValidOAuthCallback: boolean = false;
147+
if (hasCallbackParams) {
148+
// OAuth callbacks should not contain error parameters that indicate failed auth
149+
const hasError: boolean = url.searchParams.has('error');
150+
151+
if (!hasError) {
152+
// Validate that there's a temporary session that initiated this OAuth flow
153+
const tempSessionToken: string | undefined = request.cookies.get(
154+
SessionManager.getTempSessionCookieName(),
155+
)?.value;
156+
if (tempSessionToken) {
157+
try {
158+
// Verify the temporary session exists and is valid
159+
await SessionManager.verifyTempSession(tempSessionToken);
160+
isValidOAuthCallback = true;
161+
} catch {
162+
// Invalid temp session - this is not a legitimate OAuth callback
163+
isValidOAuthCallback = false;
164+
}
165+
}
166+
}
167+
}
168+
143169
const sessionId = await getSessionIdFromRequestMiddleware(request);
144170
const isAuthenticated = await hasValidSession(request);
145171

146172
const asgardeo: AsgardeoMiddlewareContext = {
147173
protectRoute: async (options?: {redirect?: string}): Promise<NextResponse | void> => {
174+
// Skip protection if this is a validated OAuth callback - let the callback handler process it first
175+
// This prevents race conditions where middleware redirects before OAuth callback completes
176+
if (isValidOAuthCallback) {
177+
return;
178+
}
179+
148180
if (!isAuthenticated) {
149181
const referer = request.headers.get('referer');
150182
// TODO: Make this configurable or call the signIn() from here.

0 commit comments

Comments
 (0)