@chemmangat/msal-next 4.2.1 → 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 +454 -708
  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.2.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,544 +42,356 @@ 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:**
427
-
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`
432
-
433
- ---
434
-
435
- ## 🎯 Key Features
77
+ ### 4. Azure AD setup
436
78
 
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)
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**
445
83
 
446
84
  ---
447
85
 
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!')}
469
- onError={(error) => console.error(error)}
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) => {}}
470
135
  />
471
136
  ```
472
137
 
473
138
  #### SignOutButton
474
- Pre-styled sign-out button.
475
139
 
476
140
  ```tsx
477
141
  <SignOutButton
478
142
  variant="light"
479
143
  size="medium"
480
- onSuccess={() => console.log('Signed out!')}
481
- onError={(error) => console.error(error)}
144
+ onSuccess={() => {}}
145
+ onError={(error) => {}}
482
146
  />
483
147
  ```
484
148
 
485
149
  #### AuthGuard
486
- Protect components that require authentication.
150
+
151
+ Protects content and redirects unauthenticated users to login.
487
152
 
488
153
  ```tsx
489
154
  <AuthGuard
490
155
  loadingComponent={<div>Loading...</div>}
491
- fallbackComponent={<div>Please sign in</div>}
156
+ fallbackComponent={<div>Redirecting...</div>}
157
+ scopes={['User.Read']}
158
+ onAuthRequired={() => {}}
492
159
  >
493
160
  <ProtectedContent />
494
161
  </AuthGuard>
495
162
  ```
496
163
 
497
164
  #### UserAvatar
498
- Display user photo from Microsoft Graph.
499
165
 
500
166
  ```tsx
501
- <UserAvatar
502
- size={48}
503
- showTooltip={true}
504
- 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) => {}}
505
195
  />
506
196
  ```
507
197
 
198
+ ---
199
+
508
200
  ### Hooks
509
201
 
510
202
  #### useMsalAuth()
511
- Main authentication hook.
512
203
 
513
204
  ```tsx
514
205
  const {
515
- account, // Current user account
516
- accounts, // All cached accounts
517
- isAuthenticated, // Boolean: is user signed in?
518
- inProgress, // Boolean: is auth in progress?
519
- loginRedirect, // Function: sign in (redirect flow)
520
- logoutRedirect, // Function: sign out (redirect flow)
521
- acquireToken, // Function: get access token (silent with redirect fallback)
522
- acquireTokenSilent, // Function: get access token (silent only, no fallback)
523
- acquireTokenRedirect, // Function: get access token via redirect
524
- clearSession, // Function: clear MSAL session without Microsoft logout
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
525
216
  } = useMsalAuth();
526
217
  ```
527
218
 
528
219
  #### useUserProfile()
529
- Fetch user profile from Microsoft Graph.
220
+
221
+ Fetches user profile from Microsoft Graph `/me` (30+ fields).
530
222
 
531
223
  ```tsx
532
224
  const {
533
- profile, // User profile with 30+ fields
534
- loading, // Boolean: is loading?
535
- error, // Error object if failed
536
- refetch, // Function: refetch profile
537
- clearCache, // Function: clear cached profile
225
+ profile, // UserProfile | null
226
+ loading, // boolean
227
+ error, // Error | null
228
+ refetch, // () => Promise<void>
229
+ clearCache, // () => void
538
230
  } = useUserProfile();
539
231
 
540
- // Access profile fields
541
- console.log(profile?.displayName);
542
- console.log(profile?.department);
543
- console.log(profile?.preferredLanguage);
544
- console.log(profile?.employeeId);
232
+ // With custom fields
233
+ interface MyProfile extends UserProfile { customField: string }
234
+ const { profile } = useUserProfile<MyProfile>();
545
235
  ```
546
236
 
547
237
  #### useGraphApi()
548
- Pre-configured Microsoft Graph API client.
549
238
 
