@chemmangat/msal-next 2.0.0 → 2.0.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/README.md CHANGED
@@ -1,551 +1,551 @@
1
- # @chemmangat/msal-next
2
-
3
- Production-grade MSAL authentication library for Next.js App Router with minimal boilerplate.
4
-
5
- [![npm version](https://badge.fury.io/js/@chemmangat%2Fmsal-next.svg)](https://www.npmjs.com/package/@chemmangat/msal-next)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
-
8
- ## Features
9
-
10
- ✨ **Minimal Setup** - Just provide your `clientId` to get started
11
- 🔐 **Production Ready** - Comprehensive error handling, retry logic, and SSR support
12
- 🎨 **Beautiful Components** - Pre-styled Microsoft-branded UI components
13
- 🪝 **Powerful Hooks** - Easy-to-use hooks for auth, Graph API, and user data
14
- 🛡️ **Type Safe** - Full TypeScript support with generics for custom claims
15
- ⚡ **Edge Compatible** - Middleware support for protecting routes at the edge
16
- 📦 **Zero Config** - Sensible defaults with full customization options
17
-
18
- ## Installation
19
-
20
- ```bash
21
- npm install @chemmangat/msal-next @azure/msal-browser @azure/msal-react
22
- ```
23
-
24
- ## Quick Start
25
-
26
- ### 1. Wrap your app with MsalAuthProvider
27
-
28
- ```tsx
29
- // app/layout.tsx
30
- import { MsalAuthProvider } from '@chemmangat/msal-next';
31
-
32
- export default function RootLayout({ children }) {
33
- return (
34
- <html>
35
- <body>
36
- <MsalAuthProvider clientId="YOUR_CLIENT_ID">
37
- {children}
38
- </MsalAuthProvider>
39
- </body>
40
- </html>
41
- );
42
- }
43
- ```
44
-
45
- ### 2. Add sign-in button
46
-
47
- ```tsx
48
- // app/page.tsx
49
- 'use client';
50
-
51
- import { MicrosoftSignInButton, useMsalAuth } from '@chemmangat/msal-next';
52
-
53
- export default function Home() {
54
- const { isAuthenticated } = useMsalAuth();
55
-
56
- if (isAuthenticated) {
57
- return <div>Welcome! You're signed in.</div>;
58
- }
59
-
60
- return <MicrosoftSignInButton />;
61
- }
62
- ```
63
-
64
- That's it! 🎉
65
-
66
- ## Components
67
-
68
- ### MsalAuthProvider
69
-
70
- The root provider that initializes MSAL.
71
-
72
- ```tsx
73
- <MsalAuthProvider
74
- clientId="YOUR_CLIENT_ID"
75
- tenantId="YOUR_TENANT_ID" // Optional
76
- scopes={['User.Read', 'Mail.Read']} // Optional
77
- enableLogging={true} // Optional
78
- >
79
- {children}
80
- </MsalAuthProvider>
81
- ```
82
-
83
- ### MicrosoftSignInButton
84
-
85
- Pre-styled sign-in button with Microsoft branding.
86
-
87
- ```tsx
88
- <MicrosoftSignInButton
89
- variant="dark" // or "light"
90
- size="medium" // "small", "medium", "large"
91
- useRedirect={false} // Use popup by default
92
- onSuccess={() => console.log('Signed in!')}
93
- />
94
- ```
95
-
96
- ### SignOutButton
97
-
98
- Pre-styled sign-out button matching the sign-in button style.
99
-
100
- ```tsx
101
- <SignOutButton
102
- variant="light"
103
- size="medium"
104
- onSuccess={() => console.log('Signed out!')}
105
- />
106
- ```
107
-
108
- ### AuthGuard
109
-
110
- Protect pages/components that require authentication.
111
-
112
- ```tsx
113
- <AuthGuard
114
- loadingComponent={<div>Loading...</div>}
115
- fallbackComponent={<div>Redirecting to login...</div>}
116
- >
117
- <ProtectedContent />
118
- </AuthGuard>
119
- ```
120
-
121
- ### UserAvatar
122
-
123
- Display user photo from MS Graph with fallback initials.
124
-
125
- ```tsx
126
- <UserAvatar
127
- size={48}
128
- showTooltip={true}
129
- fallbackImage="/default-avatar.png"
130
- />
131
- ```
132
-
133
- ### AuthStatus
134
-
135
- Show current authentication state.
136
-
137
- ```tsx
138
- <AuthStatus
139
- showDetails={true}
140
- renderAuthenticated={(username) => (
141
- <div>Logged in as {username}</div>
142
- )}
143
- />
144
- ```
145
-
146
- ### ErrorBoundary
147
-
148
- Catch and handle authentication errors gracefully.
149
-
150
- ```tsx
151
- <ErrorBoundary
152
- fallback={(error, reset) => (
153
- <div>
154
- <p>Error: {error.message}</p>
155
- <button onClick={reset}>Try Again</button>
156
- </div>
157
- )}
158
- >
159
- <App />
160
- </ErrorBoundary>
161
- ```
162
-
163
- ## Hooks
164
-
165
- ### useMsalAuth
166
-
167
- Main authentication hook with all auth operations.
168
-
169
- ```tsx
170
- const {
171
- account,
172
- isAuthenticated,
173
- inProgress,
174
- loginPopup,
175
- loginRedirect,
176
- logoutPopup,
177
- logoutRedirect,
178
- acquireToken,
179
- } = useMsalAuth();
180
-
181
- // Login
182
- await loginPopup(['User.Read']);
183
-
184
- // Get token
185
- const token = await acquireToken(['User.Read']);
186
-
187
- // Logout
188
- await logoutPopup();
189
- ```
190
-
191
- ### useGraphApi
192
-
193
- Pre-configured fetch wrapper for MS Graph API.
194
-
195
- ```tsx
196
- const graph = useGraphApi();
197
-
198
- // GET request
199
- const user = await graph.get('/me');
200
-
201
- // POST request
202
- const message = await graph.post('/me/messages', {
203
- subject: 'Hello',
204
- body: { content: 'World' }
205
- });
206
-
207
- // Custom request
208
- const data = await graph.request('/me/drive', {
209
- scopes: ['Files.Read'],
210
- version: 'beta'
211
- });
212
- ```
213
-
214
- ### useUserProfile
215
-
216
- Fetch and cache user profile from MS Graph.
217
-
218
- ```tsx
219
- const { profile, loading, error, refetch } = useUserProfile();
220
-
221
- if (loading) return <div>Loading...</div>;
222
- if (error) return <div>Error: {error.message}</div>;
223
-
224
- return (
225
- <div>
226
- <h1>{profile.displayName}</h1>
227
- <p>{profile.mail}</p>
228
- <p>{profile.jobTitle}</p>
229
- </div>
230
- );
231
- ```
232
-
233
- ### useRoles
234
-
235
- Access user's Azure AD roles and groups.
236
-
237
- ```tsx
238
- const { roles, groups, hasRole, hasAnyRole } = useRoles();
239
-
240
- if (hasRole('Admin')) {
241
- return <AdminPanel />;
242
- }
243
-
244
- if (hasAnyRole(['Editor', 'Contributor'])) {
245
- return <EditorPanel />;
246
- }
247
-
248
- return <ViewerPanel />;
249
- ```
250
-
251
- ## Utilities
252
-
253
- ### withAuth
254
-
255
- Higher-order component for protecting pages.
256
-
257
- ```tsx
258
- const ProtectedPage = withAuth(MyPage, {
259
- useRedirect: true,
260
- scopes: ['User.Read']
261
- });
262
-
263
- export default ProtectedPage;
264
- ```
265
-
266
- ### getServerSession
267
-
268
- Server-side session helper for App Router.
269
-
270
- **Important:** Import from `@chemmangat/msal-next/server` in Server Components only.
271
-
272
- ```tsx
273
- // app/dashboard/page.tsx
274
- import { getServerSession } from '@chemmangat/msal-next/server';
275
- import { redirect } from 'next/navigation';
276
-
277
- export default async function DashboardPage() {
278
- const session = await getServerSession();
279
-
280
- if (!session.isAuthenticated) {
281
- redirect('/login');
282
- }
283
-
284
- return <div>Welcome {session.username}</div>;
285
- }
286
- ```
287
-
288
- ### createAuthMiddleware
289
-
290
- Protect routes at the edge with middleware.
291
-
292
- ```tsx
293
- // middleware.ts
294
- import { createAuthMiddleware } from '@chemmangat/msal-next';
295
-
296
- export const middleware = createAuthMiddleware({
297
- protectedRoutes: ['/dashboard', '/profile', '/api/protected'],
298
- publicOnlyRoutes: ['/login'],
299
- loginPath: '/login',
300
- debug: true,
301
- });
302
-
303
- export const config = {
304
- matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
305
- };
306
- ```
307
-
308
- ### Retry Logic
309
-
310
- Built-in exponential backoff for token acquisition.
311
-
312
- ```tsx
313
- import { retryWithBackoff, createRetryWrapper } from '@chemmangat/msal-next';
314
-
315
- // Wrap any async function with retry logic
316
- const token = await retryWithBackoff(
317
- () => acquireToken(['User.Read']),
318
- {
319
- maxRetries: 3,
320
- initialDelay: 1000,
321
- backoffMultiplier: 2,
322
- debug: true
323
- }
324
- );
325
-
326
- // Create a reusable retry wrapper
327
- const acquireTokenWithRetry = createRetryWrapper(acquireToken, {
328
- maxRetries: 3
329
- });
330
- ```
331
-
332
- ### Debug Logger
333
-
334
- Comprehensive logging for troubleshooting.
335
-
336
- ```tsx
337
- import { getDebugLogger } from '@chemmangat/msal-next';
338
-
339
- const logger = getDebugLogger({
340
- enabled: true,
341
- level: 'debug',
342
- showTimestamp: true
343
- });
344
-
345
- logger.info('User logged in', { username: 'user@example.com' });
346
- logger.error('Authentication failed', { error });
347
- ```
348
-
349
- ## TypeScript Support
350
-
351
- ### Custom Token Claims
352
-
353
- Extend the `CustomTokenClaims` interface for type-safe custom claims.
354
-
355
- ```tsx
356
- import { CustomTokenClaims } from '@chemmangat/msal-next';
357
-
358
- interface MyCustomClaims extends CustomTokenClaims {
359
- roles: string[];
360
- department: string;
361
- employeeId: string;
362
- }
363
-
364
- const { account } = useMsalAuth();
365
- const claims = account?.idTokenClaims as MyCustomClaims;
366
-
367
- console.log(claims.roles); // Type-safe!
368
- console.log(claims.department); // Type-safe!
369
- ```
370
-
371
- ## Advanced Examples
372
-
373
- ### Multi-Tenant Support
374
-
375
- ```tsx
376
- <MsalAuthProvider
377
- clientId="YOUR_CLIENT_ID"
378
- authorityType="common" // Supports any Azure AD tenant
379
- >
380
- {children}
381
- </MsalAuthProvider>
382
- ```
383
-
384
- ### Custom Scopes
385
-
386
- ```tsx
387
- <MsalAuthProvider
388
- clientId="YOUR_CLIENT_ID"
389
- scopes={[
390
- 'User.Read',
391
- 'Mail.Read',
392
- 'Calendars.Read',
393
- 'Files.Read.All'
394
- ]}
395
- >
396
- {children}
397
- </MsalAuthProvider>
398
- ```
399
-
400
- ### Multiple Account Selection
401
-
402
- ```tsx
403
- const { accounts, loginPopup } = useMsalAuth();
404
-
405
- // Show account picker
406
- await loginPopup(scopes, {
407
- prompt: 'select_account'
408
- });
409
-
410
- // List all accounts
411
- accounts.map(account => (
412
- <div key={account.homeAccountId}>
413
- {account.username}
414
- </div>
415
- ));
416
- ```
417
-
418
- ### Server-Side Rendering
419
-
420
- ```tsx
421
- // app/profile/page.tsx
422
- import { getServerSession } from '@chemmangat/msal-next';
423
-
424
- export default async function ProfilePage() {
425
- const session = await getServerSession();
426
-
427
- return (
428
- <div>
429
- <h1>Profile</h1>
430
- {session.isAuthenticated ? (
431
- <p>Welcome {session.username}</p>
432
- ) : (
433
- <p>Please sign in</p>
434
- )}
435
- </div>
436
- );
437
- }
438
- ```
439
-
440
- ### Role-Based Access Control
441
-
442
- ```tsx
443
- 'use client';
444
-
445
- import { useRoles, AuthGuard } from '@chemmangat/msal-next';
446
-
447
- function AdminPanel() {
448
- const { hasRole, hasAnyRole, hasAllRoles } = useRoles();
449
-
450
- if (!hasRole('Admin')) {
451
- return <div>Access denied</div>;
452
- }
453
-
454
- return <div>Admin content</div>;
455
- }
456
-
457
- export default function AdminPage() {
458
- return (
459
- <AuthGuard>
460
- <AdminPanel />
461
- </AuthGuard>
462
- );
463
- }
464
- ```
465
-
466
- ## Configuration Options
467
-
468
- ### MsalAuthConfig
469
-
470
- | Option | Type | Default | Description |
471
- |--------|------|---------|-------------|
472
- | `clientId` | `string` | **Required** | Azure AD Application (client) ID |
473
- | `tenantId` | `string` | `undefined` | Azure AD Directory (tenant) ID |
474
- | `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | `'common'` | Authority type |
475
- | `redirectUri` | `string` | `window.location.origin` | Redirect URI after auth |
476
- | `scopes` | `string[]` | `['User.Read']` | Default scopes |
477
- | `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | `'sessionStorage'` | Token cache location |
478
- | `enableLogging` | `boolean` | `false` | Enable debug logging |
479
-
480
- ## Troubleshooting
481
-
482
- ### Common Issues
483
-
484
- **Issue**: "No active account" error
485
- **Solution**: Ensure user is logged in before calling `acquireToken`
486
-
487
- **Issue**: Token acquisition fails
488
- **Solution**: Check that required scopes are granted in Azure AD
489
-
490
- **Issue**: SSR hydration mismatch
491
- **Solution**: Use `'use client'` directive for components using auth hooks
492
-
493
- **Issue**: Middleware not protecting routes
494
- **Solution**: Ensure session cookies are being set after login
495
-
496
- ### Debug Mode
497
-
498
- Enable debug logging to troubleshoot issues:
499
-
500
- ```tsx
501
- <MsalAuthProvider
502
- clientId="YOUR_CLIENT_ID"
503
- enableLogging={true}
504
- >
505
- {children}
506
- </MsalAuthProvider>
507
- ```
508
-
509
- ## Migration Guide
510
-
511
- ### From v1.x to v2.x
512
-
513
- v2.0 is backward compatible with v1.x. New features are additive:
514
-
515
- ```tsx
516
- // v1.x - Still works!
517
- import { MsalAuthProvider, useMsalAuth } from '@chemmangat/msal-next';
518
-
519
- // v2.x - New features
520
- import {
521
- AuthGuard,
522
- SignOutButton,
523
- UserAvatar,
524
- useGraphApi,
525
- useUserProfile,
526
- useRoles,
527
- withAuth,
528
- createAuthMiddleware,
529
- } from '@chemmangat/msal-next';
530
- ```
531
-
532
- ## Contributing
533
-
534
- Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
535
-
536
- ## License
537
-
538
- MIT © [Chemmangat](https://github.com/chemmangat)
539
-
540
- ## Support
541
-
542
- - 📖 [Documentation](https://github.com/chemmangat/msal-next#readme)
543
- - 🐛 [Issue Tracker](https://github.com/chemmangat/msal-next/issues)
544
- - 💬 [Discussions](https://github.com/chemmangat/msal-next/discussions)
545
-
546
- ## Acknowledgments
547
-
548
- Built with:
549
- - [@azure/msal-browser](https://github.com/AzureAD/microsoft-authentication-library-for-js)
550
- - [@azure/msal-react](https://github.com/AzureAD/microsoft-authentication-library-for-js)
551
- - [Next.js](https://nextjs.org/)
1
+ # @chemmangat/msal-next
2
+
3
+ Production-grade MSAL authentication library for Next.js App Router with minimal boilerplate.
4
+
5
+ [![npm version](https://badge.fury.io/js/@chemmangat%2Fmsal-next.svg)](https://www.npmjs.com/package/@chemmangat/msal-next)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ ✨ **Minimal Setup** - Just provide your `clientId` to get started
11
+ 🔐 **Production Ready** - Comprehensive error handling, retry logic, and SSR support
12
+ 🎨 **Beautiful Components** - Pre-styled Microsoft-branded UI components
13
+ 🪝 **Powerful Hooks** - Easy-to-use hooks for auth, Graph API, and user data
14
+ 🛡️ **Type Safe** - Full TypeScript support with generics for custom claims
15
+ ⚡ **Edge Compatible** - Middleware support for protecting routes at the edge
16
+ 📦 **Zero Config** - Sensible defaults with full customization options
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @chemmangat/msal-next @azure/msal-browser @azure/msal-react
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Wrap your app with MsalAuthProvider
27
+
28
+ ```tsx
29
+ // app/layout.tsx
30
+ import { MsalAuthProvider } from '@chemmangat/msal-next';
31
+
32
+ export default function RootLayout({ children }) {
33
+ return (
34
+ <html>
35
+ <body>
36
+ <MsalAuthProvider clientId="YOUR_CLIENT_ID">
37
+ {children}
38
+ </MsalAuthProvider>
39
+ </body>
40
+ </html>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ### 2. Add sign-in button
46
+
47
+ ```tsx
48
+ // app/page.tsx
49
+ 'use client';
50
+
51
+ import { MicrosoftSignInButton, useMsalAuth } from '@chemmangat/msal-next';
52
+
53
+ export default function Home() {
54
+ const { isAuthenticated } = useMsalAuth();
55
+
56
+ if (isAuthenticated) {
57
+ return <div>Welcome! You're signed in.</div>;
58
+ }
59
+
60
+ return <MicrosoftSignInButton />;
61
+ }
62
+ ```
63
+
64
+ That's it! 🎉
65
+
66
+ ## Components
67
+
68
+ ### MsalAuthProvider
69
+
70
+ The root provider that initializes MSAL.
71
+
72
+ ```tsx
73
+ <MsalAuthProvider
74
+ clientId="YOUR_CLIENT_ID"
75
+ tenantId="YOUR_TENANT_ID" // Optional
76
+ scopes={['User.Read', 'Mail.Read']} // Optional
77
+ enableLogging={true} // Optional
78
+ >
79
+ {children}
80
+ </MsalAuthProvider>
81
+ ```
82
+
83
+ ### MicrosoftSignInButton
84
+
85
+ Pre-styled sign-in button with Microsoft branding.
86
+
87
+ ```tsx
88
+ <MicrosoftSignInButton
89
+ variant="dark" // or "light"
90
+ size="medium" // "small", "medium", "large"
91
+ useRedirect={false} // Use popup by default
92
+ onSuccess={() => console.log('Signed in!')}
93
+ />
94
+ ```
95
+
96
+ ### SignOutButton
97
+
98
+ Pre-styled sign-out button matching the sign-in button style.
99
+
100
+ ```tsx
101
+ <SignOutButton
102
+ variant="light"
103
+ size="medium"
104
+ onSuccess={() => console.log('Signed out!')}
105
+ />
106
+ ```
107
+
108
+ ### AuthGuard
109
+
110
+ Protect pages/components that require authentication.
111
+
112
+ ```tsx
113
+ <AuthGuard
114
+ loadingComponent={<div>Loading...</div>}
115
+ fallbackComponent={<div>Redirecting to login...</div>}
116
+ >
117
+ <ProtectedContent />
118
+ </AuthGuard>
119
+ ```
120
+
121
+ ### UserAvatar
122
+
123
+ Display user photo from MS Graph with fallback initials.
124
+
125
+ ```tsx
126
+ <UserAvatar
127
+ size={48}
128
+ showTooltip={true}
129
+ fallbackImage="/default-avatar.png"
130
+ />
131
+ ```
132
+
133
+ ### AuthStatus
134
+
135
+ Show current authentication state.
136
+
137
+ ```tsx
138
+ <AuthStatus
139
+ showDetails={true}
140
+ renderAuthenticated={(username) => (
141
+ <div>Logged in as {username}</div>
142
+ )}
143
+ />
144
+ ```
145
+
146
+ ### ErrorBoundary
147
+
148
+ Catch and handle authentication errors gracefully.
149
+
150
+ ```tsx
151
+ <ErrorBoundary
152
+ fallback={(error, reset) => (
153
+ <div>
154
+ <p>Error: {error.message}</p>
155
+ <button onClick={reset}>Try Again</button>
156
+ </div>
157
+ )}
158
+ >
159
+ <App />
160
+ </ErrorBoundary>
161
+ ```
162
+
163
+ ## Hooks
164
+
165
+ ### useMsalAuth
166
+
167
+ Main authentication hook with all auth operations.
168
+
169
+ ```tsx
170
+ const {
171
+ account,
172
+ isAuthenticated,
173
+ inProgress,
174
+ loginPopup,
175
+ loginRedirect,
176
+ logoutPopup,
177
+ logoutRedirect,
178
+ acquireToken,
179
+ } = useMsalAuth();
180
+
181
+ // Login
182
+ await loginPopup(['User.Read']);
183
+
184
+ // Get token
185
+ const token = await acquireToken(['User.Read']);
186
+
187
+ // Logout
188
+ await logoutPopup();
189
+ ```
190
+
191
+ ### useGraphApi
192
+
193
+ Pre-configured fetch wrapper for MS Graph API.
194
+
195
+ ```tsx
196
+ const graph = useGraphApi();
197
+
198
+ // GET request
199
+ const user = await graph.get('/me');
200
+
201
+ // POST request
202
+ const message = await graph.post('/me/messages', {
203
+ subject: 'Hello',
204
+ body: { content: 'World' }
205
+ });
206
+
207
+ // Custom request
208
+ const data = await graph.request('/me/drive', {
209
+ scopes: ['Files.Read'],
210
+ version: 'beta'
211
+ });
212
+ ```
213
+
214
+ ### useUserProfile
215
+
216
+ Fetch and cache user profile from MS Graph.
217
+
218
+ ```tsx
219
+ const { profile, loading, error, refetch } = useUserProfile();
220
+
221
+ if (loading) return <div>Loading...</div>;
222
+ if (error) return <div>Error: {error.message}</div>;
223
+
224
+ return (
225
+ <div>
226
+ <h1>{profile.displayName}</h1>
227
+ <p>{profile.mail}</p>
228
+ <p>{profile.jobTitle}</p>
229
+ </div>
230
+ );
231
+ ```
232
+
233
+ ### useRoles
234
+
235
+ Access user's Azure AD roles and groups.
236
+
237
+ ```tsx
238
+ const { roles, groups, hasRole, hasAnyRole } = useRoles();
239
+
240
+ if (hasRole('Admin')) {
241
+ return <AdminPanel />;
242
+ }
243
+
244
+ if (hasAnyRole(['Editor', 'Contributor'])) {
245
+ return <EditorPanel />;
246
+ }
247
+
248
+ return <ViewerPanel />;
249
+ ```
250
+
251
+ ## Utilities
252
+
253
+ ### withAuth
254
+
255
+ Higher-order component for protecting pages.
256
+
257
+ ```tsx
258
+ const ProtectedPage = withAuth(MyPage, {
259
+ useRedirect: true,
260
+ scopes: ['User.Read']
261
+ });
262
+
263
+ export default ProtectedPage;
264
+ ```
265
+
266
+ ### getServerSession
267
+
268
+ Server-side session helper for App Router.
269
+
270
+ **Important:** Import from `@chemmangat/msal-next/server` in Server Components only.
271
+
272
+ ```tsx
273
+ // app/dashboard/page.tsx
274
+ import { getServerSession } from '@chemmangat/msal-next/server';
275
+ import { redirect } from 'next/navigation';
276
+
277
+ export default async function DashboardPage() {
278
+ const session = await getServerSession();
279
+
280
+ if (!session.isAuthenticated) {
281
+ redirect('/login');
282
+ }
283
+
284
+ return <div>Welcome {session.username}</div>;
285
+ }
286
+ ```
287
+
288
+ ### createAuthMiddleware
289
+
290
+ Protect routes at the edge with middleware.
291
+
292
+ ```tsx
293
+ // middleware.ts
294
+ import { createAuthMiddleware } from '@chemmangat/msal-next';
295
+
296
+ export const middleware = createAuthMiddleware({
297
+ protectedRoutes: ['/dashboard', '/profile', '/api/protected'],
298
+ publicOnlyRoutes: ['/login'],
299
+ loginPath: '/login',
300
+ debug: true,
301
+ });
302
+
303
+ export const config = {
304
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
305
+ };
306
+ ```
307
+
308
+ ### Retry Logic
309
+
310
+ Built-in exponential backoff for token acquisition.
311
+
312
+ ```tsx
313
+ import { retryWithBackoff, createRetryWrapper } from '@chemmangat/msal-next';
314
+
315
+ // Wrap any async function with retry logic
316
+ const token = await retryWithBackoff(
317
+ () => acquireToken(['User.Read']),
318
+ {
319
+ maxRetries: 3,
320
+ initialDelay: 1000,
321
+ backoffMultiplier: 2,
322
+ debug: true
323
+ }
324
+ );
325
+
326
+ // Create a reusable retry wrapper
327
+ const acquireTokenWithRetry = createRetryWrapper(acquireToken, {
328
+ maxRetries: 3
329
+ });
330
+ ```
331
+
332
+ ### Debug Logger
333
+
334
+ Comprehensive logging for troubleshooting.
335
+
336
+ ```tsx
337
+ import { getDebugLogger } from '@chemmangat/msal-next';
338
+
339
+ const logger = getDebugLogger({
340
+ enabled: true,
341
+ level: 'debug',
342
+ showTimestamp: true
343
+ });
344
+
345
+ logger.info('User logged in', { username: 'user@example.com' });
346
+ logger.error('Authentication failed', { error });
347
+ ```
348
+
349
+ ## TypeScript Support
350
+
351
+ ### Custom Token Claims
352
+
353
+ Extend the `CustomTokenClaims` interface for type-safe custom claims.
354
+
355
+ ```tsx
356
+ import { CustomTokenClaims } from '@chemmangat/msal-next';
357
+
358
+ interface MyCustomClaims extends CustomTokenClaims {
359
+ roles: string[];
360
+ department: string;
361
+ employeeId: string;
362
+ }
363
+
364
+ const { account } = useMsalAuth();
365
+ const claims = account?.idTokenClaims as MyCustomClaims;
366
+
367
+ console.log(claims.roles); // Type-safe!
368
+ console.log(claims.department); // Type-safe!
369
+ ```
370
+
371
+ ## Advanced Examples
372
+
373
+ ### Multi-Tenant Support
374
+
375
+ ```tsx
376
+ <MsalAuthProvider
377
+ clientId="YOUR_CLIENT_ID"
378
+ authorityType="common" // Supports any Azure AD tenant
379
+ >
380
+ {children}
381
+ </MsalAuthProvider>
382
+ ```
383
+
384
+ ### Custom Scopes
385
+
386
+ ```tsx
387
+ <MsalAuthProvider
388
+ clientId="YOUR_CLIENT_ID"
389
+ scopes={[
390
+ 'User.Read',
391
+ 'Mail.Read',
392
+ 'Calendars.Read',
393
+ 'Files.Read.All'
394
+ ]}
395
+ >
396
+ {children}
397
+ </MsalAuthProvider>
398
+ ```
399
+
400
+ ### Multiple Account Selection
401
+
402
+ ```tsx
403
+ const { accounts, loginPopup } = useMsalAuth();
404
+
405
+ // Show account picker
406
+ await loginPopup(scopes, {
407
+ prompt: 'select_account'
408
+ });
409
+
410
+ // List all accounts
411
+ accounts.map(account => (
412
+ <div key={account.homeAccountId}>
413
+ {account.username}
414
+ </div>
415
+ ));
416
+ ```
417
+
418
+ ### Server-Side Rendering
419
+
420
+ ```tsx
421
+ // app/profile/page.tsx
422
+ import { getServerSession } from '@chemmangat/msal-next';
423
+
424
+ export default async function ProfilePage() {
425
+ const session = await getServerSession();
426
+
427
+ return (
428
+ <div>
429
+ <h1>Profile</h1>
430
+ {session.isAuthenticated ? (
431
+ <p>Welcome {session.username}</p>
432
+ ) : (
433
+ <p>Please sign in</p>
434
+ )}
435
+ </div>
436
+ );
437
+ }
438
+ ```
439
+
440
+ ### Role-Based Access Control
441
+
442
+ ```tsx
443
+ 'use client';
444
+
445
+ import { useRoles, AuthGuard } from '@chemmangat/msal-next';
446
+
447
+ function AdminPanel() {
448
+ const { hasRole, hasAnyRole, hasAllRoles } = useRoles();
449
+
450
+ if (!hasRole('Admin')) {
451
+ return <div>Access denied</div>;
452
+ }
453
+
454
+ return <div>Admin content</div>;
455
+ }
456
+
457
+ export default function AdminPage() {
458
+ return (
459
+ <AuthGuard>
460
+ <AdminPanel />
461
+ </AuthGuard>
462
+ );
463
+ }
464
+ ```
465
+
466
+ ## Configuration Options
467
+
468
+ ### MsalAuthConfig
469
+
470
+ | Option | Type | Default | Description |
471
+ |--------|------|---------|-------------|
472
+ | `clientId` | `string` | **Required** | Azure AD Application (client) ID |
473
+ | `tenantId` | `string` | `undefined` | Azure AD Directory (tenant) ID |
474
+ | `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | `'common'` | Authority type |
475
+ | `redirectUri` | `string` | `window.location.origin` | Redirect URI after auth |
476
+ | `scopes` | `string[]` | `['User.Read']` | Default scopes |
477
+ | `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | `'sessionStorage'` | Token cache location |
478
+ | `enableLogging` | `boolean` | `false` | Enable debug logging |
479
+
480
+ ## Troubleshooting
481
+
482
+ ### Common Issues
483
+
484
+ **Issue**: "No active account" error
485
+ **Solution**: Ensure user is logged in before calling `acquireToken`
486
+
487
+ **Issue**: Token acquisition fails
488
+ **Solution**: Check that required scopes are granted in Azure AD
489
+
490
+ **Issue**: SSR hydration mismatch
491
+ **Solution**: Use `'use client'` directive for components using auth hooks
492
+
493
+ **Issue**: Middleware not protecting routes
494
+ **Solution**: Ensure session cookies are being set after login
495
+
496
+ ### Debug Mode
497
+
498
+ Enable debug logging to troubleshoot issues:
499
+
500
+ ```tsx
501
+ <MsalAuthProvider
502
+ clientId="YOUR_CLIENT_ID"
503
+ enableLogging={true}
504
+ >
505
+ {children}
506
+ </MsalAuthProvider>
507
+ ```
508
+
509
+ ## Migration Guide
510
+
511
+ ### From v1.x to v2.x
512
+
513
+ v2.0 is backward compatible with v1.x. New features are additive:
514
+
515
+ ```tsx
516
+ // v1.x - Still works!
517
+ import { MsalAuthProvider, useMsalAuth } from '@chemmangat/msal-next';
518
+
519
+ // v2.x - New features
520
+ import {
521
+ AuthGuard,
522
+ SignOutButton,
523
+ UserAvatar,
524
+ useGraphApi,
525
+ useUserProfile,
526
+ useRoles,
527
+ withAuth,
528
+ createAuthMiddleware,
529
+ } from '@chemmangat/msal-next';
530
+ ```
531
+
532
+ ## Contributing
533
+
534
+ Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
535
+
536
+ ## License
537
+
538
+ MIT © [Chemmangat](https://github.com/chemmangat)
539
+
540
+ ## Support
541
+
542
+ - 📖 [Documentation](https://github.com/chemmangat/msal-next#readme)
543
+ - 🐛 [Issue Tracker](https://github.com/chemmangat/msal-next/issues)
544
+ - 💬 [Discussions](https://github.com/chemmangat/msal-next/discussions)
545
+
546
+ ## Acknowledgments
547
+
548
+ Built with:
549
+ - [@azure/msal-browser](https://github.com/AzureAD/microsoft-authentication-library-for-js)
550
+ - [@azure/msal-react](https://github.com/AzureAD/microsoft-authentication-library-for-js)
551
+ - [Next.js](https://nextjs.org/)