@chemmangat/msal-next 4.2.0 → 4.2.2

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.
Files changed (2) hide show
  1. package/README.md +483 -614
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,108 +1,29 @@
1
1
  # @chemmangat/msal-next
2
2
 
3
- Production-grade MSAL authentication library for Next.js App Router with minimal boilerplate.
3
+ Microsoft/Azure AD authentication for Next.js App Router. Minimal setup, full TypeScript support, production-ready.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/@chemmangat%2Fmsal-next.svg)](https://www.npmjs.com/package/@chemmangat/msal-next)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Security](https://img.shields.io/badge/Security-A+-green.svg)](./SECURITY.md)
8
8
 
9
- > **📦 Current Version: 4.1.1** - Production-ready with automatic token refresh and enhanced security
9
+ **Current version: 4.2.1**
10
10
 
11
11
  ---
12
12
 
13
- ## ⭐ Why Choose @chemmangat/msal-next?
14
-
15
- ### 🚀 **5-Minute Setup**
16
- Get Azure AD authentication running in your Next.js app in just 5 minutes. No complex configuration, no boilerplate.
17
-
18
- ### 🔒 **Enterprise Security**
19
- Built on Microsoft's official MSAL library. All authentication happens client-side - tokens never touch your server. [Read Security Policy →](./SECURITY.md)
20
-
21
- ### 🎯 **Production-Ready**
22
- Used by 2,200+ developers in production. Automatic token refresh prevents unexpected logouts. Complete TypeScript support.
23
-
24
- ### 🤖 **AI-Friendly**
25
- Complete documentation optimized for AI assistants. Setup instructions that work on the first try.
26
-
27
- ### ⚡ **Zero Boilerplate**
28
- ```tsx
29
- <MSALProvider clientId="...">
30
- <MicrosoftSignInButton />
31
- </MSALProvider>
32
- ```
33
- That's it. You're done.
34
-
35
- ---
36
-
37
- ## 🎯 Top Features
38
-
39
- | Feature | Description | Status |
40
- |---------|-------------|--------|
41
- | **Automatic Token Refresh** | Prevents unexpected logouts | ✅ v4.1.0 |
42
- | **Complete TypeScript Types** | 30+ user profile fields | ✅ v4.0.2 |
43
- | **Actionable Error Messages** | Fix instructions included | ✅ v4.0.2 |
44
- | **Configuration Validation** | Catches mistakes in dev mode | ✅ v4.0.2 |
45
- | **Zero-Config Protected Routes** | One line to protect pages | ✅ v4.0.1 |
46
- | **Server Components Support** | Works in Next.js layouts | ✅ Always |
47
- | **Microsoft Graph Integration** | Pre-configured API client | ✅ Always |
48
- | **Role-Based Access Control** | Built-in RBAC support | ✅ Always |
49
-
50
- ---
51
-
52
- ## 🔒 Security First
53
-
54
- **Your tokens never leave the browser:**
55
- - ✅ Client-side authentication only
56
- - ✅ No server-side token storage
57
- - ✅ Microsoft's official MSAL library
58
- - ✅ Secure token storage (sessionStorage/localStorage)
59
- - ✅ Automatic error sanitization
60
- - ✅ HTTPS enforcement in production
61
-
62
- **[Read Complete Security Policy →](./SECURITY.md)**
63
-
64
- ---
65
-
66
- ## 🚀 Quick Start (5 Minutes)
67
-
68
- ### Step 1: Install the Package
13
+ ## Install
69
14
 
70
15
  ```bash
71
16
  npm install @chemmangat/msal-next @azure/msal-browser @azure/msal-react
72
17
  ```
73
18
 
74
- ### Step 2: Get Your Azure AD Credentials
75
-
76
- 1. Go to [Azure Portal](https://portal.azure.com)
77
- 2. Navigate to **Azure Active Directory** → **App registrations**
78
- 3. Click **New registration**
79
- 4. Enter a name (e.g., "My Next.js App")
80
- 5. Select **Single-page application (SPA)**
81
- 6. Add redirect URI: `http://localhost:3000` (for development)
82
- 7. Click **Register**
83
- 8. Copy the **Application (client) ID** and **Directory (tenant) ID**
84
-
85
- ### Step 3: Configure Environment Variables
86
-
87
- Create `.env.local` in your project root:
88
-
89
- ```bash
90
- # .env.local
91
- NEXT_PUBLIC_AZURE_AD_CLIENT_ID=your-client-id-here
92
- NEXT_PUBLIC_AZURE_AD_TENANT_ID=your-tenant-id-here
93
- ```
94
-
95
- **Important:**
96
- - Replace `your-client-id-here` and `your-tenant-id-here` with actual values from Azure Portal
97
- - Never commit `.env.local` to version control
98
- - Variables starting with `NEXT_PUBLIC_` are exposed to the browser (this is correct for MSAL)
19
+ ---
99
20
 
100
- **Security Note:** All authentication happens in the browser. Your tokens never touch your Next.js server. [Learn more →](./SECURITY.md)
21
+ ## Quick Start
101
22
 
102
- ### Step 4: Add Provider to Layout
23
+ ### 1. Add the provider to your layout
103
24
 
104
25
  ```tsx
105
- // app/layout.tsx (Server Component - no 'use client' needed!)
26
+ // app/layout.tsx (Server Component no 'use client' needed)
106
27
  import { MSALProvider } from '@chemmangat/msal-next';
107
28
 
108
29
  export default function RootLayout({ children }: { children: React.ReactNode }) {
@@ -121,489 +42,274 @@ export default function RootLayout({ children }: { children: React.ReactNode })
121
42
  }
122
43
  ```
123
44
 
124
- **Note:** MSALProvider is already marked as 'use client' internally. You don't need to add 'use client' to your layout.tsx!
45
+ `MSALProvider` is already marked `'use client'` internally.
125
46
 
126
- ### Step 5: Add Sign-In Button
47
+ ### 2. Add a sign-in button
127
48
 
128
49
  ```tsx
129
50
  // app/page.tsx
130
51
  'use client';
131
52
 
132
- import { MicrosoftSignInButton, useMsalAuth } from '@chemmangat/msal-next';
53
+ import { MicrosoftSignInButton, SignOutButton, useMsalAuth } from '@chemmangat/msal-next';
133
54
 
134
55
  export default function HomePage() {
135
56
  const { isAuthenticated, account } = useMsalAuth();
136
57
 
137
- if (isAuthenticated) {
138
- return (
139
- <div>
140
- <h1>Welcome, {account?.name}!</h1>
141
- <p>You are signed in as {account?.username}</p>
142
- </div>
143
- );
144
- }
145
-
146
- return (
58
+ return isAuthenticated ? (
147
59
  <div>
148
- <h1>Welcome to My App</h1>
149
- <MicrosoftSignInButton />
60
+ <p>Welcome, {account?.name}</p>
61
+ <SignOutButton />
150
62
  </div>
63
+ ) : (
64
+ <MicrosoftSignInButton />
151
65
  );
152
66
  }
153
67
  ```
154
68
 
155
- ### Step 6: Run Your App
156
-
157
- ```bash
158
- npm run dev
159
- ```
160
-
161
- Visit `http://localhost:3000` and click the sign-in button!
162
-
163
- ---
164
-
165
- ## 📋 Complete Setup Guide for AI Assistants
166
-
167
- When implementing MSAL authentication in a Next.js project, follow these steps:
168
-
169
- ### 1. Installation
170
-
171
- ```bash
172
- npm install @chemmangat/msal-next @azure/msal-browser@^4.0.0 @azure/msal-react@^3.0.0
173
- ```
174
-
175
- ### 2. Azure AD Configuration
176
-
177
- **Required Azure AD Setup:**
178
- - App registration created in Azure Portal
179
- - Redirect URI configured: `http://localhost:3000` (dev) and `https://yourdomain.com` (prod)
180
- - Platform type: **Single-page application (SPA)**
181
- - API permissions: At minimum `User.Read` (usually granted by default)
182
-
183
- **Get these values from Azure Portal:**
184
- - `Application (client) ID` → Use as `NEXT_PUBLIC_AZURE_AD_CLIENT_ID`
185
- - `Directory (tenant) ID` → Use as `NEXT_PUBLIC_AZURE_AD_TENANT_ID`
186
-
187
- ### 3. Environment Variables
188
-
189
- Create `.env.local`:
69
+ ### 3. Set environment variables
190
70
 
191
71
  ```bash
192
- NEXT_PUBLIC_AZURE_AD_CLIENT_ID=12345678-1234-1234-1234-123456789012
193
- NEXT_PUBLIC_AZURE_AD_TENANT_ID=87654321-4321-4321-4321-210987654321
194
- ```
195
-
196
- **Critical Rules:**
197
- - Variables MUST start with `NEXT_PUBLIC_` to be accessible in browser
198
- - Use actual GUIDs, not placeholder text
199
- - Never commit `.env.local` to version control
200
- - Restart dev server after changing environment variables
201
-
202
- ### 4. Project Structure
203
-
204
- ```
205
- your-app/
206
- ├── app/
207
- │ ├── layout.tsx # Add MSALProvider here
208
- │ ├── page.tsx # Add sign-in button here
209
- │ └── dashboard/
210
- │ └── page.tsx # Protected page example
211
- ├── .env.local # Environment variables
212
- └── package.json
213
- ```
214
-
215
- ### 5. Implementation Files
216
-
217
- **File 1: `app/layout.tsx` (Server Component)**
218
-
219
- ```tsx
220
- import { MSALProvider } from '@chemmangat/msal-next';
221
- import './globals.css';
222
-
223
- export default function RootLayout({
224
- children,
225
- }: {
226
- children: React.ReactNode;
227
- }) {
228
- return (
229
- <html lang="en">
230
- <body>
231
- <MSALProvider
232
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
233
- tenantId={process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID!}
234
- >
235
- {children}
236
- </MSALProvider>
237
- </body>
238
- </html>
239
- );
240
- }
241
- ```
242
-
243
- **File 2: `app/page.tsx` (Client Component)**
244
-
245
- ```tsx
246
- 'use client';
247
-
248
- import { MicrosoftSignInButton, SignOutButton, useMsalAuth } from '@chemmangat/msal-next';
249
-
250
- export default function HomePage() {
251
- const { isAuthenticated, account } = useMsalAuth();
252
-
253
- return (
254
- <div style={{ padding: '2rem' }}>
255
- <h1>My App</h1>
256
-
257
- {isAuthenticated ? (
258
- <div>
259
- <p>Welcome, {account?.name}!</p>
260
- <p>Email: {account?.username}</p>
261
- <SignOutButton />
262
- </div>
263
- ) : (
264
- <div>
265
- <p>Please sign in to continue</p>
266
- <MicrosoftSignInButton />
267
- </div>
268
- )}
269
- </div>
270
- );
271
- }
272
- ```
273
-
274
- **File 3: `app/dashboard/page.tsx` (Protected Page)**
275
-
276
- ```tsx
277
- 'use client';
278
-
279
- import { AuthGuard, useUserProfile } from '@chemmangat/msal-next';
280
-
281
- export default function DashboardPage() {
282
- return (
283
- <AuthGuard>
284
- <DashboardContent />
285
- </AuthGuard>
286
- );
287
- }
288
-
289
- function DashboardContent() {
290
- const { profile, loading } = useUserProfile();
291
-
292
- if (loading) return <div>Loading profile...</div>;
293
-
294
- return (
295
- <div style={{ padding: '2rem' }}>
296
- <h1>Dashboard</h1>
297
- <p>Name: {profile?.displayName}</p>
298
- <p>Email: {profile?.mail}</p>
299
- <p>Job Title: {profile?.jobTitle}</p>
300
- <p>Department: {profile?.department}</p>
301
- </div>
302
- );
303
- }
304
- ```
305
-
306
- ### 6. Common Patterns
307
-
308
- **Pattern 1: Check Authentication Status**
309
-
310
- ```tsx
311
- 'use client';
312
-
313
- import { useMsalAuth } from '@chemmangat/msal-next';
314
-
315
- export default function MyComponent() {
316
- const { isAuthenticated, account, inProgress } = useMsalAuth();
317
-
318
- if (inProgress) return <div>Loading...</div>;
319
- if (!isAuthenticated) return <div>Please sign in</div>;
320
-
321
- return <div>Hello, {account?.name}!</div>;
322
- }
323
- ```
324
-
325
- **Pattern 2: Get Access Token**
326
-
327
- ```tsx
328
- 'use client';
329
-
330
- import { useMsalAuth } from '@chemmangat/msal-next';
331
- import { useEffect, useState } from 'react';
332
-
333
- export default function DataComponent() {
334
- const { acquireToken, isAuthenticated } = useMsalAuth();
335
- const [data, setData] = useState(null);
336
-
337
- useEffect(() => {
338
- async function fetchData() {
339
- if (!isAuthenticated) return;
340
-
341
- try {
342
- const token = await acquireToken(['User.Read']);
343
-
344
- const response = await fetch('https://graph.microsoft.com/v1.0/me', {
345
- headers: { Authorization: `Bearer ${token}` },
346
- });
347
-
348
- const result = await response.json();
349
- setData(result);
350
- } catch (error) {
351
- console.error('Error fetching data:', error);
352
- }
353
- }
354
-
355
- fetchData();
356
- }, [isAuthenticated, acquireToken]);
357
-
358
- return <div>{/* Render data */}</div>;
359
- }
360
- ```
361
-
362
- **Pattern 3: Protect Routes**
363
-
364
- ```tsx
365
- 'use client';
366
-
367
- import { AuthGuard } from '@chemmangat/msal-next';
368
-
369
- export default function ProtectedPage() {
370
- return (
371
- <AuthGuard
372
- loadingComponent={<div>Checking authentication...</div>}
373
- fallbackComponent={<div>Redirecting to login...</div>}
374
- >
375
- <div>This content is protected</div>
376
- </AuthGuard>
377
- );
378
- }
379
- ```
380
-
381
- ### 7. Configuration Options
382
-
383
- ```tsx
384
- <MSALProvider
385
- // Required
386
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
387
-
388
- // Optional - for single-tenant apps
389
- tenantId={process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID}
390
-
391
- // Optional - for multi-tenant apps (default: 'common')
392
- authorityType="common" // or "organizations", "consumers", "tenant"
393
-
394
- // Optional - default scopes (default: ['User.Read'])
395
- scopes={['User.Read', 'Mail.Read']}
396
-
397
- // Optional - enable debug logging (default: false)
398
- enableLogging={true}
399
-
400
- // Optional - custom redirect URI (default: window.location.origin)
401
- redirectUri="https://yourdomain.com"
402
-
403
- // Optional - cache location (default: 'sessionStorage')
404
- cacheLocation="sessionStorage" // or "localStorage", "memoryStorage"
405
-
406
- // NEW in v4.1.0 - automatic token refresh
407
- autoRefreshToken={true} // Prevents unexpected logouts
408
- refreshBeforeExpiry={300} // Refresh 5 min before expiry
409
- >
410
- {children}
411
- </MSALProvider>
72
+ # .env.local
73
+ NEXT_PUBLIC_AZURE_AD_CLIENT_ID=your-client-id
74
+ NEXT_PUBLIC_AZURE_AD_TENANT_ID=your-tenant-id
412
75
  ```
413
76
 
414
- ### 8. Troubleshooting Checklist
415
-
416
- **If authentication doesn't work:**
417
-
418
- 1. ✅ Check environment variables are set correctly
419
- 2. ✅ Restart dev server after adding `.env.local`
420
- 3. ✅ Verify redirect URI in Azure Portal matches your app URL
421
- 4. ✅ Ensure `'use client'` is at the TOP of files using hooks
422
- 5. ✅ Check browser console for errors
423
- 6. ✅ Enable debug logging: `enableLogging={true}`
424
- 7. ✅ Verify client ID and tenant ID are valid GUIDs
425
-
426
- **Common Errors:**
77
+ ### 4. Azure AD setup
427
78
 
428
- - **"createContext only works in Client Components"** Use `MSALProvider` (not `MsalAuthProvider`) in layout.tsx
429
- - **"AADSTS50011: Redirect URI mismatch"** Add your URL to Azure Portal → Authentication → Redirect URIs
430
- - **"No active account"** User needs to sign in first before calling `acquireToken()`
431
- - **Environment variables undefined** Restart dev server after creating `.env.local`
79
+ 1. Go to [Azure Portal](https://portal.azure.com)Azure Active Directory App registrations
80
+ 2. Create a new registration, platform type: **Single-page application (SPA)**
81
+ 3. Add redirect URI: `http://localhost:3000` (dev) and your production URL
82
+ 4. Copy the **Application (client) ID** and **Directory (tenant) ID**
432
83
 
433
84
  ---
434
85
 
435
- ## 🎯 Key Features
436
-
437
- - ✅ **Zero-Config Setup** - Works out of the box with minimal configuration
438
- - ✅ **Automatic Token Refresh** - Prevents unexpected logouts (NEW in v4.1.0)
439
- - ✅ **TypeScript First** - Complete type safety with 30+ user profile fields
440
- - ✅ **Next.js 14+ Ready** - Built for App Router with Server Components support
441
- - ✅ **Automatic Validation** - Catches configuration mistakes in development
442
- - ✅ **Actionable Errors** - Clear error messages with fix instructions
443
- - ✅ **Production Ready** - Used by 2,200+ developers in production
444
- - ✅ **Fixed Interaction Issues** - No more "interaction in progress" errors (v4.1.0)
445
-
446
- ---
447
-
448
- ## 📚 API Reference
86
+ ## API Reference
449
87
 
450
88
  ### Components
451
89
 
452
90
  #### MSALProvider
453
- Wrap your app to provide authentication context.
454
91
 
455
92
  ```tsx
456
- <MSALProvider clientId="..." tenantId="...">
93
+ <MSALProvider
94
+ clientId="..."
95
+ tenantId="..."
96
+ authorityType="common"
97
+ scopes={['User.Read']}
98
+ redirectUri="https://myapp.com"
99
+ cacheLocation="sessionStorage"
100
+ enableLogging={false}
101
+ autoRefreshToken={true}
102
+ refreshBeforeExpiry={300}
103
+ allowedRedirectUris={['https://myapp.com']}
104
+ protection={{ defaultRedirectTo: '/login' }}
105
+ >
457
106
  {children}
458
107
  </MSALProvider>
459
108
  ```
460
109
 
110
+ | Prop | Type | Default | Description |
111
+ |------|------|---------|-------------|
112
+ | `clientId` | `string` | required | Azure AD Application (client) ID |
113
+ | `tenantId` | `string` | — | Directory (tenant) ID, single-tenant only |
114
+ | `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | `'common'` | Authority type |
115
+ | `redirectUri` | `string` | `window.location.origin` | Redirect URI after auth |
116
+ | `postLogoutRedirectUri` | `string` | `redirectUri` | Redirect URI after logout |
117
+ | `scopes` | `string[]` | `['User.Read']` | Default scopes |
118
+ | `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | `'sessionStorage'` | Token cache location |
119
+ | `enableLogging` | `boolean` | `false` | Debug logging |
120
+ | `autoRefreshToken` | `boolean` | `false` | Auto-refresh tokens before expiry |
121
+ | `refreshBeforeExpiry` | `number` | `300` | Seconds before expiry to refresh |
122
+ | `allowedRedirectUris` | `string[]` | — | Whitelist of allowed redirect URIs |
123
+ | `protection` | `AuthProtectionConfig` | — | Zero-config route protection config |
124
+
461
125
  #### MicrosoftSignInButton
462
- Pre-styled sign-in button with Microsoft branding.
463
126
 
464
127
  ```tsx
465
128
  <MicrosoftSignInButton
466
- variant="dark" // or "light"
467
- size="medium" // "small", "medium", "large"
468
- onSuccess={() => console.log('Signed in!')}
129
+ variant="dark" // 'dark' | 'light'
130
+ size="medium" // 'small' | 'medium' | 'large'
131
+ text="Sign in with Microsoft"
132
+ scopes={['User.Read']}
133
+ onSuccess={() => {}}
134
+ onError={(error) => {}}
469
135
  />
470
136
  ```
471
137
 
472
138
  #### SignOutButton
473
- Pre-styled sign-out button.
474
139
 
475
140
  ```tsx
476
141
  <SignOutButton
477
142
  variant="light"
478
143
  size="medium"
479
- onSuccess={() => console.log('Signed out!')}
144
+ onSuccess={() => {}}
145
+ onError={(error) => {}}
480
146
  />
481
147
  ```
482
148
 
483
149
  #### AuthGuard
484
- Protect components that require authentication.
150
+
151
+ Protects content and redirects unauthenticated users to login.
485
152
 
486
153
  ```tsx
487
154
  <AuthGuard
488
155
  loadingComponent={<div>Loading...</div>}
489
- fallbackComponent={<div>Please sign in</div>}
156
+ fallbackComponent={<div>Redirecting...</div>}
157
+ scopes={['User.Read']}
158
+ onAuthRequired={() => {}}
490
159
  >
491
160
  <ProtectedContent />
492
161
  </AuthGuard>
493
162
  ```
494
163
 
495
164
  #### UserAvatar
496
- Display user photo from Microsoft Graph.
497
165
 
498
166
  ```tsx
499
- <UserAvatar
500
- size={48}
501
- showTooltip={true}
502
- fallbackImage="/default-avatar.png"
167
+ <UserAvatar size={48} showTooltip fallbackImage="/avatar.png" />
168
+ ```
169
+
170
+ #### AccountSwitcher
171
+
172
+ ```tsx
173
+ <AccountSwitcher
174
+ variant="default" // 'default' | 'compact' | 'minimal'
175
+ maxAccounts={5}
176
+ showAvatars
177
+ showAddButton
178
+ showRemoveButton
179
+ onSwitch={(account) => {}}
180
+ onAdd={() => {}}
181
+ onRemove={(account) => {}}
182
+ />
183
+ ```
184
+
185
+ #### AccountList
186
+
187
+ ```tsx
188
+ <AccountList
189
+ showAvatars
190
+ showDetails
191
+ showActiveIndicator
192
+ clickToSwitch
193
+ orientation="vertical" // 'vertical' | 'horizontal'
194
+ onAccountClick={(account) => {}}
503
195
  />
504
196
  ```
505
197
 
198
+ ---
199
+
506
200
  ### Hooks
507
201
 
508
202
  #### useMsalAuth()
509
- Main authentication hook.
510
203
 
511
204
  ```tsx
512
205
  const {
513
- account, // Current user account
514
- accounts, // All cached accounts
515
- isAuthenticated, // Boolean: is user signed in?
516
- inProgress, // Boolean: is auth in progress?
517
- loginRedirect, // Function: sign in
518
- logoutRedirect, // Function: sign out
519
- acquireToken, // Function: get access token
206
+ account, // AccountInfo | null
207
+ accounts, // AccountInfo[]
208
+ isAuthenticated, // boolean
209
+ inProgress, // boolean
210
+ loginRedirect, // (scopes?: string[]) => Promise<void>
211
+ logoutRedirect, // () => Promise<void>
212
+ acquireToken, // (scopes: string[]) => Promise<string> — silent with redirect fallback
213
+ acquireTokenSilent, // (scopes: string[]) => Promise<string> — silent only
214
+ acquireTokenRedirect, // (scopes: string[]) => Promise<void>
215
+ clearSession, // () => Promise<void> — clears cache without Microsoft logout
520
216
  } = useMsalAuth();
521
217
  ```
522
218
 
523
219
  #### useUserProfile()
524
- Fetch user profile from Microsoft Graph.
220
+
221
+ Fetches user profile from Microsoft Graph `/me` (30+ fields).
525
222
 
526
223
  ```tsx
527
224
  const {
528
- profile, // User profile with 30+ fields
529
- loading, // Boolean: is loading?
530
- error, // Error object if failed
531
- refetch, // Function: refetch profile
532
- clearCache, // Function: clear cached profile
225
+ profile, // UserProfile | null
226
+ loading, // boolean
227
+ error, // Error | null
228
+ refetch, // () => Promise<void>
229
+ clearCache, // () => void
533
230
  } = useUserProfile();
534
231
 
535
- // Access profile fields
536
- console.log(profile?.displayName);
537
- console.log(profile?.department);
538
- console.log(profile?.preferredLanguage);
539
- console.log(profile?.employeeId);
232
+ // With custom fields
233
+ interface MyProfile extends UserProfile { customField: string }
234
+ const { profile } = useUserProfile<MyProfile>();
540
235
  ```
541
236
 
542
237
  #### useGraphApi()
543
- Pre-configured Microsoft Graph API client.
544
238
 
545
239
  ```tsx
546
240
  const graph = useGraphApi();
547
241
 
548
- // GET request
549
- const user = await graph.get('/me');
550
-
551
- // POST request
552
- const message = await graph.post('/me/messages', {
553
- subject: 'Hello',
554
- body: { content: 'World' }
555
- });
242
+ const user = await graph.get('/me');
243
+ const result = await graph.post('/me/messages', body);
244
+ await graph.put('/me/photo/$value', blob);
245
+ await graph.patch('/me', { displayName: 'New Name' });
246
+ await graph.delete('/me/messages/{id}');
247
+ const data = await graph.request('/me', { version: 'beta' });
556
248
  ```
557
249
 
558
250
  #### useRoles()
559
- Access user's Azure AD roles.
560
251
 
561
252
  ```tsx
562
253
  const {
563
- roles, // Array of role names
564
- groups, // Array of group IDs
565
- hasRole, // Function: check single role
566
- hasAnyRole, // Function: check multiple roles
567
- hasAllRoles, // Function: check all roles
254
+ roles, // string[]
255
+ groups, // string[]
256
+ loading, // boolean
257
+ error, // Error | null
258
+ hasRole, // (role: string) => boolean
259
+ hasGroup, // (groupId: string) => boolean
260
+ hasAnyRole, // (roles: string[]) => boolean
261
+ hasAllRoles, // (roles: string[]) => boolean
262
+ refetch, // () => Promise<void>
568
263
  } = useRoles();
569
-
570
- if (hasRole('Admin')) {
571
- // Show admin content
572
- }
573
264
  ```
574
265
 
575
- ---
576
-
577
- ## 🎓 Advanced Usage
578
-
579
- ### Automatic Token Refresh (NEW in v4.1.0)
266
+ #### useTokenRefresh()
580
267
 
581
- Prevent unexpected logouts by automatically refreshing tokens before they expire:
268
+ Monitors token expiry and optionally refreshes tokens automatically in the background. Use this when you want to show a session-expiry warning or trigger a manual refresh without relying solely on `MSALProvider`'s `autoRefreshToken` prop.
582
269
 
583
270
  ```tsx
584
- <MSALProvider
585
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
586
- autoRefreshToken={true} // Enable automatic refresh
587
- refreshBeforeExpiry={300} // Refresh 5 minutes before expiry
588
- >
589
- {children}
590
- </MSALProvider>
271
+ useTokenRefresh(options?: UseTokenRefreshOptions): UseTokenRefreshReturn
591
272
  ```
592
273
 
593
- **Monitor token expiry:**
274
+ Options (`UseTokenRefreshOptions`):
275
+
276
+ | Option | Type | Default | Description |
277
+ |--------|------|---------|-------------|
278
+ | `enabled` | `boolean` | `true` | Enable automatic background refresh |
279
+ | `refreshBeforeExpiry` | `number` | `300` | Seconds before expiry to trigger refresh |
280
+ | `scopes` | `string[]` | `['User.Read']` | Scopes to refresh |
281
+ | `onRefresh` | `(expiresIn: number) => void` | — | Called after a successful refresh |
282
+ | `onError` | `(error: Error) => void` | — | Called when refresh fails |
283
+
284
+ Return values (`UseTokenRefreshReturn`):
285
+
286
+ | Value | Type | Description |
287
+ |-------|------|-------------|
288
+ | `expiresIn` | `number \| null` | Seconds until the current token expires |
289
+ | `isExpiringSoon` | `boolean` | `true` when within `refreshBeforeExpiry` window |
290
+ | `refresh` | `() => Promise<void>` | Manually trigger a token refresh |
291
+ | `lastRefresh` | `Date \| null` | Timestamp of the last successful refresh |
594
292
 
595
293
  ```tsx
596
294
  'use client';
597
295
 
598
296
  import { useTokenRefresh } from '@chemmangat/msal-next';
599
297
 
600
- export default function SessionWarning() {
601
- const { expiresIn, isExpiringSoon } = useTokenRefresh();
298
+ export default function SessionBanner() {
299
+ const { expiresIn, isExpiringSoon, refresh, lastRefresh } = useTokenRefresh({
300
+ enabled: true,
301
+ refreshBeforeExpiry: 300, // warn/refresh 5 min before expiry
302
+ scopes: ['User.Read'],
303
+ onRefresh: (expiresIn) => console.log(`Refreshed — expires in ${expiresIn}s`),
304
+ onError: (error) => console.error('Refresh failed', error),
305
+ });
602
306
 
603
307
  if (isExpiringSoon) {
604
308
  return (
605
- <div className="warning">
606
- ⚠️ Your session will expire in {Math.floor(expiresIn / 60)} minutes
309
+ <div className="session-warning">
310
+ Session expires in {Math.floor((expiresIn ?? 0) / 60)} minutes.{' '}
311
+ <button onClick={refresh}>Stay signed in</button>
312
+ {lastRefresh && <span> Last refreshed: {lastRefresh.toLocaleTimeString()}</span>}
607
313
  </div>
608
314
  );
609
315
  }
@@ -612,35 +318,217 @@ export default function SessionWarning() {
612
318
  }
613
319
  ```
614
320
 
615
- ### Multi-Tenant Applications
321
+ #### useMultiAccount()
616
322
 
617
- For apps that support any Azure AD tenant:
323
+ Manages multiple simultaneously signed-in Microsoft accounts. Use this when your app needs to support users who work across multiple tenants or have both personal and work accounts. Accepts an optional `defaultScopes` argument used when adding a new account.
618
324
 
619
325
  ```tsx
620
- <MSALProvider
621
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
622
- authorityType="common" // No tenantId needed
623
- >
624
- {children}
625
- </MSALProvider>
326
+ useMultiAccount(defaultScopes?: string[]): UseMultiAccountReturn
327
+ ```
328
+
329
+ Return values (`UseMultiAccountReturn`):
330
+
331
+ | Value | Type | Description |
332
+ |-------|------|-------------|
333
+ | `accounts` | `AccountInfo[]` | All accounts currently in the MSAL cache |
334
+ | `activeAccount` | `AccountInfo \| null` | The currently active account |
335
+ | `hasMultipleAccounts` | `boolean` | `true` when more than one account is cached |
336
+ | `accountCount` | `number` | Total number of cached accounts |
337
+ | `inProgress` | `boolean` | `true` while an MSAL interaction is running |
338
+ | `switchAccount` | `(account: AccountInfo) => void` | Set a different account as active |
339
+ | `addAccount` | `(scopes?: string[]) => Promise<void>` | Sign in with an additional account |
340
+ | `removeAccount` | `(account: AccountInfo) => Promise<void>` | Remove an account from the cache |
341
+ | `signOutAccount` | `(account: AccountInfo) => Promise<void>` | Sign out a specific account |
342
+ | `signOutAll` | `() => Promise<void>` | Sign out all accounts |
343
+ | `getAccountByUsername` | `(username: string) => AccountInfo \| undefined` | Look up an account by username |
344
+ | `getAccountById` | `(homeAccountId: string) => AccountInfo \| undefined` | Look up an account by home account ID |
345
+ | `isActiveAccount` | `(account: AccountInfo) => boolean` | Check whether a given account is active |
346
+
347
+ ```tsx
348
+ 'use client';
349
+
350
+ import { useMultiAccount } from '@chemmangat/msal-next';
351
+
352
+ export default function AccountManager() {
353
+ const {
354
+ accounts,
355
+ activeAccount,
356
+ hasMultipleAccounts,
357
+ accountCount,
358
+ inProgress,
359
+ switchAccount,
360
+ addAccount,
361
+ signOutAccount,
362
+ signOutAll,
363
+ isActiveAccount,
364
+ } = useMultiAccount(['User.Read']);
365
+
366
+ return (
367
+ <div>
368
+ <p>Signed in as: {activeAccount?.name} ({activeAccount?.username})</p>
369
+ <p>{accountCount} account{accountCount !== 1 ? 's' : ''} cached</p>
370
+
371
+ {hasMultipleAccounts && (
372
+ <ul>
373
+ {accounts.map((account) => (
374
+ <li key={account.homeAccountId}>
375
+ {account.name}
376
+ {isActiveAccount(account) && ' (active)'}
377
+ <button onClick={() => switchAccount(account)} disabled={isActiveAccount(account)}>
378
+ Switch
379
+ </button>
380
+ <button onClick={() => signOutAccount(account)}>Sign out</button>
381
+ </li>
382
+ ))}
383
+ </ul>
384
+ )}
385
+
386
+ <button onClick={() => addAccount()} disabled={inProgress}>
387
+ Add another account
388
+ </button>
389
+ <button onClick={() => signOutAll()} disabled={inProgress}>
390
+ Sign out all
391
+ </button>
392
+ </div>
393
+ );
394
+ }
626
395
  ```
627
396
 
628
- ### Custom Scopes
397
+ ---
398
+
399
+ ### Higher-Order Components
629
400
 
630
- Request additional Microsoft Graph permissions:
401
+ #### withAuth
631
402
 
632
403
  ```tsx
633
- <MSALProvider
634
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
635
- scopes={['User.Read', 'Mail.Read', 'Calendars.Read', 'Files.Read']}
404
+ const ProtectedPage = withAuth(MyPage, {
405
+ loadingComponent: <Spinner />,
406
+ scopes: ['User.Read'],
407
+ });
408
+ ```
409
+
410
+ #### withPageAuth
411
+
412
+ Wraps a page component with authentication and optional role-based access control. Returns a new component that checks auth before rendering. Takes a `PageAuthConfig` as the second argument and an optional `AuthProtectionConfig` as the third for global defaults.
413
+
414
+ ```tsx
415
+ withPageAuth<P>(
416
+ Component: ComponentType<P>,
417
+ authConfig: PageAuthConfig,
418
+ globalConfig?: AuthProtectionConfig
419
+ ): ComponentType<P>
420
+ ```
421
+
422
+ `PageAuthConfig` options:
423
+
424
+ | Option | Type | Default | Description |
425
+ |--------|------|---------|-------------|
426
+ | `required` | `boolean` | `false` | Whether authentication is required |
427
+ | `roles` | `string[]` | — | User must have at least one of these roles (from `idTokenClaims.roles`) |
428
+ | `redirectTo` | `string` | `'/login'` | Where to redirect unauthenticated users |
429
+ | `loading` | `ReactNode` | — | Component shown while auth state is resolving |
430
+ | `unauthorized` | `ReactNode` | — | Component shown instead of redirecting when access is denied |
431
+ | `validate` | `(account: any) => boolean \| Promise<boolean>` | — | Custom access check; return `true` to allow, `false` to deny |
432
+
433
+ `AuthProtectionConfig` options (global defaults, third argument):
434
+
435
+ | Option | Type | Default | Description |
436
+ |--------|------|---------|-------------|
437
+ | `defaultRedirectTo` | `string` | `'/login'` | Fallback redirect for unauthenticated users |
438
+ | `defaultLoading` | `ReactNode` | — | Fallback loading component |
439
+ | `defaultUnauthorized` | `ReactNode` | — | Fallback unauthorized component |
440
+ | `debug` | `boolean` | `false` | Log auth decisions to the console |
441
+
442
+ ```tsx
443
+ // app/dashboard/page.tsx
444
+ 'use client';
445
+
446
+ import { withPageAuth } from '@chemmangat/msal-next';
447
+
448
+ function Dashboard() {
449
+ return <div>Dashboard — only admins and editors can see this</div>;
450
+ }
451
+
452
+ export default withPageAuth(
453
+ Dashboard,
454
+ {
455
+ required: true,
456
+ roles: ['Admin', 'Editor'],
457
+ redirectTo: '/login',
458
+ loading: <div>Checking access...</div>,
459
+ unauthorized: <div>You do not have permission to view this page.</div>,
460
+ },
461
+ {
462
+ debug: process.env.NODE_ENV === 'development',
463
+ }
464
+ );
465
+ ```
466
+
467
+ Custom `validate` — restrict by email domain:
468
+
469
+ ```tsx
470
+ export default withPageAuth(Dashboard, {
471
+ required: true,
472
+ validate: (account) => account.username.endsWith('@company.com'),
473
+ unauthorized: <div>Only company accounts are allowed.</div>,
474
+ });
475
+ ```
476
+
477
+ #### ProtectedPage
478
+
479
+ The underlying component that `withPageAuth` uses internally. Use it directly when you need to protect arbitrary JSX rather than a whole page component — for example inside a layout or a slot.
480
+
481
+ ```tsx
482
+ <ProtectedPage
483
+ config={PageAuthConfig}
484
+ defaultRedirectTo="/login"
485
+ defaultLoading={<Spinner />}
486
+ defaultUnauthorized={<div>Access denied</div>}
487
+ debug={false}
636
488
  >
637
489
  {children}
638
- </MSALProvider>
490
+ </ProtectedPage>
639
491
  ```
640
492
 
641
- ### Server-Side Session
493
+ Props (`ProtectedPageProps`):
642
494
 
643
- Access authentication state in Server Components:
495
+ | Prop | Type | Default | Description |
496
+ |------|------|---------|-------------|
497
+ | `children` | `ReactNode` | required | Content to render when access is granted |
498
+ | `config` | `PageAuthConfig` | required | Per-page auth rules (same shape as `withPageAuth` second arg) |
499
+ | `defaultRedirectTo` | `string` | `'/login'` | Redirect path when unauthenticated |
500
+ | `defaultLoading` | `ReactNode` | — | Loading component while auth resolves |
501
+ | `defaultUnauthorized` | `ReactNode` | — | Component shown when access is denied |
502
+ | `debug` | `boolean` | `false` | Log auth decisions to the console |
503
+
504
+ ```tsx
505
+ // app/settings/layout.tsx
506
+ 'use client';
507
+
508
+ import { ProtectedPage } from '@chemmangat/msal-next';
509
+
510
+ export default function SettingsLayout({ children }: { children: React.ReactNode }) {
511
+ return (
512
+ <ProtectedPage
513
+ config={{
514
+ required: true,
515
+ roles: ['Admin'],
516
+ redirectTo: '/login',
517
+ }}
518
+ defaultLoading={<div>Loading...</div>}
519
+ defaultUnauthorized={<div>Admins only.</div>}
520
+ >
521
+ {children}
522
+ </ProtectedPage>
523
+ );
524
+ }
525
+ ```
526
+
527
+ ---
528
+
529
+ ### Server Utilities
530
+
531
+ #### getServerSession
644
532
 
645
533
  ```tsx
646
534
  // app/profile/page.tsx (Server Component)
@@ -649,33 +537,48 @@ import { redirect } from 'next/navigation';
649
537
 
650
538
  export default async function ProfilePage() {
651
539
  const session = await getServerSession();
540
+ if (!session.isAuthenticated) redirect('/login');
541
+ return <div>Welcome, {session.username}</div>;
542
+ }
543
+ ```
652
544
 
653
- if (!session.isAuthenticated) {
654
- redirect('/login');
655
- }
545
+ ---
656
546
 
657
- return (
658
- <div>
659
- <h1>Profile</h1>
660
- <p>Welcome, {session.username}</p>
661
- </div>
662
- );
663
- }
547
+ ### Middleware
548
+
549
+ #### createAuthMiddleware
550
+
551
+ Creates a Next.js Edge middleware function that enforces authentication at the routing level — before any page or API route renders. Use this to protect entire route groups without adding `AuthGuard` or `withPageAuth` to every page.
552
+
553
+ The middleware reads a session cookie (`msal.account` by default) to determine auth state. Because MSAL runs in the browser, the cookie must be set client-side after login (e.g. via `setServerSessionCookie` from `@chemmangat/msal-next/server`). You can also supply a custom `isAuthenticated` function to integrate your own session store.
554
+
555
+ ```tsx
556
+ createAuthMiddleware(config?: AuthMiddlewareConfig): (request: NextRequest) => Promise<NextResponse>
664
557
  ```
665
558
 
666
- ### Middleware Protection
559
+ `AuthMiddlewareConfig` options:
667
560
 
668
- Protect routes at the edge:
561
+ | Option | Type | Default | Description |
562
+ |--------|------|---------|-------------|
563
+ | `protectedRoutes` | `string[]` | — | Paths that require authentication. Unauthenticated requests redirect to `loginPath` |
564
+ | `publicOnlyRoutes` | `string[]` | — | Paths only accessible when NOT authenticated (e.g. `/login`). Authenticated users are redirected to `redirectAfterLogin` |
565
+ | `loginPath` | `string` | `'/login'` | Where to redirect unauthenticated users |
566
+ | `redirectAfterLogin` | `string` | `'/'` | Where to redirect authenticated users away from `publicOnlyRoutes` |
567
+ | `sessionCookie` | `string` | `'msal.account'` | Cookie name used to detect an active session |
568
+ | `isAuthenticated` | `(request: NextRequest) => boolean \| Promise<boolean>` | — | Custom auth check; overrides the default cookie check |
569
+ | `debug` | `boolean` | `false` | Log routing decisions to the console |
669
570
 
670
571
  ```tsx
671
572
  // middleware.ts
672
573
  import { createAuthMiddleware } from '@chemmangat/msal-next';
673
574
 
674
575
  export const middleware = createAuthMiddleware({
675
- protectedRoutes: ['/dashboard', '/profile', '/api/protected'],
676
- publicOnlyRoutes: ['/login'],
576
+ protectedRoutes: ['/dashboard', '/profile', '/settings', '/api/protected'],
577
+ publicOnlyRoutes: ['/login', '/signup'],
677
578
  loginPath: '/login',
678
- debug: true,
579
+ redirectAfterLogin: '/dashboard',
580
+ sessionCookie: 'msal.account',
581
+ debug: process.env.NODE_ENV === 'development',
679
582
  });
680
583
 
681
584
  export const config = {
@@ -683,174 +586,140 @@ export const config = {
683
586
  };
684
587
  ```
685
588
 
686
- ### Error Handling
687
-
688
- Use enhanced error handling for better debugging:
589
+ Custom `isAuthenticated` — use your own session store:
689
590
 
690
591
  ```tsx
691
- 'use client';
692
-
693
- import { useMsalAuth, wrapMsalError } from '@chemmangat/msal-next';
592
+ // middleware.ts
593
+ import { createAuthMiddleware } from '@chemmangat/msal-next';
594
+ import { verifySession } from './lib/session';
694
595
 
695
- export default function LoginPage() {
696
- const { loginRedirect } = useMsalAuth();
697
-
698
- const handleLogin = async () => {
699
- try {
700
- await loginRedirect();
701
- } catch (error) {
702
- const msalError = wrapMsalError(error);
703
-
704
- // Check if user cancelled (not a real error)
705
- if (msalError.isUserCancellation()) {
706
- return;
707
- }
708
-
709
- // Display actionable error message
710
- console.error(msalError.toConsoleString());
711
- }
712
- };
596
+ export const middleware = createAuthMiddleware({
597
+ protectedRoutes: ['/dashboard'],
598
+ loginPath: '/login',
599
+ isAuthenticated: async (request) => {
600
+ const token = request.cookies.get('session-token')?.value;
601
+ if (!token) return false;
602
+ return verifySession(token);
603
+ },
604
+ });
713
605
 
714
- return <button onClick={handleLogin}>Sign In</button>;
715
- }
606
+ export const config = {
607
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
608
+ };
716
609
  ```
717
610
 
718
- ### Custom Profile Fields
611
+ ---
612
+
613
+ ## Common Patterns
719
614
 
720
- Extend UserProfile with organization-specific fields:
615
+ ### Protect a page with AuthGuard
721
616
 
722
617
  ```tsx
723
618
  'use client';
724
619
 
725
- import { useUserProfile, UserProfile } from '@chemmangat/msal-next';
620
+ import { AuthGuard, useUserProfile } from '@chemmangat/msal-next';
726
621
 
727
- interface MyCompanyProfile extends UserProfile {
728
- customField: string;
622
+ export default function DashboardPage() {
623
+ return (
624
+ <AuthGuard>
625
+ <DashboardContent />
626
+ </AuthGuard>
627
+ );
729
628
  }
730
629
 
731
- export default function ProfilePage() {
732
- const { profile } = useUserProfile<MyCompanyProfile>();
733
-
630
+ function DashboardContent() {
631
+ const { profile, loading } = useUserProfile();
632
+ if (loading) return <div>Loading...</div>;
734
633
  return (
735
634
  <div>
736
- <p>Department: {profile?.department}</p>
737
- <p>Custom Field: {profile?.customField}</p>
635
+ <p>{profile?.displayName}</p>
636
+ <p>{profile?.mail}</p>
637
+ <p>{profile?.department}</p>
738
638
  </div>
739
639
  );
740
640
  }
741
641
  ```
742
642
 
743
- ---
744
-
745
- ## 🔧 Configuration Reference
746
-
747
- ### MSALProvider Props
748
-
749
- | Prop | Type | Required | Default | Description |
750
- |------|------|----------|---------|-------------|
751
- | `clientId` | `string` | ✅ Yes | - | Azure AD Application (client) ID |
752
- | `tenantId` | `string` | No | - | Azure AD Directory (tenant) ID (for single-tenant) |
753
- | `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | No | `'common'` | Authority type |
754
- | `redirectUri` | `string` | No | `window.location.origin` | Redirect URI after authentication |
755
- | `scopes` | `string[]` | No | `['User.Read']` | Default scopes |
756
- | `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | No | `'sessionStorage'` | Token cache location |
757
- | `enableLogging` | `boolean` | No | `false` | Enable debug logging |
758
-
759
- ### Authority Types
643
+ ### Acquire a token for API calls
760
644
 
761
- - **`common`** - Multi-tenant (any Azure AD tenant or Microsoft account)
762
- - **`organizations`** - Any organizational Azure AD tenant
763
- - **`consumers`** - Microsoft personal accounts only
764
- - **`tenant`** - Single-tenant (requires `tenantId`)
765
-
766
- ---
767
-
768
- ## 📖 Additional Resources
769
-
770
- ### Documentation
771
- - [SECURITY.md](./SECURITY.md) - **Security policy and best practices** ⭐
772
- - [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) - Common issues and solutions
773
- - [CHANGELOG.md](./CHANGELOG.md) - Version history
774
- - [MIGRATION_GUIDE_v3.md](./MIGRATION_GUIDE_v3.md) - Migrating from v2.x
775
- - [EXAMPLES_v4.0.2.md](./EXAMPLES_v4.0.2.md) - Code examples
776
-
777
- ### Security
778
- - 🔒 [Security Policy](./SECURITY.md) - Complete security documentation
779
- - 🛡️ [Best Practices](./SECURITY.md#-best-practices) - Security guidelines
780
- - ⚠️ [Common Mistakes](./SECURITY.md#-common-security-mistakes) - What to avoid
781
- - ✅ [Security Checklist](./SECURITY.md#-security-checklist) - Pre-deployment checklist
782
-
783
- ### Links
784
- - 📦 [npm Package](https://www.npmjs.com/package/@chemmangat/msal-next)
785
- - 🚀 [Live Demo](https://github.com/Chemmangat/msal-next-demo) - Sample implementation
786
- - 🐛 [Report Issues](https://github.com/chemmangat/msal-next/issues)
787
- - 💬 [Discussions](https://github.com/chemmangat/msal-next/discussions)
788
- - ⭐ [GitHub Repository](https://github.com/chemmangat/msal-next)
789
-
790
- ### Microsoft Resources
791
- - [Azure AD Documentation](https://learn.microsoft.com/en-us/azure/active-directory/)
792
- - [MSAL.js Documentation](https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-overview)
793
- - [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview)
794
-
795
- ---
645
+ ```tsx
646
+ 'use client';
796
647
 
797
- ## FAQ
648
+ import { useMsalAuth } from '@chemmangat/msal-next';
798
649
 
799
- **Q: Do I need to create an Azure AD app registration?**
800
- A: Yes, you need an app registration in Azure Portal to get the client ID and tenant ID.
650
+ export default function DataComponent() {
651
+ const { acquireToken, isAuthenticated } = useMsalAuth();
801
652
 
802
- **Q: Can I use this with Next.js Pages Router?**
803
- A: This package is designed for App Router. For Pages Router, use v2.x or consider migrating to App Router.
653
+ const fetchData = async () => {
654
+ if (!isAuthenticated) return;
655
+ const token = await acquireToken(['User.Read']);
656
+ const res = await fetch('https://graph.microsoft.com/v1.0/me', {
657
+ headers: { Authorization: `Bearer ${token}` },
658
+ });
659
+ return res.json();
660
+ };
661
+ }
662
+ ```
804
663
 
805
- **Q: Is this free to use?**
806
- A: Yes, the package is MIT licensed and free. Azure AD has a free tier for up to 50,000 users.
664
+ ### Error handling
807
665
 
808
- **Q: Can I use this for multi-tenant SaaS apps?**
809
- A: Yes! Set `authorityType="common"` and omit `tenantId`.
666
+ ```tsx
667
+ import { useMsalAuth, wrapMsalError } from '@chemmangat/msal-next';
810
668
 
811
- **Q: How do I get additional user information?**
812
- A: Use `useUserProfile()` hook which provides 30+ fields from Microsoft Graph.
669
+ const { loginRedirect } = useMsalAuth();
813
670
 
814
- **Q: Can I customize the sign-in button?**
815
- A: Yes, `MicrosoftSignInButton` accepts `variant`, `size`, `className`, and `style` props.
671
+ const handleLogin = async () => {
672
+ try {
673
+ await loginRedirect();
674
+ } catch (error) {
675
+ const msalError = wrapMsalError(error);
676
+ if (msalError.isUserCancellation()) return;
677
+ console.error(msalError.toConsoleString());
678
+ }
679
+ };
680
+ ```
816
681
 
817
- **Q: Does this work with Azure AD B2C?**
818
- A: This package is designed for Azure AD. For B2C, you may need additional configuration.
682
+ ### Automatic token refresh
819
683
 
820
- **Q: How do I protect API routes?**
821
- A: Use `getServerSession()` in API routes or `createAuthMiddleware()` for edge protection.
684
+ ```tsx
685
+ <MSALProvider
686
+ clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
687
+ autoRefreshToken={true}
688
+ refreshBeforeExpiry={300}
689
+ >
690
+ {children}
691
+ </MSALProvider>
692
+ ```
822
693
 
823
694
  ---
824
695
 
825
- ## 🤝 Contributing
826
-
827
- Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
696
+ ## Troubleshooting
828
697
 
829
- ---
830
-
831
- ## 📄 License
698
+ - **"createContext only works in Client Components"** — use `MSALProvider` (not `MsalAuthProvider`) in layout.tsx
699
+ - **"AADSTS50011: Redirect URI mismatch"** — add your URL to Azure Portal → Authentication → Redirect URIs
700
+ - **"No active account"** — user must sign in before calling `acquireToken()`
701
+ - **Environment variables undefined** — restart the dev server after creating `.env.local`
832
702
 
833
- MIT © [Chemmangat](https://github.com/chemmangat)
703
+ See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for more.
834
704
 
835
705
  ---
836
706
 
837
- ## 🙏 Acknowledgments
707
+ ## Additional Resources
838
708
 
839
- Built with:
840
- - [@azure/msal-browser](https://github.com/AzureAD/microsoft-authentication-library-for-js)
841
- - [@azure/msal-react](https://github.com/AzureAD/microsoft-authentication-library-for-js)
842
- - [Next.js](https://nextjs.org/)
709
+ - [SECURITY.md](./SECURITY.md) — security policy and best practices
710
+ - [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) — common issues
711
+ - [CHANGELOG.md](./CHANGELOG.md) — version history
712
+ - [MIGRATION_GUIDE_v3.md](./MIGRATION_GUIDE_v3.md) — migrating from v2.x
713
+ - [npm package](https://www.npmjs.com/package/@chemmangat/msal-next)
714
+ - [GitHub](https://github.com/chemmangat/msal-next)
715
+ - [Report issues](https://github.com/chemmangat/msal-next/issues)
843
716
 
844
717
  ---
845
718
 
846
- ## 📊 Stats
719
+ ## Contributing
847
720
 
848
- - 📦 2,200+ weekly downloads
849
- - ⭐ Used in production by developers worldwide
850
- - 🔒 Security-focused with regular updates
851
- - 📚 Comprehensive documentation
852
- - 🎯 TypeScript-first with complete type safety
721
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md).
853
722
 
854
- ---
723
+ ## License
855
724
 
856
- **Need help?** Check [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) or [open an issue](https://github.com/chemmangat/msal-next/issues)!
725
+ MIT © [Chemmangat](https://github.com/chemmangat)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chemmangat/msal-next",
3
- "version": "4.2.0",
3
+ "version": "4.2.2",
4
4
  "description": "Production-ready Microsoft/Azure AD authentication for Next.js App Router. Zero-config setup, TypeScript-first, multi-account support, auto token refresh. The easiest way to add Microsoft login to your Next.js app.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",