550
239
  ```tsx
551
240
  const graph = useGraphApi();
552
241
 
553
- // GET request
554
- const user = await graph.get('/me');
555
-
556
- // POST request
557
- const message = await graph.post('/me/messages', {
558
- subject: 'Hello',
559
- body: { content: 'World' }
560
- });
561
-
562
- // PUT, PATCH, DELETE
563
- await graph.put('/me/photo/$value', photoBlob);
242
+ const user = await graph.get('/me');
243
+ const result = await graph.post('/me/messages', body);
244
+ await graph.put('/me/photo/$value', blob);
564
245
  await graph.patch('/me', { displayName: 'New Name' });
565
246
  await graph.delete('/me/messages/{id}');
566
-
567
- // Custom request with options
568
- const data = await graph.request('/me', { version: 'beta', scopes: ['User.Read'] });
247
+ const data = await graph.request('/me', { version: 'beta' });
569
248
  ```
570
249
 
571
250
  #### useRoles()
572
- Access user's Azure AD roles and groups.
573
251
 
574
252
  ```tsx
575
253
  const {
576
- roles, // Array of role names
577
- groups, // Array of group IDs
578
- loading, // Boolean: is loading?
579
- error, // Error object if failed
580
- hasRole, // Function: check single role
581
- hasGroup, // Function: check single group by ID
582
- hasAnyRole, // Function: check if user has any of the given roles
583
- hasAllRoles, // Function: check if user has all of the given roles
584
- refetch, // Function: refetch roles and groups
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>
585
263
  } = useRoles();
586
-
587
- if (hasRole('Admin')) {
588
- // Show admin content
589
- }
590
264
  ```
591
265
 
592
266
  #### useTokenRefresh()
593
- Monitor and control token refresh state.
267
+
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.
594
269
 
595
270
  ```tsx
596
- const {
597
- expiresIn, // Seconds until token expires (null if unknown)
598
- isExpiringSoon, // Boolean: token expiring within threshold
599
- refresh, // Function: manually trigger token refresh
600
- lastRefresh, // Date: when token was last refreshed
601
- } = useTokenRefresh({
602
- refreshBeforeExpiry: 300, // seconds before expiry to refresh
603
- scopes: ['User.Read'],
604
- onRefresh: (expiresIn) => console.log(`Refreshed, expires in ${expiresIn}s`),
605
- onError: (error) => console.error(error),
606
- });
271
+ useTokenRefresh(options?: UseTokenRefreshOptions): UseTokenRefreshReturn
607
272
  ```
608
273
 
609
- #### useMultiAccount()
610
- Manage multiple signed-in Microsoft accounts.
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 |
611
292
 
612
293
  ```tsx
613
- const {
614
- accounts, // All signed-in accounts
615
- activeAccount, // Currently active account
616
- hasMultipleAccounts, // Boolean: more than one account signed in
617
- accountCount, // Number of signed-in accounts
618
- inProgress, // Boolean: interaction in progress
619
- switchAccount, // Function: switch active account
620
- addAccount, // Function: sign in with another account
621
- removeAccount, // Function: remove account from cache
622
- signOutAccount, // Function: sign out a specific account
623
- signOutAll, // Function: sign out all accounts
624
- getAccountByUsername, // Function: find account by username
625
- getAccountById, // Function: find account by homeAccountId
626
- isActiveAccount, // Function: check if account is active
627
- } = useMultiAccount();
628
- ```
294
+ 'use client';
629
295
 
630
- ---
296
+ import { useTokenRefresh } from '@chemmangat/msal-next';
631
297
 
632
- ### Additional Components
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
+ });
633
306
 
634
- #### AccountSwitcher
635
- Pre-built UI for switching between multiple signed-in accounts.
307
+ if (isExpiringSoon) {
308
+ return (
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>}
313
+ </div>
314
+ );
315
+ }
316
+
317
+ return null;
318
+ }
319
+ ```
320
+
321
+ #### useMultiAccount()
322
+
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.
636
324
 
637
325
  ```tsx
