Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/afraid-pans-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@asgardeo/nextjs': patch
'@asgardeo/react': patch
---

- Avoid race condition in oauth callback and route protection
- Handle empty payload in sign-in action
4 changes: 2 additions & 2 deletions packages/nextjs/src/server/actions/signInAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@

import {cookies} from 'next/headers';
import {
CookieConfig,
generateSessionId,
EmbeddedSignInFlowStatus,
EmbeddedSignInFlowHandleRequestPayload,
EmbeddedFlowExecuteRequestConfig,
EmbeddedSignInFlowInitiateResponse,
isEmpty,
} from '@asgardeo/node';
import AsgardeoNextClient from '../../AsgardeoNextClient';
import SessionManager from '../../utils/SessionManager';
Expand Down Expand Up @@ -96,7 +96,7 @@ const signInAction = async (
}

// If no payload provided, redirect to sign-in URL for redirect-based sign-in.
if (!payload) {
if (!payload || isEmpty(payload)) {
const defaultSignInUrl = await client.getAuthorizeRequestUrl({}, sessionId);
return {success: true, data: {signInUrl: String(defaultSignInUrl)}};
}
Expand Down
32 changes: 32 additions & 0 deletions packages/nextjs/src/server/middleware/asgardeoMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,43 @@ const asgardeoMiddleware = (
return async (request: NextRequest): Promise<NextResponse> => {
const resolvedOptions = typeof options === 'function' ? options(request) : options || {};

const url: URL = new URL(request.url);
const hasCallbackParams: boolean = url.searchParams.has('code') && url.searchParams.has('state');

let isValidOAuthCallback: boolean = false;
if (hasCallbackParams) {
// OAuth callbacks should not contain error parameters that indicate failed auth
const hasError: boolean = url.searchParams.has('error');

if (!hasError) {
// Validate that there's a temporary session that initiated this OAuth flow
const tempSessionToken: string | undefined = request.cookies.get(
SessionManager.getTempSessionCookieName(),
)?.value;
if (tempSessionToken) {
try {
// Verify the temporary session exists and is valid
await SessionManager.verifyTempSession(tempSessionToken);
isValidOAuthCallback = true;
} catch {
// Invalid temp session - this is not a legitimate OAuth callback
isValidOAuthCallback = false;
}
}
}
}

const sessionId = await getSessionIdFromRequestMiddleware(request);
const isAuthenticated = await hasValidSession(request);

const asgardeo: AsgardeoMiddlewareContext = {
protectRoute: async (options?: {redirect?: string}): Promise<NextResponse | void> => {
// Skip protection if this is a validated OAuth callback - let the callback handler process it first
// This prevents race conditions where middleware redirects before OAuth callback completes
if (isValidOAuthCallback) {
return;
}

if (!isAuthenticated) {
const referer = request.headers.get('referer');
// TODO: Make this configurable or call the signIn() from here.
Expand Down
Loading