@chemmangat/msal-next 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -614,6 +614,7 @@ Note: Environment variables starting with NEXT_PUBLIC_ are exposed to the browse
614
614
 
615
615
  // src/hooks/useTokenRefresh.ts
616
616
  import { useEffect, useRef, useCallback as useCallback2 } from "react";
617
+ import { useMsal as useMsal2 } from "@azure/msal-react";
617
618
 
618
619
  // src/hooks/useMsalAuth.ts
619
620
  import { useMsal, useAccount } from "@azure/msal-react";
@@ -792,7 +793,8 @@ function useTokenRefresh(options = {}) {
792
793
  onRefresh,
793
794
  onError
794
795
  } = options;
795
- const { isAuthenticated, account, acquireTokenSilent } = useMsalAuth();
796
+ const { isAuthenticated, account } = useMsalAuth();
797
+ const { instance } = useMsal2();
796
798
  const intervalRef = useRef(null);
797
799
  const lastRefreshRef = useRef(null);
798
800
  const expiresInRef = useRef(null);
@@ -801,23 +803,27 @@ function useTokenRefresh(options = {}) {
801
803
  return;
802
804
  }
803
805
  try {
804
- await acquireTokenSilent(scopes);
806
+ const response = await instance.acquireTokenSilent({
807
+ scopes,
808
+ account,
809
+ forceRefresh: false
810
+ });
805
811
  lastRefreshRef.current = /* @__PURE__ */ new Date();
806
- const expiresIn = 3600;
812
+ const expiresIn = response.expiresOn ? Math.max(0, response.expiresOn.getTime() / 1e3 - Date.now() / 1e3) : 3600;
807
813
  expiresInRef.current = expiresIn;
808
814
  onRefresh?.(expiresIn);
809
815
  } catch (error) {
810
816
  console.error("[TokenRefresh] Failed to refresh token:", error);
811
817
  onError?.(error);
812
818
  }