638
- <AccountSwitcher
639
- showAvatars={true}
640
- maxAccounts={5}
641
- variant="default" // "default", "compact", "minimal"
642
- showAddButton={true}
643
- showRemoveButton={true}
644
- onSwitch={(account) => console.log('Switched to', account.name)}
645
- onAdd={() => console.log('Adding account')}
646
- onRemove={(account) => console.log('Removed', account.name)}
647
- />
326
+ useMultiAccount(defaultScopes?: string[]): UseMultiAccountReturn
648
327
  ```
649
328
 
650
- #### AccountList
651
- Display all signed-in accounts in a list.
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 |
652
346
 
653
347
  ```tsx
654
- <AccountList
655
- showAvatars={true}
656
- showDetails={true}
657
- showActiveIndicator={true}
658
- clickToSwitch={true}
659
- orientation="vertical" // or "horizontal"
660
- onAccountClick={(account) => console.log('Clicked', account.name)}
661
- />
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
+ }
662
395
  ```
663
396
 
664
397
  ---
@@ -666,99 +399,136 @@ Display all signed-in accounts in a list.
666
399
  ### Higher-Order Components
667
400
 
668
401
  #### withAuth
669
- Protect a component by wrapping it with `AuthGuard`.
670
402
 
671
403
  ```tsx
672
- const ProtectedPage = withAuth(MyPage);
673
-
674
- // With options
675
404
  const ProtectedPage = withAuth(MyPage, {
676
405
  loadingComponent: <Spinner />,
677
- fallbackComponent: <div>Please sign in</div>,
678
406
  scopes: ['User.Read'],
679
407
  });
680
408
  ```
681
409
 
682
410
  #### withPageAuth
683
- Add page-level auth protection with role support.
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.
684
413
 
685
414
  ```tsx
686
- const ProtectedDashboard = withPageAuth(Dashboard, {
687
- required: true,
688
- roles: ['Admin', 'Editor'],
689
- redirectTo: '/login',
690
- });
691
- export default ProtectedDashboard;
415
+ withPageAuth<P>(
416
+ Component: ComponentType<P>,
417
+ authConfig: PageAuthConfig,
418
+ globalConfig?: AuthProtectionConfig
419
+ ): ComponentType<P>
692
420
  ```
693
421
 
694
- ---
422
+ `PageAuthConfig` options:
695
423
 
696
- ### Automatic Token Refresh (NEW in v4.1.0)
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 |
697
432
 
698
- Prevent unexpected logouts by automatically refreshing tokens before they expire:
433
+ `AuthProtectionConfig` options (global defaults, third argument):
699
434
 
700
- ```tsx
701
- <MSALProvider
702
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
703
- autoRefreshToken={true} // Enable automatic refresh
704
- refreshBeforeExpiry={300} // Refresh 5 minutes before expiry
705
- >
706
- {children}
707
- </MSALProvider>
708
- ```
709
-
710
- **Monitor token expiry:**
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 |
711
441
 
712
442
  ```tsx
443
+ // app/dashboard/page.tsx
713
444
  'use client';
714
445
 
715
- import { useTokenRefresh } from '@chemmangat/msal-next';
446
+ import { withPageAuth } from '@chemmangat/msal-next';
716
447
 
717
- export default function SessionWarning() {
718
- const { expiresIn, isExpiringSoon, refresh, lastRefresh } = useTokenRefresh();
448
+ function Dashboard() {
449
+ return <div>Dashboard only admins and editors can see this</div>;
450
+ }
719
451
 
720
- if (isExpiringSoon) {
721
- return (
722
- <div className="warning">
723
- ⚠️ Your session will expire in {Math.floor((expiresIn ?? 0) / 60)} minutes
724
- <button onClick={refresh}>Refresh now</button>
725
- </div>
726
- );
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',
727
463
  }
464
+ );
465
+ ```
728
466
 
729
- return null;
730
- }
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
+ });
731
475
  ```
732
476
 
