@chemmangat/msal-next 5.2.1 → 5.3.1

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/CHANGELOG.md CHANGED
@@ -2,7 +2,42 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [5.2.1] - 2026-04-07
5
+ ## [5.3.0] - 2026-04-07
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ #### Fix 1 — Auto-sync `msal.account` session cookie (no manual cookie code needed)
10
+
11
+ `MsalAuthProvider` now automatically writes and clears the `msal.account` cookie on every auth event. The middleware works out of the box with zero manual setup.
12
+
13
+ - `LOGIN_SUCCESS` → writes `msal.account` cookie (`{ homeAccountId, username, name }`, URL-encoded, `path=/; SameSite=Lax`)
14
+ - `LOGOUT_SUCCESS` / `LOGOUT_END` → clears the cookie
15
+ - Redirect flow (`handleRedirectPromise`) → writes cookie immediately after redirect completes
16
+ - Existing cached account on init → restores cookie (handles browser restart with `localStorage` cache)
17
+
18
+ #### Fix 2 — `setServerSessionCookie` no longer calls a non-existent API route
19
+
20
+ The old implementation called `fetch('/api/auth/session')` which required an undocumented route that didn't exist. It now writes `document.cookie` directly — same format as Fix 1. The function signature changed from `async (account, accessToken?) => Promise<void>` to `(account) => void`.
21
+
22
+ A new `clearServerSessionCookie()` helper is also exported from `@chemmangat/msal-next/server`.
23
+
24
+ > As of v5.3.0 you rarely need to call either function manually — `MsalAuthProvider` handles the cookie automatically.
25
+
26
+ #### Fix 3 — `useTokenRefresh` now reads real token expiry
27
+
28
+ The hook previously hardcoded `expiresIn = 3600`. It now calls `instance.acquireTokenSilent` directly and reads `response.expiresOn` to calculate the actual remaining seconds. Falls back to `3600` if `expiresOn` is `null`.
29
+
30
+ #### Fix 4 — `navigateToLoginRequestUrl` JSDoc default corrected
31
+
32
+ The `@defaultValue` in `MsalAuthConfig` was documented as `true` but the actual default in `createMsalConfig` was `false`. Corrected to `@defaultValue false`.
33
+
34
+ #### Fix 5 — No `/api/auth/session` route required
35
+
36
+ All references to the non-existent `/api/auth/session` route have been removed. Cookie management is now entirely client-side via `document.cookie`.
37
+
38
+ ---
39
+
40
+
6
41
 
7
42
  ### 🐛 Bug Fix
8
43
 
package/dist/index.d.mts CHANGED
@@ -167,7 +167,7 @@ interface MsalAuthConfig {
167
167
  * @remarks
168
168
  * If true, redirects to the page that initiated login after successful auth.
169
169
  *
170
- * @defaultValue true
170
+ * @defaultValue false
171
171
  */
172
172
  navigateToLoginRequestUrl?: boolean;
173
173
  /**
@@ -1037,6 +1037,11 @@ interface GraphApiOptions extends RequestInit {
1037
1037
  * @default false
1038
1038
  */
1039
1039
  debug?: boolean;
1040
+ /**
1041
+ * Expected response type. Use 'blob' for binary data like images.
1042
+ * @default 'json'
1043
+ */
1044
+ responseType?: 'json' | 'blob' | 'text';
1040
1045
  }
1041
1046
  interface UseGraphApiReturn {
1042
1047
  /**
package/dist/index.d.ts CHANGED
@@ -167,7 +167,7 @@ interface MsalAuthConfig {
167
167
  * @remarks
168
168
  * If true, redirects to the page that initiated login after successful auth.
169
169
  *
170
- * @defaultValue true
170
+ * @defaultValue false
171
171
  */
172
172
  navigateToLoginRequestUrl?: boolean;
173
173
  /**
@@ -1037,6 +1037,11 @@ interface GraphApiOptions extends RequestInit {
1037
1037
  * @default false
1038
1038
  */
1039
1039
  debug?: boolean;
1040
+ /**
1041
+ * Expected response type. Use 'blob' for binary data like images.
1042
+ * @default 'json'
1043
+ */
1044
+ responseType?: 'json' | 'blob' | 'text';
1040
1045
  }
