1717 */
1818
1919import { NextRequest , NextResponse } from 'next/server' ;
20- import AsgardeoNextClient from '../AsgardeoNextClient ' ;
20+ import { CookieConfig } from '@asgardeo/node ' ;
2121import { AsgardeoNextConfig } from '../models/config' ;
2222
23- export interface AsgardeoMiddlewareOptions extends Partial < AsgardeoNextConfig > {
24- debug ?: boolean ;
25- }
26-
27- type AsgardeoAuth = {
28- protect : ( options ?: { redirect ?: string } ) => Promise < NextResponse | void > ;
29- isSignedIn : ( ) => Promise < boolean > ;
30- getUser : ( ) => Promise < any | null > ;
31- redirectToSignIn : ( afterSignInUrl ?: string ) => NextResponse ;
23+ export type AsgardeoMiddlewareOptions = Partial < AsgardeoNextConfig > ;
24+
25+ export type AsgardeoMiddlewareContext = {
26+ /**
27+ * Protect a route by redirecting unauthenticated users.
28+ * Redirect URL fallback order:
29+ * 1. options.redirect
30+ * 2. resolvedOptions.signInUrl
31+ * 3. resolvedOptions.defaultRedirect
32+ * 4. referer (if from same origin)
33+ * If none are available, throws an error.
34+ */
35+ protectRoute : ( options ?: { redirect ?: string } ) => Promise < NextResponse | void > ;
36+ /** Check if the current request has a valid Asgardeo session */
37+ isSignedIn : ( ) => boolean ;
38+ /** Get the session ID from the current request */
39+ getSessionId : ( ) => string | undefined ;
3240} ;
3341
3442type AsgardeoMiddlewareHandler = (
35- auth : AsgardeoAuth ,
43+ asgardeo : AsgardeoMiddlewareContext ,
3644 req : NextRequest ,
3745) => Promise < NextResponse | void > | NextResponse | void ;
3846
47+ /**
48+ * Checks if a request has a valid session ID in cookies.
49+ * This is a lightweight check that can be used in middleware.
50+ *
51+ * @param request - The Next.js request object
52+ * @returns True if a session ID exists, false otherwise
53+ */
54+ const hasValidSession = ( request : NextRequest ) : boolean => {
55+ const sessionId = request . cookies . get ( CookieConfig . SESSION_COOKIE_NAME ) ?. value ;
56+ return Boolean ( sessionId && sessionId . trim ( ) . length > 0 ) ;
57+ } ;
58+
59+ /**
60+ * Gets the session ID from the request cookies.
61+ *
62+ * @param request - The Next.js request object
63+ * @returns The session ID if it exists, undefined otherwise
64+ */
65+ const getSessionIdFromRequest = ( request : NextRequest ) : string | undefined => {
66+ return request . cookies . get ( CookieConfig . SESSION_COOKIE_NAME ) ?. value ;
67+ } ;
68+
3969/**
4070 * Asgardeo middleware that integrates authentication into your Next.js application.
4171 * Similar to Clerk's clerkMiddleware pattern.
@@ -46,23 +76,49 @@ type AsgardeoMiddlewareHandler = (
4676 *
4777 * @example
4878 * ```typescript
49- * // middleware.ts
79+ * // middleware.ts - Basic usage
5080 * import { asgardeoMiddleware } from '@asgardeo/nextjs';
5181 *
5282 * export default asgardeoMiddleware();
5383 * ```
5484 *
5585 * @example
5686 * ```typescript
57- * // With protection
87+ * // With route protection
5888 * import { asgardeoMiddleware, createRouteMatcher } from '@asgardeo/nextjs';
5989 *
6090 * const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
6191 *
62- * export default asgardeoMiddleware(async (auth , req) => {
92+ * export default asgardeoMiddleware(async (asgardeo , req) => {
6393 * if (isProtectedRoute(req)) {
64- * await auth.protect();
94+ * await asgardeo.protectRoute();
95+ * }
96+ * });
97+ * ```
98+ *
99+ * @example
100+ * ```typescript
101+ * // Advanced usage with custom logic
102+ * import { asgardeoMiddleware, createRouteMatcher } from '@asgardeo/nextjs';
103+ *
104+ * const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
105+ * const isAuthRoute = createRouteMatcher(['/sign-in', '/sign-up']);
106+ *
107+ * export default asgardeoMiddleware(async (asgardeo, req) => {
108+ * // Skip protection for auth routes
109+ * if (isAuthRoute(req)) return;
110+ *
111+ * // Protect specified routes
112+ * if (isProtectedRoute(req)) {
113+ * await asgardeo.protectRoute({ redirect: '/sign-in' });
114+ * }
115+ *
116+ * // Check authentication status
117+ * if (asgardeo.isSignedIn()) {
118+ * console.log('User is authenticated with session:', asgardeo.getSessionId());
65119 * }
120+ * }, {
121+ * defaultRedirect: '/sign-in'
66122 * });
67123 * ```
68124 */
@@ -71,78 +127,53 @@ const asgardeoMiddleware = (
71127 options ?: AsgardeoMiddlewareOptions | ( ( req : NextRequest ) => AsgardeoMiddlewareOptions ) ,
72128) : ( ( request : NextRequest ) => Promise < NextResponse > ) => {
73129 return async ( request : NextRequest ) : Promise < NextResponse > => {
74- // Resolve options - can be static or dynamic based on request
75130 const resolvedOptions = typeof options === 'function' ? options ( request ) : options || { } ;
76131
77- const asgardeoClient = AsgardeoNextClient . getInstance ( ) ;
78-
79- // // Initialize client if not already done
80- // if (!asgardeoClient.isInitialized && resolvedOptions) {
81- // asgardeoClient.initialize(resolvedOptions);
82- // }
83-
84- // // Debug logging
85- // if (resolvedOptions.debug) {
86- // console.log(`[Asgardeo Middleware] Processing request: ${request.nextUrl.pathname}`);
87- // }
88-
89- // // Handle auth API routes automatically
90- // if (request.nextUrl.pathname.startsWith('/api/auth/asgardeo')) {
91- // if (resolvedOptions.debug) {
92- // console.log(`[Asgardeo Middleware] Handling auth route: ${request.nextUrl.pathname}`);
93- // }
94- // return await asgardeoClient.handleAuthRequest(request);
95- // }
96-
97- // // Create auth object for the handler
98- // const auth: AsgardeoAuth = {
99- // protect: async (options?: {redirect?: string}) => {
100- // const isSignedIn = await asgardeoClient.isSignedIn(request);
101- // if (!isSignedIn) {
102- // const afterSignInUrl = options?.redirect || '/api/auth/asgardeo/signin';
103- // return NextResponse.redirect(new URL(afterSignInUrl, request.url));
104- // }
105- // },
106-
107- // isSignedIn: async () => {
108- // return await asgardeoClient.isSignedIn(request);
109- // },
110-
111- // getUser: async () => {
112- // return await asgardeoClient.getUser(request);
113- // },
114-
115- // redirectToSignIn: (afterSignInUrl?: string) => {
116- // const signInUrl = afterSignInUrl || '/api/auth/asgardeo/signin';
117- // return NextResponse.redirect(new URL(signInUrl, request.url));
118- // },
119- // };
120-
121- // // Execute user-provided handler if present
122- // let handlerResponse: NextResponse | void;
123- // if (handler) {
124- // handlerResponse = await handler(auth, request);
125- // }
126-
127- // // If handler returned a response, use it
128- // if (handlerResponse) {
129- // return handlerResponse;
130- // }
131-
132- // // Otherwise, continue with default behavior
133- // const response = NextResponse.next();
134-
135- // // Add authentication context to response headers
136- // const isSignedIn = await asgardeoClient.isSignedIn(request);
137- // if (isSignedIn) {
138- // response.headers.set('x-asgardeo-authenticated', 'true');
139- // const user = await asgardeoClient.getUser(request);
140- // if (user?.sub) {
141- // response.headers.set('x-asgardeo-user-id', user.sub);
142- // }
143- // }
144-
145- // return response;
132+ const sessionId = request . cookies . get ( CookieConfig . SESSION_COOKIE_NAME ) ?. value ;
133+ const isAuthenticated = hasValidSession ( request ) ;
134+
135+ const asgardeo : AsgardeoMiddlewareContext = {
136+ protectRoute : async ( options ?: { redirect ?: string } ) : Promise < NextResponse | void > => {
137+ if ( ! isAuthenticated ) {
138+ const referer = request . headers . get ( 'referer' ) ;
139+ // TODO: Make this configurable or call the signIn() from here.
140+ let fallbackRedirect : string = '/' ;
141+
142+ // If referer exists and is from the same origin, use it as fallback
143+ if ( referer ) {
144+ try {
145+ const refererUrl = new URL ( referer ) ;
146+ const requestUrl = new URL ( request . url ) ;
147+
148+ if ( refererUrl . origin === requestUrl . origin ) {
149+ fallbackRedirect = refererUrl . pathname + refererUrl . search ;
150+ }
151+ } catch ( error ) {
152+ // Invalid referer URL, ignore it
153+ }
154+ }
155+
156+ // Fallback chain: options.redirect -> resolvedOptions.signInUrl -> resolvedOptions.defaultRedirect -> referer (same origin only)
157+ const redirectUrl : string = ( resolvedOptions ?. signInUrl as string ) || fallbackRedirect ;
158+
159+ const signInUrl = new URL ( redirectUrl , request . url ) ;
160+
161+ return NextResponse . redirect ( signInUrl ) ;
162+ }
163+
164+ // Session exists, allow access
165+ return ;
166+ } ,
167+ isSignedIn : ( ) => isAuthenticated ,
168+ getSessionId : ( ) => sessionId ,
169+ } ;
170+
171+ if ( handler ) {
172+ const result = await handler ( asgardeo , request ) ;
173+ if ( result ) {
174+ return result ;
175+ }
176+ }
146177
147178 return NextResponse . next ( ) ;
148179 } ;
0 commit comments