733
- ### Multi-Tenant Applications
477
+ #### ProtectedPage
734
478
 
735
- For apps that support any Azure AD tenant:
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.
736
480
 
737
481
  ```tsx
738
- <MSALProvider
739
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
740
- authorityType="common" // No tenantId needed
482
+ <ProtectedPage
483
+ config={PageAuthConfig}
484
+ defaultRedirectTo="/login"
485
+ defaultLoading={<Spinner />}
486
+ defaultUnauthorized={<div>Access denied</div>}
487
+ debug={false}
741
488
  >
742
489
  {children}
743
- </MSALProvider>
490
+ </ProtectedPage>
744
491
  ```
745
492
 
746
- ### Custom Scopes
493
+ Props (`ProtectedPageProps`):
747
494
 
748
- Request additional Microsoft Graph permissions:
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 |
749
503
 
750
504
  ```tsx
751
- <MSALProvider
752
- clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
753
- scopes={['User.Read', 'Mail.Read', 'Calendars.Read', 'Files.Read']}
754
- >
755
- {children}
756
- </MSALProvider>
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
+ }
757
525
  ```
758
526
 
759
- ### Server-Side Session
527
+ ---
528
+
529
+ ### Server Utilities
760
530
 
761
- Access authentication state in Server Components:
531
+ #### getServerSession
762
532
 
763
533
  ```tsx
764
534
  // app/profile/page.tsx (Server Component)
@@ -767,33 +537,48 @@ import { redirect } from 'next/navigation';
767
537
 
768
538
  export default async function ProfilePage() {
769
539
  const session = await getServerSession();
540
+ if (!session.isAuthenticated) redirect('/login');
541
+ return <div>Welcome, {session.username}</div>;
542
+ }
543
+ ```
770
544
 
771
- if (!session.isAuthenticated) {
772
- redirect('/login');
773
- }
545
+ ---
774
546
 
775
- return (
776
- <div>
777
- <h1>Profile</h1>
778
- <p>Welcome, {session.username}</p>
779
- </div>
780
- );
781
- }
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>
782
557
  ```
783
558
 
784
- ### Middleware Protection
559
+ `AuthMiddlewareConfig` options:
785
560
 
786
- 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 |
787
570
 
788
571
  ```tsx
789
572
  // middleware.ts
790
573
  import { createAuthMiddleware } from '@chemmangat/msal-next';
791
574
 
792
575
  export const middleware = createAuthMiddleware({
793
- protectedRoutes: ['/dashboard', '/profile', '/api/protected'],
794
- publicOnlyRoutes: ['/login'],
576
+ protectedRoutes: ['/dashboard', '/profile', '/settings', '/api/protected'],
577
+ publicOnlyRoutes: ['/login', '/signup'],
795
578
  loginPath: '/login',
796
- debug: true,
579
+ redirectAfterLogin: '/dashboard',
580
+ sessionCookie: 'msal.account',
581
+ debug: process.env.NODE_ENV === 'development',
797
582
  });
798
583
 
799
584
  export const config = {
@@ -801,179 +586,140 @@ export const config = {
801
586
  };
802
587
  ```
803
588
 
804
- ### Error Handling
805
-
806
- Use enhanced error handling for better debugging:
589
+ Custom `isAuthenticated` — use your own session store:
807
590
 
808
591
  ```tsx
809
- 'use client';
810
-
811
- import { useMsalAuth, wrapMsalError } from '@chemmangat/msal-next';
592
+ // middleware.ts
593
+ import { createAuthMiddleware } from '@chemmangat/msal-next';
594
+ import { verifySession } from './lib/session';
812
595
 
813
- export default function LoginPage() {
814
- const { loginRedirect } = useMsalAuth();
815
-
816
- const handleLogin = async () => {
817
- try {
818
- await loginRedirect();
819
- } catch (error) {
820
- const msalError = wrapMsalError(error);
821
-
822
- // Check if user cancelled (not a real error)
823
- if (msalError.isUserCancellation()) {
824
- return;
825
- }
826
-
827
- // Display actionable error message
828
- console.error(msalError.toConsoleString());
829
- }
830
- };
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
+ });
831
605
 