1041
1046
  interface UseGraphApiReturn {
1042
1047
  /**
package/dist/index.js CHANGED
@@ -46,10 +46,10 @@ __export(client_exports, {
46
46
  retryWithBackoff: () => retryWithBackoff,
47
47
  safeJsonParse: () => safeJsonParse,
48
48
  sanitizeError: () => sanitizeError,
49
- useAccount: () => import_msal_react4.useAccount,
49
+ useAccount: () => import_msal_react5.useAccount,
50
50
  useGraphApi: () => useGraphApi,
51
- useIsAuthenticated: () => import_msal_react4.useIsAuthenticated,
52
- useMsal: () => import_msal_react4.useMsal,
51
+ useIsAuthenticated: () => import_msal_react5.useIsAuthenticated,
52
+ useMsal: () => import_msal_react5.useMsal,
53
53
  useMsalAuth: () => useMsalAuth,
54
54
  useMultiAccount: () => useMultiAccount,
55
55
  useRoles: () => useRoles,
@@ -66,7 +66,7 @@ __export(client_exports, {
66
66
  module.exports = __toCommonJS(client_exports);
67
67
 
68
68
  // src/components/MsalAuthProvider.tsx
69
- var import_msal_react2 = require("@azure/msal-react");
69
+ var import_msal_react3 = require("@azure/msal-react");
70
70
  var import_msal_browser3 = require("@azure/msal-browser");
71
71
  var import_react3 = require("react");
72
72
 
@@ -673,6 +673,7 @@ Note: Environment variables starting with NEXT_PUBLIC_ are exposed to the browse
673
673
 
674
674
  // src/hooks/useTokenRefresh.ts
675
675
  var import_react2 = require("react");
676
+ var import_msal_react2 = require("@azure/msal-react");
676
677
 
677
678
  // src/hooks/useMsalAuth.ts
678
679
  var import_msal_react = require("@azure/msal-react");
@@ -851,7 +852,8 @@ function useTokenRefresh(options = {}) {
851
852
  onRefresh,
852
853
  onError
853
854
  } = options;
854
- const { isAuthenticated, account, acquireTokenSilent } = useMsalAuth();
855
+ const { isAuthenticated, account } = useMsalAuth();
856
+ const { instance } = (0, import_msal_react2.useMsal)();
855
857
  const intervalRef = (0, import_react2.useRef)(null);
856
858
  const lastRefreshRef = (0, import_react2.useRef)(null);
857
859
  const expiresInRef = (0, import_react2.useRef)(null);
@@ -860,23 +862,27 @@ function useTokenRefresh(options = {}) {
860
862
  return;
861
863
  }
862
864
  try {
863
- await acquireTokenSilent(scopes);
865
+ const response = await instance.acquireTokenSilent({
866
+ scopes,
867
+ account,
868
+ forceRefresh: false
869
+ });
864
870
  lastRefreshRef.current = /* @__PURE__ */ new Date();
865
- const expiresIn = 3600;
871
+ const expiresIn = response.expiresOn ? Math.max(0, response.expiresOn.getTime() / 1e3 - Date.now() / 1e3) : 3600;
866
872
  expiresInRef.current = expiresIn;
867
873
  onRefresh?.(expiresIn);
868
874
  } catch (error) {
869
875
  console.error("[TokenRefresh] Failed to refresh token:", error);
870
876
  onError?.(error);
871
877
  }
