@asgardeo/nextjs
Version:
Next.js implementation of Asgardeo JavaScript SDK.
181 lines • 7.09 kB
JavaScript
/**
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { NextResponse } from 'next/server';
import SessionManager from '../../utils/SessionManager';
import { hasValidSession as hasValidJWTSession, getSessionFromRequest, getSessionIdFromRequest, } from '../../utils/sessionUtils';
/**
* Enhanced session validation that checks both JWT and legacy sessions
*
* @param request - The Next.js request object
* @returns True if a valid session exists, false otherwise
*/
const hasValidSession = async (request) => {
try {
return await hasValidJWTSession(request);
}
catch {
return Promise.resolve(false);
}
};
/**
* Gets the session ID from the request cookies.
* Supports both JWT and legacy session formats.
*
* @param request - The Next.js request object
* @returns The session ID if it exists, undefined otherwise
*/
const getSessionIdFromRequestMiddleware = async (request) => {
return await getSessionIdFromRequest(request);
};
/**
* Asgardeo middleware that integrates authentication into your Next.js application.
* Similar to Clerk's clerkMiddleware pattern.
*
* @param handler - Optional handler function to customize middleware behavior
* @param options - Configuration options for the middleware
* @returns Next.js middleware function
*
* @example
* ```typescript
* // middleware.ts - Basic usage
* import { asgardeoMiddleware } from '@asgardeo/nextjs';
*
* export default asgardeoMiddleware();
* ```
*
* @example
* ```typescript
* // With route protection
* import { asgardeoMiddleware, createRouteMatcher } from '@asgardeo/nextjs';
*
* const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
*
* export default asgardeoMiddleware(async (asgardeo, req) => {
* if (isProtectedRoute(req)) {
* await asgardeo.protectRoute();
* }
* });
* ```
*
* @example
* ```typescript
* // Advanced usage with custom logic
* import { asgardeoMiddleware, createRouteMatcher } from '@asgardeo/nextjs';
*
* const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
* const isAuthRoute = createRouteMatcher(['/sign-in', '/sign-up']);
*
* export default asgardeoMiddleware(async (asgardeo, req) => {
* // Skip protection for auth routes
* if (isAuthRoute(req)) return;
*
* // Protect specified routes
* if (isProtectedRoute(req)) {
* await asgardeo.protectRoute({ redirect: '/sign-in' });
* }
*
* // Check authentication status
* if (asgardeo.isSignedIn()) {
* console.log('User is authenticated with session:', asgardeo.getSessionId());
* }
* }, {
* defaultRedirect: '/sign-in'
* });
* ```
*/
const asgardeoMiddleware = (handler, options) => {
return async (request) => {
const resolvedOptions = typeof options === 'function' ? options(request) : options || {};
const url = new URL(request.url);
const hasCallbackParams = url.searchParams.has('code') && url.searchParams.has('state');
let isValidOAuthCallback = false;
if (hasCallbackParams) {
// OAuth callbacks should not contain error parameters that indicate failed auth
const hasError = url.searchParams.has('error');
if (!hasError) {
// Validate that there's a temporary session that initiated this OAuth flow
const tempSessionToken = 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 = {
protectRoute: async (options) => {
// 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.
let fallbackRedirect = '/';
// If referer exists and is from the same origin, use it as fallback
if (referer) {
try {
const refererUrl = new URL(referer);
const requestUrl = new URL(request.url);
if (refererUrl.origin === requestUrl.origin) {
fallbackRedirect = refererUrl.pathname + refererUrl.search;
}
}
catch (error) {
// Invalid referer URL, ignore it
}
}
// Fallback chain: options.redirect -> resolvedOptions.signInUrl -> resolvedOptions.defaultRedirect -> referer (same origin only)
const redirectUrl = resolvedOptions?.signInUrl || fallbackRedirect;
const signInUrl = new URL(redirectUrl, request.url);
return NextResponse.redirect(signInUrl);
}
// Session exists, allow access
return;
},
isSignedIn: () => isAuthenticated,
getSessionId: () => sessionId,
getSession: async () => {
try {
return await getSessionFromRequest(request);
}
catch {
return undefined;
}
},
};
if (handler) {
const result = await handler(asgardeo, request);
if (result) {
return result;
}
}
return NextResponse.next();
};
};
export default asgardeoMiddleware;
//# sourceMappingURL=asgardeoMiddleware.js.map