832
- return <button onClick={handleLogin}>Sign In</button>;
833
- }
606
+ export const config = {
607
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
608
+ };
834
609
  ```
835
610
 
836
- ### Custom Profile Fields
611
+ ---
837
612
 
838
- Extend UserProfile with organization-specific fields:
613
+ ## Common Patterns
614
+
615
+ ### Protect a page with AuthGuard
839
616
 
840
617
  ```tsx
841
618
  'use client';
842
619
 
843
- import { useUserProfile, UserProfile } from '@chemmangat/msal-next';
620
+ import { AuthGuard, useUserProfile } from '@chemmangat/msal-next';
844
621
 
845
- interface MyCompanyProfile extends UserProfile {
846
- customField: string;
622
+ export default function DashboardPage() {
623
+ return (
624
+ <AuthGuard>
625
+ <DashboardContent />
626
+ </AuthGuard>
627
+ );
847
628
  }
848
629
 
849
- export default function ProfilePage() {
850
- const { profile } = useUserProfile<MyCompanyProfile>();
851
-
630
+ function DashboardContent() {
631
+ const { profile, loading } = useUserProfile();
632
+ if (loading) return <div>Loading...</div>;
852
633
  return (
853
634
  <div>
854
- <p>Department: {profile?.department}</p>
855
- <p>Custom Field: {profile?.customField}</p>
635
+ <p>{profile?.displayName}</p>
636
+ <p>{profile?.mail}</p>
637
+ <p>{profile?.department}</p>
856
638
  </div>
857
639
  );
858
640
  }
859
641
  ```
860
642
 
861
- ---
862
-
863
- ## 🔧 Configuration Reference
864
-
865
- ### MSALProvider Props
643
+ ### Acquire a token for API calls
866
644
 
867
- | Prop | Type | Required | Default | Description |
868
- |------|------|----------|---------|-------------|
869
- | `clientId` | `string` | ✅ Yes | - | Azure AD Application (client) ID |
870
- | `tenantId` | `string` | No | - | Azure AD Directory (tenant) ID (for single-tenant) |
871
- | `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | No | `'common'` | Authority type |
872
- | `redirectUri` | `string` | No | `window.location.origin` | Redirect URI after authentication |
873
- | `postLogoutRedirectUri` | `string` | No | `redirectUri` | Redirect URI after logout |
874
- | `scopes` | `string[]` | No | `['User.Read']` | Default scopes |
875
- | `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | No | `'sessionStorage'` | Token cache location |
876
- | `enableLogging` | `boolean` | No | `false` | Enable debug logging |
877
- | `autoRefreshToken` | `boolean` | No | `false` | Automatically refresh tokens before expiry |
878
- | `refreshBeforeExpiry` | `number` | No | `300` | Seconds before expiry to refresh token |
879
- | `allowedRedirectUris` | `string[]` | No | - | Whitelist of allowed redirect URIs |
880
- | `protection` | `AuthProtectionConfig` | No | - | Zero-config protected routes configuration |
881
-
882
- ### Authority Types
883
-
884
- - **`common`** - Multi-tenant (any Azure AD tenant or Microsoft account)
885
- - **`organizations`** - Any organizational Azure AD tenant
886
- - **`consumers`** - Microsoft personal accounts only
887
- - **`tenant`** - Single-tenant (requires `tenantId`)
888
-
889
- ---
890
-
891
- ## 📖 Additional Resources
892
-
893
- ### Documentation
894
- - [SECURITY.md](./SECURITY.md) - **Security policy and best practices** ⭐
895
- - [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) - Common issues and solutions
896
- - [CHANGELOG.md](./CHANGELOG.md) - Version history
897
- - [MIGRATION_GUIDE_v3.md](./MIGRATION_GUIDE_v3.md) - Migrating from v2.x
898
- - [EXAMPLES_v4.0.2.md](./EXAMPLES_v4.0.2.md) - Code examples
899
-
900
- ### Security
901
- - 🔒 [Security Policy](./SECURITY.md) - Complete security documentation
902
- - 🛡️ [Best Practices](./SECURITY.md#-best-practices) - Security guidelines
903
- - ⚠️ [Common Mistakes](./SECURITY.md#-common-security-mistakes) - What to avoid
904
- - ✅ [Security Checklist](./SECURITY.md#-security-checklist) - Pre-deployment checklist
905
-
906
- ### Links
907
- - 📦 [npm Package](https://www.npmjs.com/package/@chemmangat/msal-next)
908
- - 🚀 [Live Demo](https://github.com/Chemmangat/msal-next-demo) - Sample implementation
909
- - 🐛 [Report Issues](https://github.com/chemmangat/msal-next/issues)
910
- - 💬 [Discussions](https://github.com/chemmangat/msal-next/discussions)
911
- - ⭐ [GitHub Repository](https://github.com/chemmangat/msal-next)
912
-
913
- ### Microsoft Resources
914
- - [Azure AD Documentation](https://learn.microsoft.com/en-us/azure/active-directory/)
915
- - [MSAL.js Documentation](https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-overview)
916
- - [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview)
917
-
918
- ---
645
+ ```tsx
646
+ 'use client';
919
647
 
920
- ## FAQ
648
+ import { useMsalAuth } from '@chemmangat/msal-next';
921
649
 
922
- **Q: Do I need to create an Azure AD app registration?**
923
- 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();
924
652
 
925
- **Q: Can I use this with Next.js Pages Router?**
926
- 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
+ ```
927
663
 
928
- **Q: Is this free to use?**
929
- A: Yes, the package is MIT licensed and free. Azure AD has a free tier for up to 50,000 users.
664
+ ### Error handling
930
665
 
931
- **Q: Can I use this for multi-tenant SaaS apps?**
932
- A: Yes! Set `authorityType="common"` and omit `tenantId`.
666
+ ```tsx
667
+ import { useMsalAuth, wrapMsalError } from '@chemmangat/msal-next';
933
668
 
934
- **Q: How do I get additional user information?**
935
- A: Use `useUserProfile()` hook which provides 30+ fields from Microsoft Graph.
669
+ const { loginRedirect } = useMsalAuth();
936
670
 
937
- **Q: Can I customize the sign-in button?**
938
- 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
+ ```
939
681
 
940
- **Q: Does this work with Azure AD B2C?**
941
- A: This package is designed for Azure AD. For B2C, you may need additional configuration.
682
+ ### Automatic token refresh
942
683
 
943
- **Q: How do I protect API routes?**
944
- 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
+ ```
945
693
 
946
694
  ---
947
695
 
948
- ## 🤝 Contributing
696
+ ## Troubleshooting
949
697
 
950
- Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
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`
951
702
 
952
- ---
953
-
954
- ## 📄 License
955
-
956
- MIT © [Chemmangat](https://github.com/chemmangat)
703
+ See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for more.
957
704
 
958
705
  ---
959
706
 
960
- ## 🙏 Acknowledgments
707
+ ## Additional Resources
961
708
 
962
- Built with:
963
- - [@azure/msal-browser](https://github.com/AzureAD/microsoft-authentication-library-for-js)
964
- - [@azure/msal-react](https://github.com/AzureAD/microsoft-authentication-library-for-js)
965
- - [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)
966
716
 
967
717
  ---
968
718
 
969
- ## 📊 Stats
719
+ ## Contributing
970
720
 
971
- - 📦 2,200+ weekly downloads
972
- - ⭐ Used in production by developers worldwide
973
- - 🔒 Security-focused with regular updates
974
- - 📚 Comprehensive documentation
975
- - 🎯 TypeScript-first with complete type safety
721
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md).
976
722
 
977
- ---
723
+ ## License
978
724
 
979
- **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)