UNPKG

@asgardeo/nextjs

Version:

Next.js implementation of Asgardeo JavaScript SDK.

181 lines 7.09 kB
/** * 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