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