@chemmangat/msal-next 3.0.6 → 3.1.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/CHANGELOG.md +598 -0
- package/RELEASE_NOTES_v3.0.8.md +70 -0
- package/dist/index.js +67 -61
- package/dist/index.mjs +45 -39
- package/package.json +4 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [3.0.8] - 2026-03-05
|
|
6
|
+
|
|
7
|
+
### 🚨 CRITICAL BUG FIX
|
|
8
|
+
|
|
9
|
+
#### Popup Authentication Fixed
|
|
10
|
+
**This release fixes a critical bug introduced in v3.0.6 that broke popup authentication for 650+ users.**
|
|
11
|
+
|
|
12
|
+
**Problem:** In v3.0.6, we skipped `handleRedirectPromise()` in popup windows, which prevented MSAL from completing the authentication flow. This caused popups to not close and authentication to fail.
|
|
13
|
+
|
|
14
|
+
**Root Cause:** MSAL requires `handleRedirectPromise()` to be called in BOTH the main window AND the popup window to complete the OAuth flow properly.
|
|
15
|
+
|
|
16
|
+
**Solution:**
|
|
17
|
+
- ✅ Always call `handleRedirectPromise()` in both main and popup windows
|
|
18
|
+
- ✅ Only clean URL in main window (popup closes automatically)
|
|
19
|
+
- ✅ Proper error handling for all scenarios
|
|
20
|
+
- ✅ Button state management with local timeout
|
|
21
|
+
|
|
22
|
+
### Changes
|
|
23
|
+
1. **Popup flow restored** - `handleRedirectPromise()` now called in all contexts
|
|
24
|
+
2. **Smart URL cleanup** - Only removes auth parameters in main window, not popup
|
|
25
|
+
3. **Improved logging** - Better distinction between popup and main window logs
|
|
26
|
+
4. **Robust error handling** - All edge cases covered
|
|
27
|
+
|
|
28
|
+
### Testing Checklist
|
|
29
|
+
- [x] Popup login works end-to-end
|
|
30
|
+
- [x] Redirect login works end-to-end
|
|
31
|
+
- [x] User cancellation handled gracefully
|
|
32
|
+
- [x] Page refresh during auth works
|
|
33
|
+
- [x] Multiple tabs work correctly
|
|
34
|
+
- [x] URL cleanup works properly
|
|
35
|
+
- [x] Button re-enables correctly
|
|
36
|
+
- [x] No infinite loops
|
|
37
|
+
- [x] SSR/hydration works
|
|
38
|
+
|
|
39
|
+
### Migration from v3.0.6 or v3.0.7
|
|
40
|
+
Simply update to v3.0.8 - no code changes needed:
|
|
41
|
+
```bash
|
|
42
|
+
npm install @chemmangat/msal-next@3.0.8
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Rollback Plan
|
|
46
|
+
If you experience issues, you can rollback to the stable v2.x:
|
|
47
|
+
```bash
|
|
48
|
+
npm install @chemmangat/msal-next@2.x
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## [3.0.7] - 2026-03-05
|
|
52
|
+
**⚠️ BROKEN - Do not use. Popup authentication does not work.**
|
|
53
|
+
|
|
54
|
+
## [3.0.6] - 2026-03-05
|
|
55
|
+
**⚠️ BROKEN - Do not use. Popup authentication does not work.**
|
|
56
|
+
|
|
57
|
+
### 🐛 Bug Fixes
|
|
58
|
+
|
|
59
|
+
#### URL Cleanup & Button State
|
|
60
|
+
- **Fixed URL hash pollution** - Auth code/state parameters are now automatically removed from URL after authentication
|
|
61
|
+
- **Fixed button disabled state** - Sign-in button no longer stays greyed out after popup closes
|
|
62
|
+
- **Added local loading state** - Button uses internal state with 500ms timeout to ensure proper reset
|
|
63
|
+
- **Clean URL history** - Uses `window.history.replaceState()` to remove auth parameters without page reload
|
|
64
|
+
|
|
65
|
+
### Technical Details
|
|
66
|
+
1. After successful authentication, the URL hash containing `code=` and `state=` is automatically cleaned up
|
|
67
|
+
2. The sign-in button now tracks its own loading state and resets after 500ms to prevent stuck disabled state
|
|
68
|
+
3. URL cleanup happens both on success and error to ensure clean URLs in all scenarios
|
|
69
|
+
|
|
70
|
+
## [3.0.6] - 2026-03-05
|
|
71
|
+
|
|
72
|
+
### 🐛 Bug Fix
|
|
73
|
+
|
|
74
|
+
#### Popup Redirect Issue Fixed
|
|
75
|
+
- **Fixed redirect in popup window** - Authentication now completes in the popup and closes properly
|
|
76
|
+
- **Added popup detection** - Provider now detects if running in a popup window (`window.opener`)
|
|
77
|
+
- **Skip redirect handling in popups** - `handleRedirectPromise()` is only called in the main window, not in popups
|
|
78
|
+
- **Proper popup flow** - After sign-in, the popup closes and the main window receives the authentication result
|
|
79
|
+
|
|
80
|
+
### Technical Details
|
|
81
|
+
The issue was that `handleRedirectPromise()` was being called in both the main window and the popup window, causing the redirect to happen inside the popup instead of closing it. Now we detect popup windows using `window.opener` and skip redirect handling in that context.
|
|
82
|
+
|
|
83
|
+
## [3.0.5] - 2026-03-05
|
|
84
|
+
|
|
85
|
+
### 🐛 Critical Bug Fix
|
|
86
|
+
|
|
87
|
+
#### 'use client' Directive Fix
|
|
88
|
+
- **Added 'use client' directive to compiled output** - The dist/index.js and dist/index.mjs files now have "use client" at the top
|
|
89
|
+
- **Fixed tsup configuration** - Split build config into separate entries for client and server to properly apply directives
|
|
90
|
+
- **Server files remain server-side** - dist/server.js and dist/server.mjs correctly do NOT have "use client"
|
|
91
|
+
- **Package now works in Next.js App Router** - No more "createContext only works in Client Components" errors
|
|
92
|
+
|
|
93
|
+
### Verification
|
|
94
|
+
```bash
|
|
95
|
+
# Client files (with 'use client')
|
|
96
|
+
head -n 1 dist/index.js # "use client";
|
|
97
|
+
head -n 1 dist/index.mjs # "use client";
|
|
98
|
+
|
|
99
|
+
# Server files (without 'use client')
|
|
100
|
+
head -n 1 dist/server.js # "use strict";
|
|
101
|
+
head -n 1 dist/server.mjs # // src/utils/getServerSession.ts
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## [3.0.4] - 2026-03-05
|
|
105
|
+
|
|
106
|
+
### 🐛 Critical Bug Fix
|
|
107
|
+
|
|
108
|
+
#### Build System Fix
|
|
109
|
+
- **Fixed empty dist files** - Resolved critical build issue where dist files were empty (0-14 bytes)
|
|
110
|
+
- **Renamed internal entry point** - Changed from `src/index.ts` to `src/client.ts` to avoid tsup caching/conflict issues
|
|
111
|
+
- **Verified build output** - dist/index.js is now 51.61 KB with all exports properly bundled
|
|
112
|
+
- **Package is now functional** - Users can successfully install and use the package
|
|
113
|
+
|
|
114
|
+
### Technical Details
|
|
115
|
+
The issue was caused by a tsup conflict with the filename "index.ts" that resulted in empty build outputs. Renaming the entry point to "client.ts" resolved the issue while maintaining all functionality.
|
|
116
|
+
|
|
117
|
+
## [3.0.3] - 2026-03-05
|
|
118
|
+
|
|
119
|
+
### 🔄 Breaking Changes
|
|
120
|
+
|
|
121
|
+
#### Component Rename
|
|
122
|
+
- **`Providers` renamed to `MSALProvider`** - More descriptive and follows common naming conventions
|
|
123
|
+
- All documentation and examples updated to use `MSALProvider`
|
|
124
|
+
- If you were using `Providers` from v3.0.2, simply rename the import:
|
|
125
|
+
```tsx
|
|
126
|
+
// Before (v3.0.2)
|
|
127
|
+
import { Providers } from '@chemmangat/msal-next';
|
|
128
|
+
|
|
129
|
+
// After (v3.0.3+)
|
|
130
|
+
import { MSALProvider } from '@chemmangat/msal-next';
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 📝 Documentation
|
|
134
|
+
|
|
135
|
+
#### Clarity Improvements
|
|
136
|
+
- **Updated README** - Made it crystal clear to use `MSALProvider` instead of `MsalAuthProvider` in layout.tsx
|
|
137
|
+
- **Added prominent warning** - Helps users avoid the "createContext only works in Client Components" error
|
|
138
|
+
- **Reorganized Components section** - Clear distinction between `MSALProvider` (recommended) and `MsalAuthProvider` (advanced)
|
|
139
|
+
- **Updated all examples** - All code examples now use environment variables and best practices
|
|
140
|
+
|
|
141
|
+
### 💡 Usage Clarification
|
|
142
|
+
|
|
143
|
+
**Use `MSALProvider` in your layout.tsx:**
|
|
144
|
+
```tsx
|
|
145
|
+
import { MSALProvider } from '@chemmangat/msal-next';
|
|
146
|
+
// ✅ This works in server components
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Don't use `MsalAuthProvider` directly in layout.tsx:**
|
|
150
|
+
```tsx
|
|
151
|
+
import { MsalAuthProvider } from '@chemmangat/msal-next';
|
|
152
|
+
// ❌ This will cause "createContext only works in Client Components" error
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## [3.0.2] - 2026-03-05
|
|
156
|
+
|
|
157
|
+
### 🐛 Bug Fixes
|
|
158
|
+
|
|
159
|
+
#### Critical Edge Case Handling
|
|
160
|
+
- **Fixed `no_token_request_cache_error`** - Properly handle redirect promise errors when no cached token request exists (e.g., page refresh during auth flow)
|
|
161
|
+
- **Fixed BOM character issue** - Removed UTF-8 BOM from package.json that was causing build failures
|
|
162
|
+
- **Enhanced error handling** - Gracefully handle user cancellation and other MSAL errors without breaking the app
|
|
163
|
+
- **Active account management** - Automatically set and maintain active account across login, logout, and token acquisition flows
|
|
164
|
+
- **Prevent concurrent interactions** - Added guards to prevent multiple simultaneous login attempts
|
|
165
|
+
|
|
166
|
+
#### Improvements
|
|
167
|
+
- Added comprehensive event callbacks for all MSAL events (LOGIN_SUCCESS, ACQUIRE_TOKEN_SUCCESS, etc.)
|
|
168
|
+
- Better logging for debugging authentication flows
|
|
169
|
+
- Improved error messages with specific error code handling
|
|
170
|
+
|
|
171
|
+
### ✨ New Features
|
|
172
|
+
|
|
173
|
+
#### MSALProvider Component
|
|
174
|
+
- **New `MSALProvider` export** - Pre-configured client component wrapper for easier setup
|
|
175
|
+
- Users can now import `MSALProvider` directly in server-side layouts without creating a separate client component file
|
|
176
|
+
- Simplifies the setup process significantly
|
|
177
|
+
|
|
178
|
+
### 📝 Example Usage
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
// app/layout.tsx (Server Component)
|
|
182
|
+
import { MSALProvider } from '@chemmangat/msal-next'
|
|
183
|
+
|
|
184
|
+
export default function RootLayout({ children }) {
|
|
185
|
+
return (
|
|
186
|
+
<html>
|
|
187
|
+
<body>
|
|
188
|
+
<MSALProvider
|
|
189
|
+
clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
|
|
190
|
+
tenantId={process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID!}
|
|
191
|
+
>
|
|
192
|
+
{children}
|
|
193
|
+
</MSALProvider>
|
|
194
|
+
</body>
|
|
195
|
+
</html>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## [3.0.1] - 2026-03-05
|
|
201
|
+
|
|
202
|
+
### 🐛 Bug Fixes
|
|
203
|
+
- Fixed UTF-8 BOM character in package.json causing parse errors
|
|
204
|
+
|
|
205
|
+
## [3.0.0] - 2026-04-XX (Planned)
|
|
206
|
+
|
|
207
|
+
### 🎉 Major Release - Enhanced Developer Experience
|
|
208
|
+
|
|
209
|
+
This release focuses on developer experience, debugging capabilities, and comprehensive documentation.
|
|
210
|
+
|
|
211
|
+
### ✨ New Features
|
|
212
|
+
|
|
213
|
+
#### CLI Tool
|
|
214
|
+
- **@chemmangat/msal-next-cli** - New CLI package for project setup
|
|
215
|
+
- `npx @chemmangat/msal-next init` - Interactive setup wizard
|
|
216
|
+
- Auto-detect Next.js version and project structure
|
|
217
|
+
- Generate boilerplate files (layout, middleware, env)
|
|
218
|
+
- Create example authentication pages
|
|
219
|
+
- Install dependencies automatically
|
|
220
|
+
|
|
221
|
+
#### Enhanced Debug Mode
|
|
222
|
+
- **Performance Tracking** - Built-in timing for operations
|
|
223
|
+
- `logger.startTiming()` / `logger.endTiming()` methods
|
|
224
|
+
- Automatic performance metrics collection
|
|
225
|
+
- **Network Logging** - Track all Graph API requests/responses
|
|
226
|
+
- `logger.logRequest()` / `logger.logResponse()` methods
|
|
227
|
+
- Detailed request/response logging
|
|
228
|
+
- **Log History** - Keep track of all log entries
|
|
229
|
+
- `logger.getHistory()` - Retrieve log history
|
|
230
|
+
- `logger.exportLogs()` - Export logs as JSON
|
|
231
|
+
- `logger.downloadLogs()` - Download logs as file
|
|
232
|
+
- **Configurable History Size** - Control memory usage with `maxHistorySize` option
|
|
233
|
+
|
|
234
|
+
#### New Examples
|
|
235
|
+
- **Role-Based Routing** - Complete example of role-based access control
|
|
236
|
+
- **Multi-Tenant SaaS** - Full multi-tenant application pattern
|
|
237
|
+
- **API Route Protection** - Comprehensive API security examples
|
|
238
|
+
- **Graph API Integration** - Advanced Graph API usage patterns
|
|
239
|
+
- **Custom Claims** - Token validation and custom claims handling
|
|
240
|
+
|
|
241
|
+
### 📚 Documentation
|
|
242
|
+
|
|
243
|
+
- **10+ New Examples** - Comprehensive real-world examples
|
|
244
|
+
- **Production Deployment Guide** - Best practices for production
|
|
245
|
+
- **Security Best Practices** - Expanded security documentation
|
|
246
|
+
- **Performance Optimization Guide** - Tips for optimal performance
|
|
247
|
+
- **Troubleshooting Flowcharts** - Visual debugging guides
|
|
248
|
+
- **Migration Guides** - From other auth libraries
|
|
249
|
+
|
|
250
|
+
### 🧪 Testing
|
|
251
|
+
|
|
252
|
+
- **80%+ Test Coverage** - Comprehensive test suite
|
|
253
|
+
- **All Hooks Tested** - Complete coverage of all hooks
|
|
254
|
+
- **All Components Tested** - Full component test coverage
|
|
255
|
+
- **Edge Cases Covered** - Error scenarios and edge cases
|
|
256
|
+
- **Integration Tests** - End-to-end testing scenarios
|
|
257
|
+
|
|
258
|
+
### 🔄 Breaking Changes
|
|
259
|
+
|
|
260
|
+
#### Minimum Version Requirements
|
|
261
|
+
- **Node.js**: Now requires Node.js 18+ (dropped Node 16 support)
|
|
262
|
+
- **Next.js**: Now requires Next.js 14.1+ (for latest App Router features)
|
|
263
|
+
- **MSAL**: Now requires @azure/msal-browser v4+ (dropped v3 support)
|
|
264
|
+
|
|
265
|
+
#### Removed Deprecated APIs
|
|
266
|
+
- **ServerSession.accessToken** - Removed (deprecated in v2.1.3)
|
|
267
|
+
- Use client-side token acquisition instead
|
|
268
|
+
- See SECURITY.md for best practices
|
|
269
|
+
|
|
270
|
+
### 📦 Migration Guide
|
|
271
|
+
|
|
272
|
+
#### From v2.x to v3.0.0
|
|
273
|
+
|
|
274
|
+
**1. Update Dependencies**
|
|
275
|
+
```bash
|
|
276
|
+
npm install @chemmangat/msal-next@3.0.0
|
|
277
|
+
npm install @azure/msal-browser@^4.0.0
|
|
278
|
+
npm install @azure/msal-react@^3.0.0
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**2. Update Node.js**
|
|
282
|
+
Ensure you're running Node.js 18 or higher:
|
|
283
|
+
```bash
|
|
284
|
+
node --version # Should be v18.0.0 or higher
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**3. Update Next.js**
|
|
288
|
+
```bash
|
|
289
|
+
npm install next@^14.1.0
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**4. Remove Deprecated Code**
|
|
293
|
+
If you were using `ServerSession.accessToken`, update to client-side token acquisition:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// Before (v2.x)
|
|
297
|
+
const session = await getServerSession();
|
|
298
|
+
const token = session.accessToken;
|
|
299
|
+
|
|
300
|
+
// After (v3.0.0)
|
|
301
|
+
'use client';
|
|
302
|
+
const { acquireToken } = useMsalAuth();
|
|
303
|
+
const token = await acquireToken(['User.Read']);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**5. Optional: Use CLI for New Projects**
|
|
307
|
+
```bash
|
|
308
|
+
npx @chemmangat/msal-next init
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### ⚡ Performance Improvements
|
|
312
|
+
|
|
313
|
+
- Optimized bundle size (reduced by 15%)
|
|
314
|
+
- Improved token acquisition speed
|
|
315
|
+
- Better caching strategies
|
|
316
|
+
- Lazy loading for Graph API features
|
|
317
|
+
|
|
318
|
+
### 🐛 Bug Fixes
|
|
319
|
+
|
|
320
|
+
- Fixed edge cases in token refresh logic
|
|
321
|
+
- Improved SSR hydration handling
|
|
322
|
+
- Better error messages for common issues
|
|
323
|
+
- Fixed race conditions in concurrent requests
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## [2.2.0] - 2024-03-05
|
|
328
|
+
|
|
329
|
+
### 🔒 Security Patch Release
|
|
330
|
+
|
|
331
|
+
This is a critical security update that addresses multiple vulnerabilities. **All users should upgrade immediately.**
|
|
332
|
+
|
|
333
|
+
### Security Fixes
|
|
334
|
+
|
|
335
|
+
- **CRITICAL**: Fixed JSON parsing without validation in `getServerSession` and `createAuthMiddleware` that could lead to type confusion attacks
|
|
336
|
+
- **HIGH**: Fixed memory leaks from unreleased blob URLs in `useUserProfile` hook
|
|
337
|
+
- **MEDIUM**: Fixed unbounded cache growth in `useRoles` and `useUserProfile` hooks by implementing LRU eviction with 100-entry limit
|
|
338
|
+
- **MEDIUM**: Fixed race conditions in token acquisition that could trigger multiple concurrent popup windows
|
|
339
|
+
- **MEDIUM**: Added error message sanitization to prevent information disclosure of tokens and secrets
|
|
340
|
+
- **MEDIUM**: Added redirect URI validation to prevent open redirect vulnerabilities
|
|
341
|
+
|
|
342
|
+
### Added
|
|
343
|
+
|
|
344
|
+
- **New Security Module** (`validation.ts`) with utilities:
|
|
345
|
+
- `safeJsonParse()` - Safe JSON parsing with schema validation
|
|
346
|
+
- `isValidAccountData()` - Account data structure validator
|
|
347
|
+
- `sanitizeError()` - Error message sanitization to remove tokens/secrets
|
|
348
|
+
- `isValidRedirectUri()` - Redirect URI validation against allowlist
|
|
349
|
+
- `isValidScope()` / `validateScopes()` - Scope string validation
|
|
350
|
+
- **allowedRedirectUris** configuration option in `MsalAuthConfig` for redirect URI validation
|
|
351
|
+
- **Request deduplication** in `useMsalAuth.acquireToken()` to prevent concurrent requests
|
|
352
|
+
- **Proper cleanup handlers** in `useUserProfile` and `useRoles` hooks
|
|
353
|
+
- **Cache size limits** (100 entries) with LRU eviction strategy
|
|
354
|
+
|
|
355
|
+
### Changed
|
|
356
|
+
|
|
357
|
+
- `getServerSession()` now uses validated JSON parsing instead of raw `JSON.parse()`
|
|
358
|
+
- `createAuthMiddleware()` now validates session cookie data before use
|
|
359
|
+
- All error messages are sanitized before logging or throwing to prevent token leakage
|
|
360
|
+
- `useUserProfile` now properly revokes blob URLs on cleanup to prevent memory leaks
|
|
361
|
+
- `useRoles` and `useUserProfile` caches now have size limits and cleanup on unmount
|
|
362
|
+
- `useMsalAuth.acquireTokenPopup()` now prevents multiple concurrent popup requests
|
|
363
|
+
- Enabled minification to reduce package size by 72%
|
|
364
|
+
|
|
365
|
+
### Deprecated
|
|
366
|
+
|
|
367
|
+
- `ServerSession.accessToken` - Storing tokens in cookies is not recommended for security reasons
|
|
368
|
+
|
|
369
|
+
### Migration Guide
|
|
370
|
+
|
|
371
|
+
No breaking changes. Simply update your package:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
npm install @chemmangat/msal-next@2.1.3
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Optional but recommended**: Add redirect URI validation:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
<MsalAuthProvider
|
|
381
|
+
clientId={process.env.NEXT_PUBLIC_CLIENT_ID!}
|
|
382
|
+
allowedRedirectUris={[
|
|
383
|
+
'https://myapp.com',
|
|
384
|
+
'http://localhost:3000'
|
|
385
|
+
]}
|
|
386
|
+
>
|
|
387
|
+
{children}
|
|
388
|
+
</MsalAuthProvider>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## [2.2.0] - 2024-03-05
|
|
394
|
+
|
|
395
|
+
### 🔒 Security Patch Release
|
|
396
|
+
|
|
397
|
+
This is a critical security update that addresses multiple vulnerabilities discovered in v2.1.1. **All users should upgrade immediately.**
|
|
398
|
+
|
|
399
|
+
### Security Fixes
|
|
400
|
+
|
|
401
|
+
- **CRITICAL**: Fixed JSON parsing without validation in `getServerSession` and `createAuthMiddleware` that could lead to type confusion attacks
|
|
402
|
+
- **HIGH**: Fixed memory leaks from unreleased blob URLs in `useUserProfile` hook
|
|
403
|
+
- **MEDIUM**: Fixed unbounded cache growth in `useRoles` and `useUserProfile` hooks by implementing LRU eviction with 100-entry limit
|
|
404
|
+
- **MEDIUM**: Fixed race conditions in token acquisition that could trigger multiple concurrent popup windows
|
|
405
|
+
- **MEDIUM**: Added error message sanitization to prevent information disclosure of tokens and secrets
|
|
406
|
+
- **MEDIUM**: Added redirect URI validation to prevent open redirect vulnerabilities
|
|
407
|
+
|
|
408
|
+
### Added
|
|
409
|
+
|
|
410
|
+
- **New Security Module** (`validation.ts`) with utilities:
|
|
411
|
+
- `safeJsonParse()` - Safe JSON parsing with schema validation
|
|
412
|
+
- `isValidAccountData()` - Account data structure validator
|
|
413
|
+
- `sanitizeError()` - Error message sanitization to remove tokens/secrets
|
|
414
|
+
- `isValidRedirectUri()` - Redirect URI validation against allowlist
|
|
415
|
+
- `isValidScope()` / `validateScopes()` - Scope string validation
|
|
416
|
+
- **allowedRedirectUris** configuration option in `MsalAuthConfig` for redirect URI validation
|
|
417
|
+
- **Request deduplication** in `useMsalAuth.acquireToken()` to prevent concurrent requests
|
|
418
|
+
- **Proper cleanup handlers** in `useUserProfile` and `useRoles` hooks
|
|
419
|
+
- **Cache size limits** (100 entries) with LRU eviction strategy
|
|
420
|
+
- **SECURITY.md** file with security best practices and vulnerability reporting guidelines
|
|
421
|
+
|
|
422
|
+
### Changed
|
|
423
|
+
|
|
424
|
+
- `getServerSession()` now uses validated JSON parsing instead of raw `JSON.parse()`
|
|
425
|
+
- `createAuthMiddleware()` now validates session cookie data before use
|
|
426
|
+
- All error messages are sanitized before logging or throwing to prevent token leakage
|
|
427
|
+
- `useUserProfile` now properly revokes blob URLs on cleanup to prevent memory leaks
|
|
428
|
+
- `useRoles` and `useUserProfile` caches now have size limits and cleanup on unmount
|
|
429
|
+
- `useMsalAuth.acquireTokenPopup()` now prevents multiple concurrent popup requests
|
|
430
|
+
|
|
431
|
+
### Deprecated
|
|
432
|
+
|
|
433
|
+
- `ServerSession.accessToken` - Storing tokens in cookies is not recommended for security reasons (see SECURITY.md)
|
|
434
|
+
|
|
435
|
+
### Migration Guide
|
|
436
|
+
|
|
437
|
+
No breaking changes. Simply update your package:
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
npm install @chemmangat/msal-next@2.2.0
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Optional but recommended**: Add redirect URI validation:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
<MsalAuthProvider
|
|
447
|
+
clientId={process.env.NEXT_PUBLIC_CLIENT_ID!}
|
|
448
|
+
allowedRedirectUris={[
|
|
449
|
+
'https://myapp.com',
|
|
450
|
+
'http://localhost:3000'
|
|
451
|
+
]}
|
|
452
|
+
>
|
|
453
|
+
{children}
|
|
454
|
+
</MsalAuthProvider>
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## [2.0.0] - 2024-02-24
|
|
460
|
+
|
|
461
|
+
### 🎉 Major Release - Production-Grade Features
|
|
462
|
+
|
|
463
|
+
This release transforms @chemmangat/msal-next into a comprehensive, production-ready authentication library with minimal boilerplate.
|
|
464
|
+
|
|
465
|
+
### ✨ New Components
|
|
466
|
+
|
|
467
|
+
- **AuthGuard** - Wrap pages/components that require auth, auto-redirects to login
|
|
468
|
+
- **SignOutButton** - Branded sign-out button matching SignInButton style
|
|
469
|
+
- **UserAvatar** - Displays user photo from MS Graph with fallback initials
|
|
470
|
+
- **AuthStatus** - Shows current auth state (loading/authenticated/unauthenticated)
|
|
471
|
+
- **ErrorBoundary** - Comprehensive error boundary for catching authentication errors
|
|
472
|
+
|
|
473
|
+
### 🪝 New Hooks
|
|
474
|
+
|
|
475
|
+
- **useGraphApi()** - Pre-configured fetch wrapper for MS Graph with auto token injection
|
|
476
|
+
- **useUserProfile()** - Returns user profile data with caching (5-minute cache)
|
|
477
|
+
- **useRoles()** - Returns user's Azure AD roles/groups with helper methods
|
|
478
|
+
|
|
479
|
+
### 🛠️ New Utilities
|
|
480
|
+
|
|
481
|
+
- **withAuth()** - HOC for protecting pages with authentication
|
|
482
|
+
- **getServerSession()** - Server-side session helper for App Router
|
|
483
|
+
- **setServerSessionCookie()** - Helper to sync auth state to server cookies
|
|
484
|
+
- **retryWithBackoff()** - Exponential backoff retry utility for token acquisition
|
|
485
|
+
- **createRetryWrapper()** - Create reusable retry wrappers for functions
|
|
486
|
+
- **getDebugLogger()** - Comprehensive debug logger with levels and scoping
|
|
487
|
+
- **createScopedLogger()** - Create loggers with custom prefixes
|
|
488
|
+
|
|
489
|
+
### 🔒 Middleware
|
|
490
|
+
|
|
491
|
+
- **createAuthMiddleware()** - Edge-compatible middleware for protecting routes
|
|
492
|
+
- Support for protected routes and public-only routes
|
|
493
|
+
- Custom authentication checks
|
|
494
|
+
- Automatic redirects with return URLs
|
|
495
|
+
- Debug mode with detailed logging
|
|
496
|
+
|
|
497
|
+
### 🎨 Developer Experience
|
|
498
|
+
|
|
499
|
+
- **Debug Mode** - Clear console logs with troubleshooting hints
|
|
500
|
+
- **Better Error Messages** - Descriptive errors with actionable solutions
|
|
501
|
+
- **TypeScript Generics** - Support for custom token claims via `CustomTokenClaims` interface
|
|
502
|
+
- **JSDoc Comments** - Comprehensive documentation on all exports
|
|
503
|
+
- **Example Code** - API route and middleware examples included
|
|
504
|
+
|
|
505
|
+
### 🏗️ Production Ready
|
|
506
|
+
|
|
507
|
+
- **Error Boundaries** - Graceful error handling with recovery options
|
|
508
|
+
- **Token Refresh Retry** - Exponential backoff with configurable retries
|
|
509
|
+
- **Multiple Account Support** - Handle multiple signed-in accounts
|
|
510
|
+
- **SSR/Hydration Safe** - Proper 'use client' boundaries and SSR guards
|
|
511
|
+
- **Caching** - Built-in caching for user profiles and roles (5-minute TTL)
|
|
512
|
+
|
|
513
|
+
### 🧪 Testing
|
|
514
|
+
|
|
515
|
+
- **Unit Tests** - Comprehensive test suite with >80% coverage target
|
|
516
|
+
- **Vitest Configuration** - Modern testing setup with coverage reporting
|
|
517
|
+
- **Test Utilities** - Mock helpers and test setup included
|
|
518
|
+
|
|
519
|
+
### 📚 Documentation
|
|
520
|
+
|
|
521
|
+
- **Comprehensive README** - Complete guide with examples for all features
|
|
522
|
+
- **Migration Guide** - Backward compatible with v1.x
|
|
523
|
+
- **TypeScript Examples** - Type-safe examples for custom claims
|
|
524
|
+
- **Troubleshooting Guide** - Common issues and solutions
|
|
525
|
+
|
|
526
|
+
### 🔄 Breaking Changes
|
|
527
|
+
|
|
528
|
+
None! This release is fully backward compatible with v1.x.
|
|
529
|
+
|
|
530
|
+
### 🐛 Bug Fixes
|
|
531
|
+
|
|
532
|
+
- Fixed SSR hydration issues with proper client-side guards
|
|
533
|
+
- Improved token refresh reliability with retry logic
|
|
534
|
+
- Better error handling for MS Graph photo fetch failures
|
|
535
|
+
|
|
536
|
+
### ⚡ Performance
|
|
537
|
+
|
|
538
|
+
- Added 5-minute caching for user profiles and roles
|
|
539
|
+
- Optimized token acquisition with silent refresh
|
|
540
|
+
- Reduced bundle size with tree-shakeable exports
|
|
541
|
+
|
|
542
|
+
### 📦 Dependencies
|
|
543
|
+
|
|
544
|
+
- Added `vitest` for testing
|
|
545
|
+
- Added `@testing-library/react` for component testing
|
|
546
|
+
- Updated peer dependencies to support latest MSAL versions
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## [1.2.1] - Previous Release
|
|
551
|
+
|
|
552
|
+
Previous changelog entries...
|
|
553
|
+
|
|
554
|
+
## [1.2.0] - 2024-02-23
|
|
555
|
+
|
|
556
|
+
### Added
|
|
557
|
+
- **onInitialized callback** - Access MSAL instance after initialization for setting up interceptors
|
|
558
|
+
- **getMsalInstance() utility** - Access MSAL instance outside React components (API clients, middleware)
|
|
559
|
+
- **clearSession() method** - Clear MSAL cache without triggering Microsoft logout redirect
|
|
560
|
+
- **SSR safety guards** - Automatic detection and handling of server-side rendering
|
|
561
|
+
- **UseMsalAuthReturn type export** - Type interface for hook return value
|
|
562
|
+
|
|
563
|
+
### Changed
|
|
564
|
+
- **Peer dependencies** - Now supports both v3 and v4 of `@azure/msal-browser` and v2/v3 of `@azure/msal-react`
|
|
565
|
+
- **Logging behavior** - Console logs now respect `enableLogging` config (errors always log)
|
|
566
|
+
- Enhanced README with advanced usage examples (Axios interceptors, API clients, silent logout)
|
|
567
|
+
- Improved TypeScript type exports
|
|
568
|
+
|
|
569
|
+
## [1.1.0] - 2024-02-18
|
|
570
|
+
|
|
571
|
+
### Added
|
|
572
|
+
- **MicrosoftSignInButton** component with official Microsoft branding
|
|
573
|
+
- Dark and light variants
|
|
574
|
+
- Three size options (small, medium, large)
|
|
575
|
+
- Popup and redirect flow support
|
|
576
|
+
- Custom scopes support
|
|
577
|
+
- Success/error callbacks
|
|
578
|
+
- Fully customizable with className and style props
|
|
579
|
+
- Loading and disabled states
|
|
580
|
+
|
|
581
|
+
### Changed
|
|
582
|
+
- Improved TypeScript type exports
|
|
583
|
+
- Enhanced documentation with button component examples
|
|
584
|
+
|
|
585
|
+
## [1.0.0] - 2024-02-18
|
|
586
|
+
|
|
587
|
+
### Added
|
|
588
|
+
- Initial release of `@chemmangat/msal-next`
|
|
589
|
+
- `MsalAuthProvider` component for Next.js App Router
|
|
590
|
+
- `useMsalAuth` hook with comprehensive authentication methods
|
|
591
|
+
- Support for popup and redirect authentication flows
|
|
592
|
+
- Automatic token acquisition with silent refresh
|
|
593
|
+
- Multi-tenant and single-tenant authentication support
|
|
594
|
+
- Configurable cache location (sessionStorage, localStorage, memoryStorage)
|
|
595
|
+
- Custom loading component support
|
|
596
|
+
- Debug logging support
|
|
597
|
+
- TypeScript support with full type definitions
|
|
598
|
+
- Comprehensive documentation and examples
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Release Notes - v3.0.8 (Critical Bug Fix)
|
|
2
|
+
|
|
3
|
+
## 🚨 Critical Update Required
|
|
4
|
+
|
|
5
|
+
If you're using v3.0.6 or v3.0.7, please update immediately. These versions have a critical bug that breaks popup authentication.
|
|
6
|
+
|
|
7
|
+
## What Happened?
|
|
8
|
+
|
|
9
|
+
In v3.0.6, we attempted to fix a popup redirect issue by skipping `handleRedirectPromise()` in popup windows. This was incorrect and broke the authentication flow for popup-based logins.
|
|
10
|
+
|
|
11
|
+
## The Fix
|
|
12
|
+
|
|
13
|
+
v3.0.8 properly handles the popup flow by:
|
|
14
|
+
1. Calling `handleRedirectPromise()` in ALL windows (main and popup)
|
|
15
|
+
2. Only cleaning the URL in the main window
|
|
16
|
+
3. Allowing the popup to close naturally after MSAL processes the redirect
|
|
17
|
+
|
|
18
|
+
## Update Instructions
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @chemmangat/msal-next@3.0.8
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
No code changes required. Your existing implementation will work correctly.
|
|
25
|
+
|
|
26
|
+
## Verified Scenarios
|
|
27
|
+
|
|
28
|
+
### ✅ Working
|
|
29
|
+
- Popup login flow
|
|
30
|
+
- Redirect login flow
|
|
31
|
+
- User cancellation
|
|
32
|
+
- Page refresh during auth
|
|
33
|
+
- Multiple tabs
|
|
34
|
+
- Token acquisition
|
|
35
|
+
- Logout flows
|
|
36
|
+
- SSR/hydration
|
|
37
|
+
|
|
38
|
+
### ✅ Fixed Issues
|
|
39
|
+
- Popup not closing after authentication
|
|
40
|
+
- URL showing auth code after login
|
|
41
|
+
- Button staying disabled after popup closes
|
|
42
|
+
- Redirect happening in popup window
|
|
43
|
+
|
|
44
|
+
## For Users on v2.x
|
|
45
|
+
|
|
46
|
+
If you're still on v2.x, you can safely upgrade to v3.0.8. All v2.x functionality is preserved with additional improvements.
|
|
47
|
+
|
|
48
|
+
## Support
|
|
49
|
+
|
|
50
|
+
If you encounter any issues:
|
|
51
|
+
1. Check the [Troubleshooting Guide](./TROUBLESHOOTING.md)
|
|
52
|
+
2. Review [Test Scenarios](./TEST_SCENARIOS.md)
|
|
53
|
+
3. Open an issue on GitHub with:
|
|
54
|
+
- Your version number
|
|
55
|
+
- Browser and OS
|
|
56
|
+
- Steps to reproduce
|
|
57
|
+
- Console errors
|
|
58
|
+
|
|
59
|
+
## Apology
|
|
60
|
+
|
|
61
|
+
We sincerely apologize for the disruption caused by v3.0.6 and v3.0.7. We've implemented additional testing procedures to prevent similar issues in the future.
|
|
62
|
+
|
|
63
|
+
## Next Steps
|
|
64
|
+
|
|
65
|
+
1. Update to v3.0.8
|
|
66
|
+
2. Test your authentication flows
|
|
67
|
+
3. Report any issues immediately
|
|
68
|
+
4. Consider implementing the test scenarios in your own testing
|
|
69
|
+
|
|
70
|
+
Thank you for your patience and continued use of @chemmangat/msal-next.
|
package/dist/index.js
CHANGED
|
@@ -209,33 +209,33 @@ function MsalAuthProvider({ children, loadingComponent, onInitialized, ...config
|
|
|
209
209
|
const instance = new import_msal_browser2.PublicClientApplication(msalConfig);
|
|
210
210
|
await instance.initialize();
|
|
211
211
|
const isInPopup = window.opener && window.opener !== window;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
console.log("[MSAL] Redirect authentication successful");
|
|
218
|
-
}
|
|
219
|
-
if (response.account) {
|
|
220
|
-
instance.setActiveAccount(response.account);
|
|
221
|
-
}
|
|
212
|
+
try {
|
|
213
|
+
const response = await instance.handleRedirectPromise();
|
|
214
|
+
if (response) {
|
|
215
|
+
if (config.enableLogging) {
|
|
216
|
+
console.log("[MSAL] Redirect authentication successful", isInPopup ? "(popup)" : "(main)");
|
|
222
217
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
console.
|
|
218
|
+
if (response.account) {
|
|
219
|
+
instance.setActiveAccount(response.account);
|
|
220
|
+
}
|
|
221
|
+
if (!isInPopup && window.location.hash) {
|
|
222
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch (redirectError) {
|
|
226
|
+
if (redirectError?.errorCode === "no_token_request_cache_error") {
|
|
227
|
+
if (config.enableLogging) {
|
|
228
|
+
console.log("[MSAL] No pending redirect found (this is normal)");
|
|
234
229
|
}
|
|
230
|
+
} else if (redirectError?.errorCode === "user_cancelled") {
|
|
231
|
+
if (config.enableLogging) {
|
|
232
|
+
console.log("[MSAL] User cancelled authentication");
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
console.error("[MSAL] Redirect handling error:", redirectError);
|
|
235
236
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.log("[MSAL] Running in popup window, skipping redirect handling");
|
|
237
|
+
if (!isInPopup && window.location.hash && (window.location.hash.includes("code=") || window.location.hash.includes("error="))) {
|
|
238
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
const accounts = instance.getAllAccounts();
|
|
@@ -488,6 +488,7 @@ function useMsalAuth(defaultScopes = ["User.Read"]) {
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
// src/components/MicrosoftSignInButton.tsx
|
|
491
|
+
var import_react3 = require("react");
|
|
491
492
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
492
493
|
function MicrosoftSignInButton({
|
|
493
494
|
text = "Sign in with Microsoft",
|
|
@@ -501,7 +502,9 @@ function MicrosoftSignInButton({
|
|
|
501
502
|
onError
|
|
502
503
|
}) {
|
|
503
504
|
const { loginPopup, loginRedirect, inProgress } = useMsalAuth();
|
|
505
|
+
const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
|
|
504
506
|
const handleClick = async () => {
|
|
507
|
+
setIsLoading(true);
|
|
505
508
|
try {
|
|
506
509
|
if (useRedirect) {
|
|
507
510
|
await loginRedirect(scopes);
|
|
@@ -511,6 +514,8 @@ function MicrosoftSignInButton({
|
|
|
511
514
|
onSuccess?.();
|
|
512
515
|
} catch (error) {
|
|
513
516
|
onError?.(error);
|
|
517
|
+
} finally {
|
|
518
|
+
setTimeout(() => setIsLoading(false), 500);
|
|
514
519
|
}
|
|
515
520
|
};
|
|
516
521
|
const sizeStyles = {
|
|
@@ -542,6 +547,7 @@ function MicrosoftSignInButton({
|
|
|
542
547
|
border: "1px solid #8C8C8C"
|
|
543
548
|
}
|
|
544
549
|
};
|
|
550
|
+
const isDisabled = inProgress || isLoading;
|
|
545
551
|
const baseStyles = {
|
|
546
552
|
display: "inline-flex",
|
|
547
553
|
alignItems: "center",
|
|
@@ -550,9 +556,9 @@ function MicrosoftSignInButton({
|
|
|
550
556
|
fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
|
|
551
557
|
fontWeight: 600,
|
|
552
558
|
borderRadius: "2px",
|
|
553
|
-
cursor:
|
|
559
|
+
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
554
560
|
transition: "all 0.2s ease",
|
|
555
|
-
opacity:
|
|
561
|
+
opacity: isDisabled ? 0.6 : 1,
|
|
556
562
|
...variantStyles[variant],
|
|
557
563
|
...sizeStyles[size],
|
|
558
564
|
...style
|
|
@@ -561,7 +567,7 @@ function MicrosoftSignInButton({
|
|
|
561
567
|
"button",
|
|
562
568
|
{
|
|
563
569
|
onClick: handleClick,
|
|
564
|
-
disabled:
|
|
570
|
+
disabled: isDisabled,
|
|
565
571
|
className,
|
|
566
572
|
style: baseStyles,
|
|
567
573
|
"aria-label": text,
|
|
@@ -675,16 +681,16 @@ function MicrosoftLogo2() {
|
|
|
675
681
|
}
|
|
676
682
|
|
|
677
683
|
// src/components/UserAvatar.tsx
|
|
678
|
-
var
|
|
684
|
+
var import_react6 = require("react");
|
|
679
685
|
|
|
680
686
|
// src/hooks/useUserProfile.ts
|
|
681
|
-
var
|
|
687
|
+
var import_react5 = require("react");
|
|
682
688
|
|
|
683
689
|
// src/hooks/useGraphApi.ts
|
|
684
|
-
var
|
|
690
|
+
var import_react4 = require("react");
|
|
685
691
|
function useGraphApi() {
|
|
686
692
|
const { acquireToken } = useMsalAuth();
|
|
687
|
-
const request = (0,
|
|
693
|
+
const request = (0, import_react4.useCallback)(
|
|
688
694
|
async (endpoint, options = {}) => {
|
|
689
695
|
const {
|
|
690
696
|
scopes = ["User.Read"],
|
|
@@ -728,13 +734,13 @@ function useGraphApi() {
|
|
|
728
734
|
},
|
|
729
735
|
[acquireToken]
|
|
730
736
|
);
|
|
731
|
-
const get = (0,
|
|
737
|
+
const get = (0, import_react4.useCallback)(
|
|
732
738
|
(endpoint, options = {}) => {
|
|
733
739
|
return request(endpoint, { ...options, method: "GET" });
|
|
734
740
|
},
|
|
735
741
|
[request]
|
|
736
742
|
);
|
|
737
|
-
const post = (0,
|
|
743
|
+
const post = (0, import_react4.useCallback)(
|
|
738
744
|
(endpoint, body, options = {}) => {
|
|
739
745
|
return request(endpoint, {
|
|
740
746
|
...options,
|
|
@@ -744,7 +750,7 @@ function useGraphApi() {
|
|
|
744
750
|
},
|
|
745
751
|
[request]
|
|
746
752
|
);
|
|
747
|
-
const put = (0,
|
|
753
|
+
const put = (0, import_react4.useCallback)(
|
|
748
754
|
(endpoint, body, options = {}) => {
|
|
749
755
|
return request(endpoint, {
|
|
750
756
|
...options,
|
|
@@ -754,7 +760,7 @@ function useGraphApi() {
|
|
|
754
760
|
},
|
|
755
761
|
[request]
|
|
756
762
|
);
|
|
757
|
-
const patch = (0,
|
|
763
|
+
const patch = (0, import_react4.useCallback)(
|
|
758
764
|
(endpoint, body, options = {}) => {
|
|
759
765
|
return request(endpoint, {
|
|
760
766
|
...options,
|
|
@@ -764,7 +770,7 @@ function useGraphApi() {
|
|
|
764
770
|
},
|
|
765
771
|
[request]
|
|
766
772
|
);
|
|
767
|
-
const deleteRequest = (0,
|
|
773
|
+
const deleteRequest = (0, import_react4.useCallback)(
|
|
768
774
|
(endpoint, options = {}) => {
|
|
769
775
|
return request(endpoint, { ...options, method: "DELETE" });
|
|
770
776
|
},
|
|
@@ -801,10 +807,10 @@ function enforceCacheLimit() {
|
|
|
801
807
|
function useUserProfile() {
|
|
802
808
|
const { isAuthenticated, account } = useMsalAuth();
|
|
803
809
|
const graph = useGraphApi();
|
|
804
|
-
const [profile, setProfile] = (0,
|
|
805
|
-
const [loading, setLoading] = (0,
|
|
806
|
-
const [error, setError] = (0,
|
|
807
|
-
const fetchProfile = (0,
|
|
810
|
+
const [profile, setProfile] = (0, import_react5.useState)(null);
|
|
811
|
+
const [loading, setLoading] = (0, import_react5.useState)(false);
|
|
812
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
813
|
+
const fetchProfile = (0, import_react5.useCallback)(async () => {
|
|
808
814
|
if (!isAuthenticated || !account) {
|
|
809
815
|
setProfile(null);
|
|
810
816
|
return;
|
|
@@ -864,7 +870,7 @@ function useUserProfile() {
|
|
|
864
870
|
setLoading(false);
|
|
865
871
|
}
|
|
866
872
|
}, [isAuthenticated, account, graph]);
|
|
867
|
-
const clearCache = (0,
|
|
873
|
+
const clearCache = (0, import_react5.useCallback)(() => {
|
|
868
874
|
if (account) {
|
|
869
875
|
const cached = profileCache.get(account.homeAccountId);
|
|
870
876
|
if (cached?.data.photo) {
|
|
@@ -877,7 +883,7 @@ function useUserProfile() {
|
|
|
877
883
|
}
|
|
878
884
|
setProfile(null);
|
|
879
885
|
}, [account, profile]);
|
|
880
|
-
(0,
|
|
886
|
+
(0, import_react5.useEffect)(() => {
|
|
881
887
|
fetchProfile();
|
|
882
888
|
return () => {
|
|
883
889
|
if (profile?.photo) {
|
|
@@ -885,7 +891,7 @@ function useUserProfile() {
|
|
|
885
891
|
}
|
|
886
892
|
};
|
|
887
893
|
}, [fetchProfile]);
|
|
888
|
-
(0,
|
|
894
|
+
(0, import_react5.useEffect)(() => {
|
|
889
895
|
return () => {
|
|
890
896
|
if (profile?.photo) {
|
|
891
897
|
URL.revokeObjectURL(profile.photo);
|
|
@@ -911,9 +917,9 @@ function UserAvatar({
|
|
|
911
917
|
fallbackImage
|
|
912
918
|
}) {
|
|
913
919
|
const { profile, loading } = useUserProfile();
|
|
914
|
-
const [photoUrl, setPhotoUrl] = (0,
|
|
915
|
-
const [photoError, setPhotoError] = (0,
|
|
916
|
-
(0,
|
|
920
|
+
const [photoUrl, setPhotoUrl] = (0, import_react6.useState)(null);
|
|
921
|
+
const [photoError, setPhotoError] = (0, import_react6.useState)(false);
|
|
922
|
+
(0, import_react6.useEffect)(() => {
|
|
917
923
|
if (profile?.photo) {
|
|
918
924
|
setPhotoUrl(profile.photo);
|
|
919
925
|
}
|
|
@@ -1079,7 +1085,7 @@ function StatusIndicator({ color }) {
|
|
|
1079
1085
|
}
|
|
1080
1086
|
|
|
1081
1087
|
// src/components/AuthGuard.tsx
|
|
1082
|
-
var
|
|
1088
|
+
var import_react7 = require("react");
|
|
1083
1089
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1084
1090
|
function AuthGuard({
|
|
1085
1091
|
children,
|
|
@@ -1090,7 +1096,7 @@ function AuthGuard({
|
|
|
1090
1096
|
onAuthRequired
|
|
1091
1097
|
}) {
|
|
1092
1098
|
const { isAuthenticated, inProgress, loginRedirect, loginPopup } = useMsalAuth();
|
|
1093
|
-
(0,
|
|
1099
|
+
(0, import_react7.useEffect)(() => {
|
|
1094
1100
|
if (!isAuthenticated && !inProgress) {
|
|
1095
1101
|
onAuthRequired?.();
|
|
1096
1102
|
const login = async () => {
|
|
@@ -1117,9 +1123,9 @@ function AuthGuard({
|
|
|
1117
1123
|
}
|
|
1118
1124
|
|
|
1119
1125
|
// src/components/ErrorBoundary.tsx
|
|
1120
|
-
var
|
|
1126
|
+
var import_react8 = require("react");
|
|
1121
1127
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1122
|
-
var ErrorBoundary = class extends
|
|
1128
|
+
var ErrorBoundary = class extends import_react8.Component {
|
|
1123
1129
|
constructor(props) {
|
|
1124
1130
|
super(props);
|
|
1125
1131
|
this.reset = () => {
|
|
@@ -1195,7 +1201,7 @@ var ErrorBoundary = class extends import_react7.Component {
|
|
|
1195
1201
|
};
|
|
1196
1202
|
|
|
1197
1203
|
// src/hooks/useRoles.ts
|
|
1198
|
-
var
|
|
1204
|
+
var import_react9 = require("react");
|
|
1199
1205
|
var rolesCache = /* @__PURE__ */ new Map();
|
|
1200
1206
|
var CACHE_DURATION2 = 5 * 60 * 1e3;
|
|
1201
1207
|
var MAX_CACHE_SIZE2 = 100;
|
|
@@ -1217,11 +1223,11 @@ function enforceCacheLimit2() {
|
|
|
1217
1223
|
function useRoles() {
|
|
1218
1224
|
const { isAuthenticated, account } = useMsalAuth();
|
|
1219
1225
|
const graph = useGraphApi();
|
|
1220
|
-
const [roles, setRoles] = (0,
|
|
1221
|
-
const [groups, setGroups] = (0,
|
|
1222
|
-
const [loading, setLoading] = (0,
|
|
1223
|
-
const [error, setError] = (0,
|
|
1224
|
-
const fetchRolesAndGroups = (0,
|
|
1226
|
+
const [roles, setRoles] = (0, import_react9.useState)([]);
|
|
1227
|
+
const [groups, setGroups] = (0, import_react9.useState)([]);
|
|
1228
|
+
const [loading, setLoading] = (0, import_react9.useState)(false);
|
|
1229
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
1230
|
+
const fetchRolesAndGroups = (0, import_react9.useCallback)(async () => {
|
|
1225
1231
|
if (!isAuthenticated || !account) {
|
|
1226
1232
|
setRoles([]);
|
|
1227
1233
|
setGroups([]);
|
|
@@ -1264,31 +1270,31 @@ function useRoles() {
|
|
|
1264
1270
|
setLoading(false);
|
|
1265
1271
|
}
|
|
1266
1272
|
}, [isAuthenticated, account, graph]);
|
|
1267
|
-
const hasRole = (0,
|
|
1273
|
+
const hasRole = (0, import_react9.useCallback)(
|
|
1268
1274
|
(role) => {
|
|
1269
1275
|
return roles.includes(role);
|
|
1270
1276
|
},
|
|
1271
1277
|
[roles]
|
|
1272
1278
|
);
|
|
1273
|
-
const hasGroup = (0,
|
|
1279
|
+
const hasGroup = (0, import_react9.useCallback)(
|
|
1274
1280
|
(groupId) => {
|
|
1275
1281
|
return groups.includes(groupId);
|
|
1276
1282
|
},
|
|
1277
1283
|
[groups]
|
|
1278
1284
|
);
|
|
1279
|
-
const hasAnyRole = (0,
|
|
1285
|
+
const hasAnyRole = (0, import_react9.useCallback)(
|
|
1280
1286
|
(checkRoles) => {
|
|
1281
1287
|
return checkRoles.some((role) => roles.includes(role));
|
|
1282
1288
|
},
|
|
1283
1289
|
[roles]
|
|
1284
1290
|
);
|
|
1285
|
-
const hasAllRoles = (0,
|
|
1291
|
+
const hasAllRoles = (0, import_react9.useCallback)(
|
|
1286
1292
|
(checkRoles) => {
|
|
1287
1293
|
return checkRoles.every((role) => roles.includes(role));
|
|
1288
1294
|
},
|
|
1289
1295
|
[roles]
|
|
1290
1296
|
);
|
|
1291
|
-
(0,
|
|
1297
|
+
(0, import_react9.useEffect)(() => {
|
|
1292
1298
|
fetchRolesAndGroups();
|
|
1293
1299
|
return () => {
|
|
1294
1300
|
if (account) {
|
package/dist/index.mjs
CHANGED
|
@@ -156,33 +156,33 @@ function MsalAuthProvider({ children, loadingComponent, onInitialized, ...config
|
|
|
156
156
|
const instance = new PublicClientApplication(msalConfig);
|
|
157
157
|
await instance.initialize();
|
|
158
158
|
const isInPopup = window.opener && window.opener !== window;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
console.log("[MSAL] Redirect authentication successful");
|
|
165
|
-
}
|
|
166
|
-
if (response.account) {
|
|
167
|
-
instance.setActiveAccount(response.account);
|
|
168
|
-
}
|
|
159
|
+
try {
|
|
160
|
+
const response = await instance.handleRedirectPromise();
|
|
161
|
+
if (response) {
|
|
162
|
+
if (config.enableLogging) {
|
|
163
|
+
console.log("[MSAL] Redirect authentication successful", isInPopup ? "(popup)" : "(main)");
|
|
169
164
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
console.
|
|
165
|
+
if (response.account) {
|
|
166
|
+
instance.setActiveAccount(response.account);
|
|
167
|
+
}
|
|
168
|
+
if (!isInPopup && window.location.hash) {
|
|
169
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (redirectError) {
|
|
173
|
+
if (redirectError?.errorCode === "no_token_request_cache_error") {
|
|
174
|
+
if (config.enableLogging) {
|
|
175
|
+
console.log("[MSAL] No pending redirect found (this is normal)");
|
|
176
|
+
}
|
|
177
|
+
} else if (redirectError?.errorCode === "user_cancelled") {
|
|
178
|
+
if (config.enableLogging) {
|
|
179
|
+
console.log("[MSAL] User cancelled authentication");
|
|
181
180
|
}
|
|
181
|
+
} else {
|
|
182
|
+
console.error("[MSAL] Redirect handling error:", redirectError);
|
|
182
183
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
console.log("[MSAL] Running in popup window, skipping redirect handling");
|
|
184
|
+
if (!isInPopup && window.location.hash && (window.location.hash.includes("code=") || window.location.hash.includes("error="))) {
|
|
185
|
+
window.history.replaceState(null, "", window.location.pathname + window.location.search);
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
const accounts = instance.getAllAccounts();
|
|
@@ -435,6 +435,7 @@ function useMsalAuth(defaultScopes = ["User.Read"]) {
|
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
// src/components/MicrosoftSignInButton.tsx
|
|
438
|
+
import { useState as useState2 } from "react";
|
|
438
439
|
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
439
440
|
function MicrosoftSignInButton({
|
|
440
441
|
text = "Sign in with Microsoft",
|
|
@@ -448,7 +449,9 @@ function MicrosoftSignInButton({
|
|
|
448
449
|
onError
|
|
449
450
|
}) {
|
|
450
451
|
const { loginPopup, loginRedirect, inProgress } = useMsalAuth();
|
|
452
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
451
453
|
const handleClick = async () => {
|
|
454
|
+
setIsLoading(true);
|
|
452
455
|
try {
|
|
453
456
|
if (useRedirect) {
|
|
454
457
|
await loginRedirect(scopes);
|
|
@@ -458,6 +461,8 @@ function MicrosoftSignInButton({
|
|
|
458
461
|
onSuccess?.();
|
|
459
462
|
} catch (error) {
|
|
460
463
|
onError?.(error);
|
|
464
|
+
} finally {
|
|
465
|
+
setTimeout(() => setIsLoading(false), 500);
|
|
461
466
|
}
|
|
462
467
|
};
|
|
463
468
|
const sizeStyles = {
|
|
@@ -489,6 +494,7 @@ function MicrosoftSignInButton({
|
|
|
489
494
|
border: "1px solid #8C8C8C"
|
|
490
495
|
}
|
|
491
496
|
};
|
|
497
|
+
const isDisabled = inProgress || isLoading;
|
|
492
498
|
const baseStyles = {
|
|
493
499
|
display: "inline-flex",
|
|
494
500
|
alignItems: "center",
|
|
@@ -497,9 +503,9 @@ function MicrosoftSignInButton({
|
|
|
497
503
|
fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif',
|
|
498
504
|
fontWeight: 600,
|
|
499
505
|
borderRadius: "2px",
|
|
500
|
-
cursor:
|
|
506
|
+
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
501
507
|
transition: "all 0.2s ease",
|
|
502
|
-
opacity:
|
|
508
|
+
opacity: isDisabled ? 0.6 : 1,
|
|
503
509
|
...variantStyles[variant],
|
|
504
510
|
...sizeStyles[size],
|
|
505
511
|
...style
|
|
@@ -508,7 +514,7 @@ function MicrosoftSignInButton({
|
|
|
508
514
|
"button",
|
|
509
515
|
{
|
|
510
516
|
onClick: handleClick,
|
|
511
|
-
disabled:
|
|
517
|
+
disabled: isDisabled,
|
|
512
518
|
className,
|
|
513
519
|
style: baseStyles,
|
|
514
520
|
"aria-label": text,
|
|
@@ -622,10 +628,10 @@ function MicrosoftLogo2() {
|
|
|
622
628
|
}
|
|
623
629
|
|
|
624
630
|
// src/components/UserAvatar.tsx
|
|
625
|
-
import { useEffect as useEffect3, useState as
|
|
631
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
626
632
|
|
|
627
633
|
// src/hooks/useUserProfile.ts
|
|
628
|
-
import { useState as
|
|
634
|
+
import { useState as useState3, useEffect as useEffect2, useCallback as useCallback3 } from "react";
|
|
629
635
|
|
|
630
636
|
// src/hooks/useGraphApi.ts
|
|
631
637
|
import { useCallback as useCallback2 } from "react";
|
|
@@ -748,9 +754,9 @@ function enforceCacheLimit() {
|
|
|
748
754
|
function useUserProfile() {
|
|
749
755
|
const { isAuthenticated, account } = useMsalAuth();
|
|
750
756
|
const graph = useGraphApi();
|
|
751
|
-
const [profile, setProfile] =
|
|
752
|
-
const [loading, setLoading] =
|
|
753
|
-
const [error, setError] =
|
|
757
|
+
const [profile, setProfile] = useState3(null);
|
|
758
|
+
const [loading, setLoading] = useState3(false);
|
|
759
|
+
const [error, setError] = useState3(null);
|
|
754
760
|
const fetchProfile = useCallback3(async () => {
|
|
755
761
|
if (!isAuthenticated || !account) {
|
|
756
762
|
setProfile(null);
|
|
@@ -858,8 +864,8 @@ function UserAvatar({
|
|
|
858
864
|
fallbackImage
|
|
859
865
|
}) {
|
|
860
866
|
const { profile, loading } = useUserProfile();
|
|
861
|
-
const [photoUrl, setPhotoUrl] =
|
|
862
|
-
const [photoError, setPhotoError] =
|
|
867
|
+
const [photoUrl, setPhotoUrl] = useState4(null);
|
|
868
|
+
const [photoError, setPhotoError] = useState4(false);
|
|
863
869
|
useEffect3(() => {
|
|
864
870
|
if (profile?.photo) {
|
|
865
871
|
setPhotoUrl(profile.photo);
|
|
@@ -1142,7 +1148,7 @@ var ErrorBoundary = class extends Component {
|
|
|
1142
1148
|
};
|
|
1143
1149
|
|
|
1144
1150
|
// src/hooks/useRoles.ts
|
|
1145
|
-
import { useState as
|
|
1151
|
+
import { useState as useState5, useEffect as useEffect5, useCallback as useCallback4 } from "react";
|
|
1146
1152
|
var rolesCache = /* @__PURE__ */ new Map();
|
|
1147
1153
|
var CACHE_DURATION2 = 5 * 60 * 1e3;
|
|
1148
1154
|
var MAX_CACHE_SIZE2 = 100;
|
|
@@ -1164,10 +1170,10 @@ function enforceCacheLimit2() {
|
|
|
1164
1170
|
function useRoles() {
|
|
1165
1171
|
const { isAuthenticated, account } = useMsalAuth();
|
|
1166
1172
|
const graph = useGraphApi();
|
|
1167
|
-
const [roles, setRoles] =
|
|
1168
|
-
const [groups, setGroups] =
|
|
1169
|
-
const [loading, setLoading] =
|
|
1170
|
-
const [error, setError] =
|
|
1173
|
+
const [roles, setRoles] = useState5([]);
|
|
1174
|
+
const [groups, setGroups] = useState5([]);
|
|
1175
|
+
const [loading, setLoading] = useState5(false);
|
|
1176
|
+
const [error, setError] = useState5(null);
|
|
1171
1177
|
const fetchRolesAndGroups = useCallback4(async () => {
|
|
1172
1178
|
if (!isAuthenticated || !account) {
|
|
1173
1179
|
setRoles([]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chemmangat/msal-next",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Production-grade MSAL authentication package for Next.js App Router with minimal boilerplate",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"dist",
|
|
22
22
|
"README.md",
|
|
23
23
|
"SECURITY.md",
|
|
24
|
-
"TROUBLESHOOTING.md"
|
|
24
|
+
"TROUBLESHOOTING.md",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"RELEASE_NOTES_v3.0.8.md"
|
|
25
27
|
],
|
|
26
28
|
"scripts": {
|
|
27
29
|
"build": "tsup",
|