@chemmangat/msal-next 2.0.1 → 2.1.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/dist/server.d.mts CHANGED
@@ -13,7 +13,6 @@ interface ServerSession {
13
13
  username?: string;
14
14
  /**
15
15
  * Access token (if available in cookie)
16
- * @deprecated Storing tokens in cookies is not recommended for security reasons
17
16
  */
18
17
  accessToken?: string;
19
18
  }
package/dist/server.d.ts CHANGED
@@ -13,7 +13,6 @@ interface ServerSession {
13
13
  username?: string;
14
14
  /**
15
15
  * Access token (if available in cookie)
16
- * @deprecated Storing tokens in cookies is not recommended for security reasons
17
16
  */
18
17
  accessToken?: string;
19
18
  }
package/dist/server.js CHANGED
@@ -1,91 +1 @@
1
- 'use strict';
2
-
3
- var headers = require('next/headers');
4
-
5
- // src/utils/getServerSession.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/getServerSession.ts
26
- async function getServerSession() {
27
- try {
28
- const cookieStore = await headers.cookies();
29
- const headersList = await headers.headers();
30
- const msalAccount = cookieStore.get("msal.account");
31
- const msalToken = cookieStore.get("msal.token");
32
- if (msalAccount?.value) {
33
- const accountData = safeJsonParse(
34
- msalAccount.value,
35
- isValidAccountData
36
- );
37
- if (accountData) {
38
- return {
39
- isAuthenticated: true,
40
- accountId: accountData.homeAccountId,
41
- username: accountData.username,
42
- accessToken: msalToken?.value
43
- };
44
- } else {
45
- console.warn("[ServerSession] Invalid account data in cookie");
46
- }
47
- }
48
- const authHeader = headersList.get("x-msal-authenticated");
49
- if (authHeader === "true") {
50
- const username = headersList.get("x-msal-username");
51
- return {
52
- isAuthenticated: true,
53
- username: username || void 0
54
- };
55
- }
56
- return {
57
- isAuthenticated: false
58
- };
59
- } catch (error) {
60
- console.error("[ServerSession] Error reading session:", error);
61
- return {
62
- isAuthenticated: false
63
- };
64
- }
65
- }
66
- async function setServerSessionCookie(account, accessToken) {
67
- try {
68
- const accountData = {
69
- homeAccountId: account.homeAccountId,
70
- 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
- });
83
- } catch (error) {
84
- console.error("[ServerSession] Failed to set session cookie:", error);
85
- }
86
- }
87
-
88
- exports.getServerSession = getServerSession;
89
- exports.setServerSessionCookie = setServerSessionCookie;
90
- //# sourceMappingURL=server.js.map
91
- //# sourceMappingURL=server.js.map
1
+ 'use strict';var headers=require('next/headers');async function i(){try{let e=await headers.cookies(),r=await headers.headers(),t=e.get("msal.account"),o=e.get("msal.token");if(t?.value)try{let s=JSON.parse(t.value);return {isAuthenticated:!0,accountId:s.homeAccountId,username:s.username,accessToken:o?.value}}catch(s){console.error("[ServerSession] Failed to parse account data:",s);}return r.get("x-msal-authenticated")==="true"?{isAuthenticated:!0,username:r.get("x-msal-username")||void 0}:{isAuthenticated:!1}}catch(e){return console.error("[ServerSession] Error reading session:",e),{isAuthenticated:false}}}async function c(e,r){try{let t={homeAccountId:e.homeAccountId,username:e.username,name:e.name};await fetch("/api/auth/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({account:t,token:r})});}catch(t){console.error("[ServerSession] Failed to set session cookie:",t);}}exports.getServerSession=i;exports.setServerSessionCookie=c;
package/dist/server.mjs CHANGED
@@ -1,88 +1 @@
1
- import { cookies, headers } from 'next/headers';
2
-
3
- // src/utils/getServerSession.ts
4
-
5
- // src/utils/validation.ts
6
- function safeJsonParse(jsonString, validator) {
7
- try {
8
- const parsed = JSON.parse(jsonString);
9
- if (validator(parsed)) {
10
- return parsed;
11
- }
12
- console.warn("[Validation] JSON validation failed");
13
- return null;
14
- } catch (error) {
15
- console.error("[Validation] JSON parse error:", error);
16
- return null;
17
- }
18
- }
19
- function isValidAccountData(data) {
20
- 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");
21
- }
22
-
23
- // src/utils/getServerSession.ts
24
- async function getServerSession() {
25
- try {
26
- const cookieStore = await cookies();
27
- const headersList = await headers();
28
- const msalAccount = cookieStore.get("msal.account");
29
- const msalToken = cookieStore.get("msal.token");
30
- if (msalAccount?.value) {
31
- const accountData = safeJsonParse(
32
- msalAccount.value,
33
- isValidAccountData
34
- );
35
- if (accountData) {
36
- return {
37
- isAuthenticated: true,
38
- accountId: accountData.homeAccountId,
39
- username: accountData.username,
40
- accessToken: msalToken?.value
41
- };
42
- } else {
43
- console.warn("[ServerSession] Invalid account data in cookie");
44
- }
45
- }
46
- const authHeader = headersList.get("x-msal-authenticated");
47
- if (authHeader === "true") {
48
- const username = headersList.get("x-msal-username");
49
- return {
50
- isAuthenticated: true,
51
- username: username || void 0
52
- };
53
- }
54
- return {
55
- isAuthenticated: false
56
- };
57
- } catch (error) {
58
- console.error("[ServerSession] Error reading session:", error);
59
- return {
60
- isAuthenticated: false
61
- };
62
- }
63
- }
64
- async function setServerSessionCookie(account, accessToken) {
65
- try {
66
- const accountData = {
67
- homeAccountId: account.homeAccountId,
68
- 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
- });
81
- } catch (error) {
82
- console.error("[ServerSession] Failed to set session cookie:", error);
83
- }
84
- }
85
-
86
- export { getServerSession, setServerSessionCookie };
87
- //# sourceMappingURL=server.mjs.map
88
- //# sourceMappingURL=server.mjs.map
1
+ import {cookies,headers}from'next/headers';async function i(){try{let e=await cookies(),r=await headers(),t=e.get("msal.account"),o=e.get("msal.token");if(t?.value)try{let s=JSON.parse(t.value);return {isAuthenticated:!0,accountId:s.homeAccountId,username:s.username,accessToken:o?.value}}catch(s){console.error("[ServerSession] Failed to parse account data:",s);}return r.get("x-msal-authenticated")==="true"?{isAuthenticated:!0,username:r.get("x-msal-username")||void 0}:{isAuthenticated:!1}}catch(e){return console.error("[ServerSession] Error reading session:",e),{isAuthenticated:false}}}async function c(e,r){try{let t={homeAccountId:e.homeAccountId,username:e.username,name:e.name};await fetch("/api/auth/session",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({account:t,token:r})});}catch(t){console.error("[ServerSession] Failed to set session cookie:",t);}}export{i as getServerSession,c as setServerSessionCookie};
package/package.json CHANGED
@@ -1,77 +1,94 @@
1
- {
2
- "name": "@chemmangat/msal-next",
3
- "version": "2.0.1",
4
- "description": "Production-grade MSAL authentication package for Next.js App Router with minimal boilerplate",
5
- "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.js"
13
- },
14
- "./server": {
15
- "types": "./dist/server.d.ts",
16
- "import": "./dist/server.mjs",
17
- "require": "./dist/server.js"
18
- }
19
- },
20
- "files": [
21
- "dist",
22
- "README.md",
23
- "SECURITY.md"
24
- ],
25
- "scripts": {
26
- "build": "tsup",
27
- "dev": "tsup --watch",
28
- "test": "vitest run",
29
- "test:watch": "vitest",
30
- "test:coverage": "vitest run --coverage",
31
- "prepublishOnly": "npm run build"
32
- },
33
- "keywords": [
34
- "msal",
35
- "nextjs",
36
- "authentication",
37
- "azure-ad",
38
- "microsoft",
39
- "oauth",
40
- "next.js",
41
- "app-router",
42
- "typescript",
43
- "sso",
44
- "microsoft-graph"
45
- ],
46
- "author": "Hari Manoj (chemmangat)",
47
- "license": "MIT",
48
- "repository": {
49
- "type": "git",
50
- "url": "https://github.com/chemmangat/msal-next.git",
51
- "directory": "packages/core"
52
- },
53
- "homepage": "https://github.com/chemmangat/msal-next#readme",
54
- "bugs": {
55
- "url": "https://github.com/chemmangat/msal-next/issues"
56
- },
57
- "peerDependencies": {
58
- "@azure/msal-browser": "^3.11.0 || ^4.0.0",
59
- "@azure/msal-react": "^2.0.0 || ^3.0.0",
60
- "next": ">=14.0.0",
61
- "react": ">=18.0.0",
62
- "react-dom": ">=18.0.0"
63
- },
64
- "devDependencies": {
65
- "@azure/msal-browser": "^3.11.1",
66
- "@azure/msal-react": "^2.0.15",
67
- "@testing-library/react": "^14.0.0",
68
- "@testing-library/react-hooks": "^8.0.1",
69
- "@types/react": "^18.2.0",
70
- "@vitest/coverage-v8": "^1.0.0",
71
- "jsdom": "^23.0.0",
72
- "react": "^18.2.0",
73
- "tsup": "^8.0.1",
74
- "typescript": "^5.3.0",
75
- "vitest": "^1.0.0"
76
- }
77
- }
1
+ {
2
+ "name": "@chemmangat/msal-next",
3
+ "version": "2.1.1",
4
+ "description": "Production-grade MSAL authentication package for Next.js App Router with minimal boilerplate",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./server": {
15
+ "types": "./dist/server.d.ts",
16
+ "import": "./dist/server.mjs",
17
+ "require": "./dist/server.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "dev": "tsup --watch",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest",
29
+ "test:coverage": "vitest run --coverage",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "keywords": [
33
+ "msal",
34
+ "nextjs",
35
+ "next.js",
36
+ "next",
37
+ "authentication",
38
+ "auth",
39
+ "azure-ad",
40
+ "azure",
41
+ "microsoft",
42
+ "microsoft-authentication",
43
+ "oauth",
44
+ "oauth2",
45
+ "app-router",
46
+ "typescript",
47
+ "sso",
48
+ "single-sign-on",
49
+ "microsoft-graph",
50
+ "graph-api",
51
+ "azure-active-directory",
52
+ "entra-id",
53
+ "login",
54
+ "signin",
55
+ "react",
56
+ "msal-react",
57
+ "msal-browser",
58
+ "next14",
59
+ "next15",
60
+ "nextjs-auth",
61
+ "microsoft-login"
62
+ ],
63
+ "author": "Chemmangat",
64
+ "license": "MIT",
65
+ "repository": {
66
+ "type": "git",
67
+ "url": "https://github.com/chemmangat/msal-next.git",
68
+ "directory": "packages/core"
69
+ },
70
+ "homepage": "https://github.com/chemmangat/msal-next#readme",
71
+ "bugs": {
72
+ "url": "https://github.com/chemmangat/msal-next/issues"
73
+ },
74
+ "peerDependencies": {
75
+ "@azure/msal-browser": "^3.11.0 || ^4.0.0",
76
+ "@azure/msal-react": "^2.0.0 || ^3.0.0",
77
+ "next": ">=14.0.0",
78
+ "react": ">=18.0.0",
79
+ "react-dom": ">=18.0.0"
80
+ },
81
+ "devDependencies": {
82
+ "@azure/msal-browser": "^3.11.1",
83
+ "@azure/msal-react": "^2.0.15",
84
+ "@testing-library/react": "^14.0.0",
85
+ "@testing-library/react-hooks": "^8.0.1",
86
+ "@types/react": "^18.2.0",
87
+ "@vitest/coverage-v8": "^1.0.0",
88
+ "jsdom": "^23.0.0",
89
+ "react": "^18.2.0",
90
+ "tsup": "^8.0.1",
91
+ "typescript": "^5.3.0",
92
+ "vitest": "^1.0.0"
93
+ }
94
+ }
package/SECURITY.md DELETED
@@ -1,152 +0,0 @@
1
- # Security Policy
2
-
3
- ## Supported Versions
4
-
5
- | Version | Supported |
6
- | ------- | ------------------ |
7
- | 2.0.x | :white_check_mark: |
8
- | < 2.0 | :x: |
9
-
10
- ## Security Updates in v2.0.1
11
-
12
- This release addresses several security vulnerabilities discovered in v2.0.0. We strongly recommend all users upgrade immediately.
13
-
14
- ### Fixed Vulnerabilities
15
-
16
- #### 1. Memory Leaks from Blob URLs (Medium Severity)
17
- **Affected:** `useUserProfile` hook
18
- **Fixed:** Added proper cleanup of blob URLs using `URL.revokeObjectURL()` in useEffect cleanup functions.
19
-
20
- #### 2. Unbounded Cache Growth (Medium Severity)
21
- **Affected:** `useRoles` and `useUserProfile` hooks
22
- **Fixed:** Implemented LRU cache eviction with a maximum size limit of 100 entries to prevent memory exhaustion.
23
-
24
- #### 3. Race Conditions in Token Acquisition (Medium Severity)
25
- **Affected:** `useMsalAuth` hook
26
- **Fixed:** Implemented request deduplication to prevent multiple concurrent popup windows and token requests.
27
-
28
- #### 4. JSON Parsing Without Validation (High Severity)
29
- **Affected:** `getServerSession`, `createAuthMiddleware`
30
- **Fixed:** Added schema validation for all JSON parsing operations using the new `safeJsonParse` utility.
31
-
32
- #### 5. Information Disclosure in Error Messages (Medium Severity)
33
- **Affected:** All hooks and utilities
34
- **Fixed:** Implemented error sanitization to remove tokens, secrets, and sensitive information from error messages.
35
-
36
- #### 6. Missing Redirect URI Validation (Medium Severity)
37
- **Affected:** `createMsalConfig`
38
- **Fixed:** Added optional `allowedRedirectUris` configuration to validate redirect URIs and prevent open redirect vulnerabilities.
39
-
40
- ### New Security Features
41
-
42
- - **Input Validation Utilities**: New `validation.ts` module with functions for safe JSON parsing, scope validation, and redirect URI validation
43
- - **Error Sanitization**: Automatic removal of tokens and secrets from error messages
44
- - **Cache Management**: Proper cache size limits and cleanup on component unmount
45
- - **Request Deduplication**: Prevention of concurrent token acquisition requests
46
-
47
- ## Best Practices
48
-
49
- ### DO NOT Store Tokens in Cookies
50
-
51
- The example code in `src/examples/api-route-session.ts` demonstrates cookie-based session management but includes a warning. **Do not use this pattern in production** as it exposes tokens to CSRF attacks.
52
-
53
- Instead:
54
- - Use MSAL's built-in sessionStorage/localStorage (client-side only)
55
- - Implement proper server-side session management with encrypted session IDs
56
- - Never store access tokens in cookies
57
-
58
- ### Use Redirect URI Validation
59
-
60
- ```typescript
61
- <MsalAuthProvider
62
- clientId={process.env.NEXT_PUBLIC_CLIENT_ID!}
63
- allowedRedirectUris={[
64
- 'https://myapp.com',
65
- 'https://staging.myapp.com',
66
- 'http://localhost:3000'
67
- ]}
68
- >
69
- {children}
70
- </MsalAuthProvider>
71
- ```
72
-
73
- ### Implement Security Headers
74
-
75
- Add comprehensive security headers in your `next.config.js`:
76
-
77
- ```javascript
78
- async headers() {
79
- return [
80
- {
81
- source: '/(.*)',
82
- headers: [
83
- {
84
- key: 'Content-Security-Policy',
85
- value: "default-src 'self'; script-src 'self'; connect-src 'self' https://login.microsoftonline.com https://graph.microsoft.com;"
86
- },
87
- {
88
- key: 'X-Frame-Options',
89
- value: 'DENY'
90
- },
91
- {
92
- key: 'X-Content-Type-Options',
93
- value: 'nosniff'
94
- },
95
- {
96
- key: 'Referrer-Policy',
97
- value: 'strict-origin-when-cross-origin'
98
- },
99
- {
100
- key: 'Permissions-Policy',
101
- value: 'camera=(), microphone=(), geolocation=()'
102
- }
103
- ]
104
- }
105
- ];
106
- }
107
- ```
108
-
109
- ### Validate Scopes
110
-
111
- Use the built-in scope validation:
112
-
113
- ```typescript
114
- import { validateScopes } from '@chemmangat/msal-next';
115
-
116
- const scopes = ['User.Read', 'Mail.Read'];
117
- if (!validateScopes(scopes)) {
118
- throw new Error('Invalid scopes');
119
- }
120
- ```
121
-
122
- ### Use HTTPS in Development
123
-
124
- Always test with HTTPS locally to catch security issues early:
125
-
126
- ```bash
127
- # Use mkcert or similar tools
128
- mkcert localhost
129
- ```
130
-
131
- ## Reporting a Vulnerability
132
-
133
- If you discover a security vulnerability, please email security@chemmangat.com with:
134
-
135
- 1. Description of the vulnerability
136
- 2. Steps to reproduce
137
- 3. Potential impact
138
- 4. Suggested fix (if any)
139
-
140
- We will respond within 48 hours and provide a timeline for a fix.
141
-
142
- ## Security Checklist
143
-
144
- - [ ] Updated to v2.0.1 or later
145
- - [ ] Not storing tokens in cookies
146
- - [ ] Security headers configured in next.config.js
147
- - [ ] Redirect URI validation enabled
148
- - [ ] Using HTTPS in production
149
- - [ ] Regular dependency audits (`npm audit`)
150
- - [ ] Environment variables properly secured
151
- - [ ] CSRF protection on state-changing endpoints
152
- - [ ] Rate limiting on authentication endpoints