872
- }, [isAuthenticated, account, acquireTokenSilent, scopes, onRefresh, onError]);
878
+ }, [isAuthenticated, account, instance, scopes, onRefresh, onError]);
873
879
  (0, import_react2.useEffect)(() => {
874
880
  if (!enabled || !isAuthenticated) {
875
881
  return;
876
882
  }
877
883
  refresh();
878
884
  intervalRef.current = setInterval(() => {
879
- if (!expiresInRef.current) {
885
+ if (expiresInRef.current === null) {
880
886
  return;
881
887
  }
882
888
  const timeSinceRefresh = lastRefreshRef.current ? (Date.now() - lastRefreshRef.current.getTime()) / 1e3 : 0;
@@ -1009,6 +1015,23 @@ function validateTenantAccess(account, config) {
1009
1015
  // src/components/MsalAuthProvider.tsx
1010
1016
  var import_jsx_runtime = require("react/jsx-runtime");
1011
1017
  var globalMsalInstance = null;
1018
+ function writeMsalSessionCookie(account) {
1019
+ try {
1020
+ const data = encodeURIComponent(JSON.stringify({
1021
+ homeAccountId: account.homeAccountId,
1022
+ username: account.username,
1023
+ name: account.name ?? ""
1024
+ }));
1025
+ document.cookie = `msal.account=${data}; path=/; SameSite=Lax`;
1026
+ } catch {
1027
+ }
1028
+ }
1029
+ function clearMsalSessionCookie() {
1030
+ try {
1031
+ document.cookie = "msal.account=; path=/; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:00 GMT";
1032
+ } catch {
1033
+ }
1034
+ }
1012
1035
  function getMsalInstance() {
1013
1036
  return globalMsalInstance;
1014
1037
  }
@@ -1058,6 +1081,7 @@ function MsalAuthProvider({
1058
1081
  }
1059
1082
  if (response.account) {
1060
1083
  instance.setActiveAccount(response.account);
1084
+ writeMsalSessionCookie(response.account);
1061
1085
  if (config.multiTenant) {
1062
1086
  const validation = validateTenantAccess(response.account, config.multiTenant);
1063
1087
  if (!validation.allowed) {
@@ -1103,6 +1127,7 @@ function MsalAuthProvider({
1103
1127
  const accounts = instance.getAllAccounts();
1104
1128
  if (accounts.length > 0 && !instance.getActiveAccount()) {
1105
1129
  instance.setActiveAccount(accounts[0]);
1130
+ writeMsalSessionCookie(accounts[0]);
1106
1131
  }
1107
1132
  const loggingEnabled = config.enableLogging || false;
1108
1133
  instance.addEventCallback((event) => {
@@ -1111,6 +1136,7 @@ function MsalAuthProvider({
1111
1136
  const account = "account" in payload ? payload.account : payload;
1112
1137
  if (account) {
1113
1138
  instance.setActiveAccount(account);
1139
+ writeMsalSessionCookie(account);
1114
1140
  }
1115
1141
  if (loggingEnabled) {
1116
1142
  console.log("[MSAL] Login successful:", account?.username);
@@ -1122,10 +1148,14 @@ function MsalAuthProvider({
1122
1148
  }
1123
1149
  if (event.eventType === import_msal_browser3.EventType.LOGOUT_SUCCESS) {
1124
1150
  instance.setActiveAccount(null);
1151
+ clearMsalSessionCookie();
1125
1152
  if (loggingEnabled) {
1126
1153
  console.log("[MSAL] Logout successful");
1127
1154
  }
1128
1155
  }
1156
+ if (import_msal_browser3.EventType.LOGOUT_END !== void 0 && event.eventType === import_msal_browser3.EventType.LOGOUT_END) {
1157
+ clearMsalSessionCookie();
1158
+ }
1129
1159
  if (event.eventType === import_msal_browser3.EventType.ACQUIRE_TOKEN_SUCCESS) {
1130
1160
  const payload = event.payload;
1131
1161
  if (payload?.account && !instance.getActiveAccount()) {
@@ -1157,7 +1187,7 @@ function MsalAuthProvider({
1157
1187
  if (!msalInstance) {
1158
1188
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: loadingComponent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "Loading authentication..." }) });
1159
1189
  }
1160
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_msal_react2.MsalProvider, { instance: msalInstance, children: [
1190
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_msal_react3.MsalProvider, { instance: msalInstance, children: [
1161
1191
  autoRefreshToken && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1162
1192
  TokenRefreshManager,
1163
1193
  {
@@ -1400,6 +1430,7 @@ function useGraphApi() {
1400
1430
  scopes = ["User.Read"],
1401
1431
  version = "v1.0",
1402
1432
  debug = false,
1433
+ responseType = "json",
1403
1434
  ...fetchOptions
1404
1435
  } = options;
1405
1436
  try {
@@ -1425,9 +1456,16 @@ function useGraphApi() {
1425
1456
  if (response.status === 204 || response.headers.get("content-length") === "0") {
1426
1457
  return null;
1427
1458
  }
1428
- const data = await response.json();
1459
+ let data;
1460
+ if (responseType === "blob") {
1461
+ data = await response.blob();
1462
+ } else if (responseType === "text") {
1463
+ data = await response.text();
1464
+ } else {
1465
+ data = await response.json();
1466
+ }
1429
1467
  if (debug) {
1430
- console.log("[GraphAPI] Response:", data);
1468
+ console.log("[GraphAPI] Response:", responseType === "blob" ? "[Blob]" : data);
1431
1469
  }
1432
1470
  return data;
1433
1471
  } catch (error) {
@@ -1535,11 +1573,9 @@ function useUserProfile() {
1535
1573
  try {
1536
1574
  const photoBlob = await graph.get("/me/photo/$value", {
1537
1575
  scopes: ["User.Read"],
1538
- headers: {
1539
- "Content-Type": "image/jpeg"
1540
- }
1576
+ responseType: "blob"
1541
1577
  });
1542
- if (photoBlob) {
1578
+ if (photoBlob instanceof Blob && photoBlob.size > 0) {
1543
1579
  photoUrl = URL.createObjectURL(photoBlob);
1544
1580
  }
1545
1581
  } catch (photoError) {
@@ -1927,11 +1963,11 @@ var ErrorBoundary = class extends import_react10.Component {
1927
1963
  };
1928
1964
 
1929
1965
  // src/hooks/useMultiAccount.ts
1930
- var import_msal_react3 = require("@azure/msal-react");
1966
+ var import_msal_react4 = require("@azure/msal-react");
1931
1967
  var import_msal_browser4 = require("@azure/msal-browser");
1932
1968
  var import_react11 = require("react");
1933
1969
  function useMultiAccount(defaultScopes = ["User.Read"]) {
1934
- const { instance, accounts, inProgress } = (0, import_msal_react3.useMsal)();
1970
+ const { instance, accounts, inProgress } = (0, import_msal_react4.useMsal)();
1935
1971
  const [activeAccount, setActiveAccount] = (0, import_react11.useState)(
1936
1972
  instance.getActiveAccount()
1937
1973
  );
@@ -3166,7 +3202,7 @@ function withPageAuth(Component2, authConfig, globalConfig) {
3166
3202
  }
3167
3203
 
3168
3204
  // src/client.ts
3169
- var import_msal_react4 = require("@azure/msal-react");
3205
+ var import_msal_react5 = require("@azure/msal-react");
3170
3206
  // Annotate the CommonJS export names for ESM import in node:
3171
3207
  0 && (module.exports = {
3172
3208
  AccountList,
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()) {
@@ -1341,6 +1371,7 @@ function useGraphApi() {
1341
1371
  scopes = ["User.Read"],
1342
1372
  version = "v1.0",
1343
1373
  debug = false,
1374
+ responseType = "json",
1344
1375
  ...fetchOptions
1345
1376
  } = options;
1346
1377
  try {
@@ -1366,9 +1397,16 @@ function useGraphApi() {
1366
1397
  if (response.status === 204 || response.headers.get("content-length") === "0") {
1367
1398
  return null;
1368
1399
  }
1369
- const data = await response.json();
1400
+ let data;
1401
+ if (responseType === "blob") {
1402
+ data = await response.blob();
1403
+ } else if (responseType === "text") {
1404
+ data = await response.text();
1405
+ } else {
1406
+ data = await response.json();
1407
+ }
1370
1408
  if (debug) {
1371
- console.log("[GraphAPI] Response:", data);
1409
+ console.log("[GraphAPI] Response:", responseType === "blob" ? "[Blob]" : data);
1372
1410
  }
1373
1411
  return data;
1374
1412
  } catch (error) {
@@ -1476,11 +1514,9 @@ function useUserProfile() {
1476
1514
  try {
1477
1515
  const photoBlob = await graph.get("/me/photo/$value", {
1478
1516
  scopes: ["User.Read"],
1479
- headers: {
1480
- "Content-Type": "image/jpeg"
1481
- }
1517
+ responseType: "blob"
1482
1518
  });
1483
- if (photoBlob) {
1519
+ if (photoBlob instanceof Blob && photoBlob.size > 0) {
1484
1520
  photoUrl = URL.createObjectURL(photoBlob);
1485
1521
  }
1486
1522
  } catch (photoError) {
@@ -1868,11 +1904,11 @@ var ErrorBoundary = class extends Component {
1868
1904
  };
1869
1905
 
1870
1906
  // src/hooks/useMultiAccount.ts
1871
- import { useMsal as useMsal2 } from "@azure/msal-react";
1907
+ import { useMsal as useMsal3 } from "@azure/msal-react";
1872
1908
  import { InteractionStatus as InteractionStatus2 } from "@azure/msal-browser";
1873
1909
  import { useCallback as useCallback5, useMemo as useMemo2, useState as useState5, useEffect as useEffect7 } from "react";
1874
1910
  function useMultiAccount(defaultScopes = ["User.Read"]) {
1875
- const { instance, accounts, inProgress } = useMsal2();
1911
+ const { instance, accounts, inProgress } = useMsal3();
1876
1912
  const [activeAccount, setActiveAccount] = useState5(
1877
1913
  instance.getActiveAccount()
1878
1914
  );
@@ -3107,7 +3143,7 @@ function withPageAuth(Component2, authConfig, globalConfig) {
3107
3143
  }
3108
3144
 
3109
3145
  // src/client.ts
3110
- import { useMsal as useMsal3, useIsAuthenticated, useAccount as useAccount2 } from "@azure/msal-react";
3146
+ import { useMsal as useMsal4, useIsAuthenticated, useAccount as useAccount2 } from "@azure/msal-react";
3111
3147
  export {
3112
3148
  AccountList,
3113
3149
  AccountSwitcher,
@@ -3137,7 +3173,7 @@ export {
3137
3173
  useAccount2 as useAccount,
3138
3174
  useGraphApi,
3139
3175
  useIsAuthenticated,
3140
- useMsal3 as useMsal,
3176
+ useMsal4 as useMsal,
3141
3177
  useMsalAuth,
3142
3178
  useMultiAccount,
3143
3179
  useRoles,
package/dist/server.d.mts CHANGED
@@ -41,14 +41,33 @@ interface ServerSession {
41
41
  */
42
42
  declare function getServerSession(): Promise<ServerSession>;
43
43
  /**
44
- * Helper to set server session in cookies (call from client-side after auth)
44
+ * Writes the `msal.account` session cookie directly via `document.cookie`.
45
+ *
46
+ * @remarks
47
+ * **Must be called from a Client Component** (browser context only).
48
+ *
49
+ * As of v5.3.0 this is no longer necessary for most apps — `MsalAuthProvider`
50
+ * automatically writes and clears the cookie on every login/logout event.
51
+ * Only call this manually if you need to set the cookie outside of the normal
52
+ * MSAL auth flow (e.g. after a silent SSO check in a custom component).
45
53
  *
46
54
  * @example
47
55
  * ```tsx
48
- * // After successful login
49
- * await setServerSessionCookie(account, accessToken);
56
+ * 'use client';
57
+ * import { setServerSessionCookie } from '@chemmangat/msal-next/server';
58
+ *
59
+ * // After a custom auth event:
60
+ * setServerSessionCookie(account);
50
61
  * ```
51
62
  */
52
- declare function setServerSessionCookie(account: any, accessToken?: string): Promise<void>;
63
+ declare function setServerSessionCookie(account: any): void;
64
+ /**
65
+ * Clears the `msal.account` session cookie.
66
+ *
67
+ * @remarks
68
+ * **Must be called from a Client Component** (browser context only).
69
+ * As of v5.3.0 this is handled automatically by `MsalAuthProvider` on logout.
70
+ */
71
+ declare function clearServerSessionCookie(): void;
53
72
 
54
- export { type ServerSession, getServerSession, setServerSessionCookie };
73
+ export { type ServerSession, clearServerSessionCookie, getServerSession, setServerSessionCookie };
package/dist/server.d.ts CHANGED
@@ -41,14 +41,33 @@ interface ServerSession {
41
41
  */
42
42
  declare function getServerSession(): Promise<ServerSession>;
43
43
  /**
44
- * Helper to set server session in cookies (call from client-side after auth)
44
+ * Writes the `msal.account` session cookie directly via `document.cookie`.
45
+ *
46
+ * @remarks
47
+ * **Must be called from a Client Component** (browser context only).
48
+ *
49
+ * As of v5.3.0 this is no longer necessary for most apps — `MsalAuthProvider`
50
+ * automatically writes and clears the cookie on every login/logout event.
51
+ * Only call this manually if you need to set the cookie outside of the normal
52
+ * MSAL auth flow (e.g. after a silent SSO check in a custom component).
45
53
  *
46
54
  * @example
47
55
  * ```tsx
48
- * // After successful login
49
- * await setServerSessionCookie(account, accessToken);
56
+ * 'use client';
57
+ * import { setServerSessionCookie } from '@chemmangat/msal-next/server';
58
+ *
59
+ * // After a custom auth event:
60
+ * setServerSessionCookie(account);
50
61
  * ```
51
62
  */
52
- declare function setServerSessionCookie(account: any, accessToken?: string): Promise<void>;
63
+ declare function setServerSessionCookie(account: any): void;
64
+ /**
65
+ * Clears the `msal.account` session cookie.
66
+ *
67
+ * @remarks
68
+ * **Must be called from a Client Component** (browser context only).
69
+ * As of v5.3.0 this is handled automatically by `MsalAuthProvider` on logout.
70
+ */
71
+ declare function clearServerSessionCookie(): void;
53
72
 
54
- export { type ServerSession, getServerSession, setServerSessionCookie };
73
+ export { type ServerSession, clearServerSessionCookie, getServerSession, setServerSessionCookie };
package/dist/server.js CHANGED
@@ -63,27 +63,29 @@ async function getServerSession() {
63
63
  };
64
64
  }
65
65
  }
66
- async function setServerSessionCookie(account, accessToken) {
66
+ function setServerSessionCookie(account) {
67
+ if (typeof document === "undefined") {
68
+ console.warn("[ServerSession] setServerSessionCookie must be called in a browser (Client Component) context.");
69
+ return;
70
+ }
67
71
  try {
68
- const accountData = {
72
+ const data = encodeURIComponent(JSON.stringify({
69
73
  homeAccountId: account.homeAccountId,
70
74
  username: account.username,
71
- name: account.name
72
- };
73
- await fetch("/api/auth/session", {
74
- method: "POST",
75
- headers: {
76
- "Content-Type": "application/json"
77
- },
78
- body: JSON.stringify({
79
- account: accountData,
80
- token: accessToken
81
- })
82
- });
75
+ name: account.name ?? ""
76
+ }));
77
+ document.cookie = `msal.account=${data}; path=/; SameSite=Lax`;
83
78
  } catch (error) {
84
79
  console.error("[ServerSession] Failed to set session cookie:", error);
85
80
  }
86
81
  }
82
+ function clearServerSessionCookie() {
83
+ if (typeof document === "undefined") {
84
+ return;
85
+ }
86
+ document.cookie = "msal.account=; path=/; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:00 GMT";
87
+ }
87
88
 
89
+ exports.clearServerSessionCookie = clearServerSessionCookie;
88
90
  exports.getServerSession = getServerSession;
89
91
  exports.setServerSessionCookie = setServerSessionCookie;
package/dist/server.mjs CHANGED
@@ -61,26 +61,27 @@ async function getServerSession() {
61
61
  };
62
62
  }
63
63
  }
64
- async function setServerSessionCookie(account, accessToken) {
64
+ function setServerSessionCookie(account) {
65
+ if (typeof document === "undefined") {
66
+ console.warn("[ServerSession] setServerSessionCookie must be called in a browser (Client Component) context.");
67
+ return;
68
+ }
65
69
  try {
66
- const accountData = {
70
+ const data = encodeURIComponent(JSON.stringify({
67
71
  homeAccountId: account.homeAccountId,
68
72
  username: account.username,
69
- name: account.name
70
- };
71
- await fetch("/api/auth/session", {
72
- method: "POST",
73
- headers: {
74
- "Content-Type": "application/json"
75
- },
76
- body: JSON.stringify({
77
- account: accountData,
78
- token: accessToken
79
- })
80
- });
73
+ name: account.name ?? ""
74
+ }));
75
+ document.cookie = `msal.account=${data}; path=/; SameSite=Lax`;
81
76
  } catch (error) {
82
77
  console.error("[ServerSession] Failed to set session cookie:", error);
83
78
  }
84
79
  }
80
+ function clearServerSessionCookie() {
81
+ if (typeof document === "undefined") {
82
+ return;
83
+ }
84
+ document.cookie = "msal.account=; path=/; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:00 GMT";
85
+ }
85
86
 
86
- export { getServerSession, setServerSessionCookie };
87
+ export { clearServerSessionCookie, getServerSession, setServerSessionCookie };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chemmangat/msal-next",
3
- "version": "5.2.1",
3
+ "version": "5.3.1",
4
4
  "description": "Production-ready Microsoft/Azure AD authentication for Next.js App Router. Zero-config setup, TypeScript-first, multi-account support, auto token refresh. The easiest way to add Microsoft login to your Next.js app.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",