813
- }, [isAuthenticated, account, acquireTokenSilent, scopes, onRefresh, onError]);
819
+ }, [isAuthenticated, account, instance, scopes, onRefresh, onError]);
814
820
  useEffect(() => {
815
821
  if (!enabled || !isAuthenticated) {
816
822
  return;
817
823
  }
818
824
  refresh();
819
825
  intervalRef.current = setInterval(() => {
820
- if (!expiresInRef.current) {
826
+ if (expiresInRef.current === null) {
821
827
  return;
822
828
  }
823
829
  const timeSinceRefresh = lastRefreshRef.current ? (Date.now() - lastRefreshRef.current.getTime()) / 1e3 : 0;
@@ -950,6 +956,23 @@ function validateTenantAccess(account, config) {
950
956
  // src/components/MsalAuthProvider.tsx
951
957
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
952
958
  var globalMsalInstance = null;
959
+ function writeMsalSessionCookie(account) {
960
+ try {
961
+ const data = encodeURIComponent(JSON.stringify({
962
+ homeAccountId: account.homeAccountId,
963
+ username: account.username,
964
+ name: account.name ?? ""
965
+ }));
966
+ document.cookie = `msal.account=${data}; path=/; SameSite=Lax`;
967
+ } catch {
968
+ }
969
+ }
970
+ function clearMsalSessionCookie() {
971
+ try {
972
+ document.cookie = "msal.account=; path=/; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:00 GMT";
973
+ } catch {
974
+ }
975
+ }
953
976
  function getMsalInstance() {
954
977
  return globalMsalInstance;
955
978
  }
@@ -999,6 +1022,7 @@ function MsalAuthProvider({
999
1022
  }
1000
1023
  if (response.account) {
1001
1024
  instance.setActiveAccount(response.account);
1025
+ writeMsalSessionCookie(response.account);
1002
1026
  if (config.multiTenant) {
1003
1027
  const validation = validateTenantAccess(response.account, config.multiTenant);
1004
1028
  if (!validation.allowed) {
@@ -1044,6 +1068,7 @@ function MsalAuthProvider({
1044
1068
  const accounts = instance.getAllAccounts();
1045
1069
  if (accounts.length > 0 && !instance.getActiveAccount()) {
1046
1070
  instance.setActiveAccount(accounts[0]);
1071
+ writeMsalSessionCookie(accounts[0]);
1047
1072
  }
1048
1073
  const loggingEnabled = config.enableLogging || false;
1049
1074
  instance.addEventCallback((event) => {
@@ -1052,6 +1077,7 @@ function MsalAuthProvider({
1052
1077
  const account = "account" in payload ? payload.account : payload;
1053
1078
  if (account) {
1054
1079
  instance.setActiveAccount(account);
1080
+ writeMsalSessionCookie(account);
1055
1081
  }
1056
1082
  if (loggingEnabled) {
1057
1083
  console.log("[MSAL] Login successful:", account?.username);
@@ -1063,10 +1089,14 @@ function MsalAuthProvider({
1063
1089
  }
1064
1090
  if (event.eventType === EventType.LOGOUT_SUCCESS) {
1065
1091
  instance.setActiveAccount(null);
1092
+ clearMsalSessionCookie();
1066
1093
  if (loggingEnabled) {
1067
1094
  console.log("[MSAL] Logout successful");
1068
1095
  }
1069
1096
  }
1097
+ if (EventType.LOGOUT_END !== void 0 && event.eventType === EventType.LOGOUT_END) {
1098
+ clearMsalSessionCookie();
1099
+ }
1070
1100
  if (event.eventType === EventType.ACQUIRE_TOKEN_SUCCESS) {
1071
1101
  const payload = event.payload;
1072
1102
  if (payload?.account && !instance.getActiveAccount()) {
@@ -1868,11 +1898,11 @@ var ErrorBoundary = class extends Component {
1868
1898
  };
1869
1899
 
1870
1900
  // src/hooks/useMultiAccount.ts
1871
- import { useMsal as useMsal2 } from "@azure/msal-react";
1901
+ import { useMsal as useMsal3 } from "@azure/msal-react";
1872
1902
  import { InteractionStatus as InteractionStatus2 } from "@azure/msal-browser";
1873
1903
  import { useCallback as useCallback5, useMemo as useMemo2, useState as useState5, useEffect as useEffect7 } from "react";
1874
1904
  function useMultiAccount(defaultScopes = ["User.Read"]) {
1875
- const { instance, accounts, inProgress } = useMsal2();
1905
+ const { instance, accounts, inProgress } = useMsal3();
1876
1906
  const [activeAccount, setActiveAccount] = useState5(
1877
1907
  instance.getActiveAccount()
1878
1908
  );
@@ -3106,107 +3136,8 @@ function withPageAuth(Component2, authConfig, globalConfig) {
3106
3136
  return WrappedComponent;
3107
3137
  }
3108
3138
 
3109
- // src/middleware/createAuthMiddleware.ts
3110
- import { NextResponse } from "next/server";
3111
- function createAuthMiddleware(config = {}) {
3112
- const {
3113
- protectedRoutes = [],
3114
- publicOnlyRoutes = [],
3115
- loginPath = "/login",
3116
- redirectAfterLogin = "/",
3117
- sessionCookie = "msal.account",
3118
- isAuthenticated: customAuthCheck,
3119
- tenantConfig,
3120
- tenantDeniedPath = "/unauthorized",
3121
- debug = false
3122
- } = config;
3123
- return async function authMiddleware(request) {
3124
- const { pathname } = request.nextUrl;
3125
- if (debug) {
3126
- console.log("[AuthMiddleware] Processing:", pathname);
3127
- }
3128
- let authenticated = false;
3129
- if (customAuthCheck) {
3130
- authenticated = await customAuthCheck(request);
3131
- } else {
3132
- const sessionData = request.cookies.get(sessionCookie);
3133
- authenticated = !!sessionData?.value;
3134
- }
3135
- if (debug) {
3136
- console.log("[AuthMiddleware] Authenticated:", authenticated);
3137
- }
3138
- const isProtectedRoute = protectedRoutes.some(
3139
- (route) => pathname.startsWith(route)
3140
- );
3141
- const isPublicOnlyRoute = publicOnlyRoutes.some(
3142
- (route) => pathname.startsWith(route)
3143
- );
3144
- if (isProtectedRoute && !authenticated) {
3145
- if (debug) {
3146
- console.log("[AuthMiddleware] Redirecting to login");
3147
- }
3148
- const url = request.nextUrl.clone();
3149
- url.pathname = loginPath;
3150
- url.searchParams.set("returnUrl", pathname);
3151
- return NextResponse.redirect(url);
3152
- }
3153
- if (isProtectedRoute && authenticated && tenantConfig) {
3154
- try {
3155
- const sessionData = request.cookies.get(sessionCookie);
3156
- if (sessionData?.value) {
3157
- const account = safeJsonParse(sessionData.value, isValidAccountData);
3158
- if (account) {
3159
- const tenantResult = validateTenantAccess(account, tenantConfig);
3160
- if (!tenantResult.allowed) {
3161
- if (debug) {
3162
- console.log("[AuthMiddleware] Tenant access denied:", tenantResult.reason);
3163
- }
3164
- const url = request.nextUrl.clone();
3165
- url.pathname = tenantDeniedPath;
3166
- url.searchParams.set("reason", tenantResult.reason || "access_denied");
3167
- return NextResponse.redirect(url);
3168
- }
3169
- }
3170
- }
3171
- } catch (error) {
3172
- if (debug) {
3173
- console.warn("[AuthMiddleware] Tenant validation error:", error);
3174
- }
3175
- }
3176
- }
3177
- if (isPublicOnlyRoute && authenticated) {
3178
- if (debug) {
3179
- console.log("[AuthMiddleware] Redirecting to home");
3180
- }
3181
- const returnUrl = request.nextUrl.searchParams.get("returnUrl");
3182
- const url = request.nextUrl.clone();
3183
- url.pathname = returnUrl || redirectAfterLogin;
3184
- url.searchParams.delete("returnUrl");
3185
- return NextResponse.redirect(url);
3186
- }
3187
- const response = NextResponse.next();
3188
- if (authenticated) {
3189
- response.headers.set("x-msal-authenticated", "true");
3190
- try {
3191
- const sessionData = request.cookies.get(sessionCookie);
3192
- if (sessionData?.value) {
3193
- const account = safeJsonParse(sessionData.value, isValidAccountData);
3194
- if (account?.username) {
3195
- response.headers.set("x-msal-username", account.username);
3196
- }
3197
- }
3198
- } catch (error) {
3199
- if (debug) {
3200
- console.warn("[AuthMiddleware] Failed to parse session data");
3201
- }
3202
- }
3203
- }
3204
- return response;
3205
- };
3206
- }
3207
-
3208
3139
  // src/client.ts
3209
- import { useMsal as useMsal3, useIsAuthenticated, useAccount as useAccount2 } from "@azure/msal-react";
3140
+ import { useMsal as useMsal4, useIsAuthenticated, useAccount as useAccount2 } from "@azure/msal-react";
3210
3141
  export {
3211
3142
  AccountList,
3212
3143
  AccountSwitcher,
@@ -3220,7 +3151,6 @@ export {
3220
3151
  ProtectedPage,
3221
3152
  SignOutButton,
3222
3153
  UserAvatar,
3223
- createAuthMiddleware,
3224
3154
  createMissingEnvVarError,
3225
3155
  createMsalConfig,
3226
3156
  createRetryWrapper,
@@ -3237,7 +3167,7 @@ export {
3237
3167
  useAccount2 as useAccount,
3238
3168
  useGraphApi,
3239
3169
  useIsAuthenticated,
3240
- useMsal3 as useMsal,
3170
+ useMsal4 as useMsal,
3241
3171
  useMsalAuth,
3242
3172
  useMultiAccount,
3243
3173
  useRoles,
@@ -0,0 +1,115 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * Type definitions for @chemmangat/msal-next
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ /**
10
+ * Multi-tenant configuration for v5.1.0
11
+ */
12
+ interface MultiTenantConfig {
13
+ /**
14
+ * Tenant mode
15
+ * - 'single' — only your own tenant (maps to authorityType 'tenant')
16
+ * - 'multi' — any Azure AD tenant (maps to authorityType 'common')
17
+ * - 'organizations' — any organisational tenant, no personal accounts
18
+ * - 'consumers' — Microsoft personal accounts only
19
+ * - 'common' — alias for 'multi'
20
+ *
21
+ * @defaultValue 'common'
22
+ */
23
+ type?: 'single' | 'multi' | 'organizations' | 'consumers' | 'common';
24
+ /**
25
+ * Tenant allow-list — only users from these tenant IDs or domains are permitted.
26
+ * Checked after authentication; users from other tenants are shown an error.
27
+ *
28
+ * @example ['contoso.com', '72f988bf-86f1-41af-91ab-2d7cd011db47']
29
+ */
30
+ allowList?: string[];
31
+ /**
32
+ * Tenant block-list — users from these tenant IDs or domains are denied.
33
+ * Takes precedence over allowList.
34
+ */
35
+ blockList?: string[];
36
+ /**
37
+ * Require a specific tenant type ('Member' | 'Guest').
38
+ * Useful to block B2B guests or to allow only guests.
39
+ */
40
+ requireType?: 'Member' | 'Guest';
41
+ /**
42
+ * Require MFA claim in the token (amr claim must contain 'mfa').
43
+ * @defaultValue false
44
+ */
45
+ requireMFA?: boolean;
46
+ }
47
+
48
+ interface AuthMiddlewareConfig {
49
+ /**
50
+ * Routes that require authentication
51
+ * @example ['/dashboard', '/profile', '/api/protected']
52
+ */
53
+ protectedRoutes?: string[];
54
+ /**
55
+ * Routes that should be accessible only when NOT authenticated
56
+ * @example ['/login', '/signup']
57
+ */
58
+ publicOnlyRoutes?: string[];
59
+ /**
60
+ * Login page path
61
+ * @default '/login'
62
+ */
63
+ loginPath?: string;
64
+ /**
65
+ * Redirect path after login
66
+ * @default '/'
67
+ */
68
+ redirectAfterLogin?: string;
69
+ /**
70
+ * Cookie name for session
71
+ * @default 'msal.account'
72
+ */
73
+ sessionCookie?: string;
74
+ /**
75
+ * Custom authentication check function
76
+ */
77
+ isAuthenticated?: (request: NextRequest) => boolean | Promise<boolean>;
78
+ /**
79
+ * Tenant access configuration (v5.1.0).
80
+ * Validated against the account stored in the session cookie.
81
+ */
82
+ tenantConfig?: MultiTenantConfig;
83
+ /**
84
+ * Path to redirect to when tenant access is denied (v5.1.0).
85
+ * @default '/unauthorized'
86
+ */
87
+ tenantDeniedPath?: string;
88
+ /**
89
+ * Enable debug logging
90
+ * @default false
91
+ */
92
+ debug?: boolean;
93
+ }
94
+ /**
95
+ * Creates authentication middleware for Next.js App Router
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * // middleware.ts
100
+ * import { createAuthMiddleware } from '@chemmangat/msal-next';
101
+ *
102
+ * export const middleware = createAuthMiddleware({
103
+ * protectedRoutes: ['/dashboard', '/profile'],
104
+ * publicOnlyRoutes: ['/login'],
105
+ * loginPath: '/login',
106
+ * });
107
+ *
108
+ * export const config = {
109
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
110
+ * };
111
+ * ```
112
+ */
113
+ declare function createAuthMiddleware(config?: AuthMiddlewareConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
114
+
115
+ export { type AuthMiddlewareConfig, createAuthMiddleware };
@@ -0,0 +1,115 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * Type definitions for @chemmangat/msal-next
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ /**
10
+ * Multi-tenant configuration for v5.1.0
11
+ */
12
+ interface MultiTenantConfig {
13
+ /**
14
+ * Tenant mode
15
+ * - 'single' — only your own tenant (maps to authorityType 'tenant')
16
+ * - 'multi' — any Azure AD tenant (maps to authorityType 'common')
17
+ * - 'organizations' — any organisational tenant, no personal accounts
18
+ * - 'consumers' — Microsoft personal accounts only
19
+ * - 'common' — alias for 'multi'
20
+ *
21
+ * @defaultValue 'common'
22
+ */
23
+ type?: 'single' | 'multi' | 'organizations' | 'consumers' | 'common';
24
+ /**
25
+ * Tenant allow-list — only users from these tenant IDs or domains are permitted.
26
+ * Checked after authentication; users from other tenants are shown an error.
27
+ *
28
+ * @example ['contoso.com', '72f988bf-86f1-41af-91ab-2d7cd011db47']
29
+ */
30
+ allowList?: string[];
31
+ /**
32
+ * Tenant block-list — users from these tenant IDs or domains are denied.
33
+ * Takes precedence over allowList.
34
+ */
35
+ blockList?: string[];
36
+ /**
37
+ * Require a specific tenant type ('Member' | 'Guest').
38
+ * Useful to block B2B guests or to allow only guests.
39
+ */
40
+ requireType?: 'Member' | 'Guest';
41
+ /**
42
+ * Require MFA claim in the token (amr claim must contain 'mfa').
43
+ * @defaultValue false
44
+ */
45
+ requireMFA?: boolean;
46
+ }
47
+
48
+ interface AuthMiddlewareConfig {
49
+ /**
50
+ * Routes that require authentication
51
+ * @example ['/dashboard', '/profile', '/api/protected']
52
+ */
53
+ protectedRoutes?: string[];
54
+ /**
55
+ * Routes that should be accessible only when NOT authenticated
56
+ * @example ['/login', '/signup']
57
+ */
58
+ publicOnlyRoutes?: string[];
59
+ /**
60
+ * Login page path
61
+ * @default '/login'
62
+ */
63
+ loginPath?: string;
64
+ /**
65
+ * Redirect path after login
66
+ * @default '/'
67
+ */
68
+ redirectAfterLogin?: string;
69
+ /**
70
+ * Cookie name for session
71
+ * @default 'msal.account'
72
+ */
73
+ sessionCookie?: string;
74
+ /**
75
+ * Custom authentication check function
76
+ */
77
+ isAuthenticated?: (request: NextRequest) => boolean | Promise<boolean>;
78
+ /**
79
+ * Tenant access configuration (v5.1.0).
80
+ * Validated against the account stored in the session cookie.
81
+ */
82
+ tenantConfig?: MultiTenantConfig;
83
+ /**
84
+ * Path to redirect to when tenant access is denied (v5.1.0).
85
+ * @default '/unauthorized'
86
+ */
87
+ tenantDeniedPath?: string;
88
+ /**
89
+ * Enable debug logging
90
+ * @default false
91
+ */
92
+ debug?: boolean;
93
+ }
94
+ /**
95
+ * Creates authentication middleware for Next.js App Router
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * // middleware.ts
100
+ * import { createAuthMiddleware } from '@chemmangat/msal-next';
101
+ *
102
+ * export const middleware = createAuthMiddleware({
103
+ * protectedRoutes: ['/dashboard', '/profile'],
104
+ * publicOnlyRoutes: ['/login'],
105
+ * loginPath: '/login',
106
+ * });
107
+ *
108
+ * export const config = {
109
+ * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
110
+ * };
111
+ * ```
112
+ */
113
+ declare function createAuthMiddleware(config?: AuthMiddlewareConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
114
+
115
+ export { type AuthMiddlewareConfig, createAuthMiddleware };
@@ -0,0 +1,203 @@
1
+ 'use strict';
2
+
3
+ var server = require('next/server');
4
+
5
+ // src/middleware/createAuthMiddleware.ts
6
+
7
+ // src/utils/validation.ts
8
+ function safeJsonParse(jsonString, validator) {
9
+ try {
10
+ const parsed = JSON.parse(jsonString);
11
+ if (validator(parsed)) {
12
+ return parsed;
13
+ }
14
+ console.warn("[Validation] JSON validation failed");
15
+ return null;
16
+ } catch (error) {
17
+ console.error("[Validation] JSON parse error:", error);
18
+ return null;
19
+ }
20
+ }
21
+ function isValidAccountData(data) {
22
+ return typeof data === "object" && data !== null && typeof data.homeAccountId === "string" && data.homeAccountId.length > 0 && typeof data.username === "string" && data.username.length > 0 && (data.name === void 0 || typeof data.name === "string");
23
+ }
24
+
25
+ // src/utils/tenantValidator.ts
26
+ function getTenantDomain(account) {
27
+ const upn = account.username || account.idTokenClaims?.preferred_username || account.idTokenClaims?.upn || "";
28
+ return upn.includes("@") ? upn.split("@")[1].toLowerCase() : null;
29
+ }
30
+ function getTenantId(account) {
31
+ return account.tenantId || account.idTokenClaims?.tid || null;
32
+ }
33
+ function matchesTenant(value, tenantId, tenantDomain) {
34
+ const v = value.toLowerCase();
35
+ if (tenantId && v === tenantId.toLowerCase()) return true;
36
+ if (tenantDomain && v === tenantDomain.toLowerCase()) return true;
37
+ return false;
38
+ }
39
+ function isGuestAccount(account) {
40
+ const claims = account.idTokenClaims ?? {};
41
+ const resourceTenantId = account.tenantId || claims["tid"] || null;
42
+ const issuer = claims["iss"] || null;
43
+ if (!issuer || !resourceTenantId) return false;
44
+ const match = issuer.match(
45
+ /https:\/\/login\.microsoftonline\.com\/([^/]+)(?:\/|$)/i
46
+ );
47
+ if (!match) return false;
48
+ const homeTenantId = match[1];
49
+ return homeTenantId.toLowerCase() !== resourceTenantId.toLowerCase();
50
+ }
51
+ function validateTenantAccess(account, config) {
52
+ const tenantId = getTenantId(account);
53
+ const tenantDomain = getTenantDomain(account);
54
+ const claims = account.idTokenClaims ?? {};
55
+ if (config.blockList && config.blockList.length > 0) {
56
+ const blocked = config.blockList.some(
57
+ (entry) => matchesTenant(entry, tenantId, tenantDomain)
58
+ );
59
+ if (blocked) {
60
+ return {
61
+ allowed: false,
62
+ reason: `Tenant "${tenantDomain || tenantId}" is blocked from accessing this application.`
63
+ };
64
+ }
65
+ }
66
+ if (config.allowList && config.allowList.length > 0) {
67
+ const allowed = config.allowList.some(
68
+ (entry) => matchesTenant(entry, tenantId, tenantDomain)
69
+ );
70
+ if (!allowed) {
71
+ return {
72
+ allowed: false,
73
+ reason: `Tenant "${tenantDomain || tenantId}" is not in the allowed list for this application.`
74
+ };
75
+ }
76
+ }
77
+ if (config.requireType) {
78
+ const isGuest = isGuestAccount(account);
79
+ if (config.requireType === "Member" && isGuest) {
80
+ return {
81
+ allowed: false,
82
+ reason: "Only member accounts are allowed. Guest (B2B) accounts are not permitted."
83
+ };
84
+ }
85
+ if (config.requireType === "Guest" && !isGuest) {
86
+ return {
87
+ allowed: false,
88
+ reason: "Only guest (B2B) accounts are allowed."
89
+ };
90
+ }
91
+ }
92
+ if (config.requireMFA) {
93
+ const amr = claims["amr"] || [];
94
+ const hasMfa = amr.includes("mfa") || amr.includes("ngcmfa") || amr.includes("hwk") || amr.includes("swk");
95
+ if (!hasMfa) {
96
+ return {
97
+ allowed: false,
98
+ reason: "Multi-factor authentication (MFA) is required to access this application."
99
+ };
100
+ }
101
+ }
102
+ return { allowed: true };
103
+ }
104
+
105
+ // src/middleware/createAuthMiddleware.ts
106
+ function createAuthMiddleware(config = {}) {
107
+ const {
108
+ protectedRoutes = [],
109
+ publicOnlyRoutes = [],
110
+ loginPath = "/login",
111
+ redirectAfterLogin = "/",
112
+ sessionCookie = "msal.account",
113
+ isAuthenticated: customAuthCheck,
114
+ tenantConfig,
115
+ tenantDeniedPath = "/unauthorized",
116
+ debug = false
117
+ } = config;
118
+ return async function authMiddleware(request) {
119
+ const { pathname } = request.nextUrl;
120
+ if (debug) {
121
+ console.log("[AuthMiddleware] Processing:", pathname);
122
+ }
123
+ let authenticated = false;
124
+ if (customAuthCheck) {
125
+ authenticated = await customAuthCheck(request);
126
+ } else {
127
+ const sessionData = request.cookies.get(sessionCookie);
128
+ authenticated = !!sessionData?.value;
129
+ }
130
+ if (debug) {
131
+ console.log("[AuthMiddleware] Authenticated:", authenticated);
132
+ }
133
+ const isProtectedRoute = protectedRoutes.some(
134
+ (route) => pathname.startsWith(route)
135
+ );
136
+ const isPublicOnlyRoute = publicOnlyRoutes.some(
137
+ (route) => pathname.startsWith(route)
138
+ );
139
+ if (isProtectedRoute && !authenticated) {
140
+ if (debug) {
141
+ console.log("[AuthMiddleware] Redirecting to login");
142
+ }
143
+ const url = request.nextUrl.clone();
144
+ url.pathname = loginPath;
145
+ url.searchParams.set("returnUrl", pathname);
146
+ return server.NextResponse.redirect(url);
147
+ }
148
+ if (isProtectedRoute && authenticated && tenantConfig) {
149
+ try {
150
+ const sessionData = request.cookies.get(sessionCookie);
151
+ if (sessionData?.value) {
152
+ const account = safeJsonParse(sessionData.value, isValidAccountData);
153
+ if (account) {
154
+ const tenantResult = validateTenantAccess(account, tenantConfig);
155
+ if (!tenantResult.allowed) {
156
+ if (debug) {
157
+ console.log("[AuthMiddleware] Tenant access denied:", tenantResult.reason);
158
+ }
159
+ const url = request.nextUrl.clone();
160
+ url.pathname = tenantDeniedPath;
161
+ url.searchParams.set("reason", tenantResult.reason || "access_denied");
162
+ return server.NextResponse.redirect(url);
163
+ }
164
+ }
165
+ }
166
+ } catch (error) {
167
+ if (debug) {
168
+ console.warn("[AuthMiddleware] Tenant validation error:", error);
169
+ }
170
+ }
171
+ }
172
+ if (isPublicOnlyRoute && authenticated) {
173
+ if (debug) {
174
+ console.log("[AuthMiddleware] Redirecting to home");
175
+ }
176
+ const returnUrl = request.nextUrl.searchParams.get("returnUrl");
177
+ const url = request.nextUrl.clone();
178
+ url.pathname = returnUrl || redirectAfterLogin;
179
+ url.searchParams.delete("returnUrl");
180
+ return server.NextResponse.redirect(url);
181
+ }
182
+ const response = server.NextResponse.next();
183
+ if (authenticated) {
184
+ response.headers.set("x-msal-authenticated", "true");
185
+ try {
186
+ const sessionData = request.cookies.get(sessionCookie);
187
+ if (sessionData?.value) {
188
+ const account = safeJsonParse(sessionData.value, isValidAccountData);
189
+ if (account?.username) {
190
+ response.headers.set("x-msal-username", account.username);
191
+ }
192
+ }
193
+ } catch (error) {
194
+ if (debug) {
195
+ console.warn("[AuthMiddleware] Failed to parse session data");
196
+ }
197
+ }
198
+ }
199
+ return response;
200
+ };
201
+ }
202
+
203
+ exports.createAuthMiddleware = createAuthMiddleware;