@chemmangat/msal-next 1.2.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +440 -179
- package/dist/index.d.mts +589 -2
- package/dist/index.d.ts +589 -2
- package/dist/index.js +2 -425
- package/dist/index.mjs +2 -393
- package/dist/server.d.mts +53 -0
- package/dist/server.d.ts +53 -0
- package/dist/server.js +1 -0
- package/dist/server.mjs +1 -0
- package/package.json +19 -4
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
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,264 +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!')}
|
|
105
|
+
/>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### AuthGuard
|
|
109
|
+
|
|
110
|
+
Protect pages/components that require authentication.
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<AuthGuard
|
|
114
|
+
loadingComponent={<div>Loading...</div>}
|
|
115
|
+
fallbackComponent={<div>Redirecting to login...</div>}
|
|
116
|
+
>
|
|
117
|
+
<ProtectedContent />
|
|
118
|
+
</AuthGuard>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### UserAvatar
|
|
122
|
+
|
|
123
|
+
Display user photo from MS Graph with fallback initials.
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<UserAvatar
|
|
127
|
+
size={48}
|
|
128
|
+
showTooltip={true}
|
|
129
|
+
fallbackImage="/default-avatar.png"
|
|
130
|
+
/>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### AuthStatus
|
|
134
|
+
|
|
135
|
+
Show current authentication state.
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
<AuthStatus
|
|
139
|
+
showDetails={true}
|
|
140
|
+
renderAuthenticated={(username) => (
|
|
141
|
+
<div>Logged in as {username}</div>
|
|
142
|
+
)}
|
|
109
143
|
/>
|
|
110
144
|
```
|
|
111
145
|
|
|
112
|
-
###
|
|
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();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### useGraphApi
|
|
192
|
+
|
|
193
|
+
Pre-configured fetch wrapper for MS Graph API.
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const graph = useGraphApi();
|
|
197
|
+
|
|
198
|
+
// GET request
|
|
199
|
+
const user = await graph.get('/me');
|
|
200
|
+
|
|
201
|
+
// POST request
|
|
202
|
+
const message = await graph.post('/me/messages', {
|
|
203
|
+
subject: 'Hello',
|
|
204
|
+
body: { content: 'World' }
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Custom request
|
|
208
|
+
const data = await graph.request('/me/drive', {
|
|
209
|
+
scopes: ['Files.Read'],
|
|
210
|
+
version: 'beta'
|
|
211
|
+
});
|
|
130
212
|
```
|
|
131
213
|
|
|
132
|
-
|
|
214
|
+
### useUserProfile
|
|
133
215
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
216
|
+
Fetch and cache user profile from MS Graph.
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
const { profile, loading, error, refetch } = useUserProfile();
|
|
220
|
+
|
|
221
|
+
if (loading) return <div>Loading...</div>;
|
|
222
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div>
|
|
226
|
+
<h1>{profile.displayName}</h1>
|
|
227
|
+
<p>{profile.mail}</p>
|
|
228
|
+
<p>{profile.jobTitle}</p>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
```
|
|
149
232
|
|
|
150
|
-
###
|
|
233
|
+
### useRoles
|
|
151
234
|
|
|
152
|
-
Access
|
|
235
|
+
Access user's Azure AD roles and groups.
|
|
153
236
|
|
|
154
237
|
```tsx
|
|
155
|
-
|
|
238
|
+
const { roles, groups, hasRole, hasAnyRole } = useRoles();
|
|
239
|
+
|
|
240
|
+
if (hasRole('Admin')) {
|
|
241
|
+
return <AdminPanel />;
|
|
242
|
+
}
|
|
156
243
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (instance) {
|
|
160
|
-
const accounts = instance.getAllAccounts();
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
throw new Error('MSAL not initialized');
|
|
227
|
-
}
|
|
308
|
+
### Retry Logic
|
|
309
|
+
|
|
310
|
+
Built-in exponential backoff for token acquisition.
|
|
228
311
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
+
);
|
|
233
325
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
326
|
+
// Create a reusable retry wrapper
|
|
327
|
+
const acquireTokenWithRetry = createRetryWrapper(acquireToken, {
|
|
328
|
+
maxRetries: 3
|
|
329
|
+
});
|
|
330
|
+
```
|
|
238
331
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
332
|
+
### Debug Logger
|
|
333
|
+
|
|
334
|
+
Comprehensive logging for troubleshooting.
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
import { getDebugLogger } from '@chemmangat/msal-next';
|
|
338
|
+
|
|
339
|
+
const logger = getDebugLogger({
|
|
340
|
+
enabled: true,
|
|
341
|
+
level: 'debug',
|
|
342
|
+
showTimestamp: true
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
logger.info('User logged in', { username: 'user@example.com' });
|
|
346
|
+
logger.error('Authentication failed', { error });
|
|
245
347
|
```
|
|
246
348
|
|
|
247
|
-
|
|
349
|
+
## TypeScript Support
|
|
350
|
+
|
|
351
|
+
### Custom Token Claims
|
|
248
352
|
|
|
249
|
-
|
|
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!
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Advanced Examples
|
|
372
|
+
|
|
373
|
+
### Multi-Tenant Support
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
<MsalAuthProvider
|
|
377
|
+
clientId="YOUR_CLIENT_ID"
|
|
378
|
+
authorityType="common" // Supports any Azure AD tenant
|
|
379
|
+
>
|
|
380
|
+
{children}
|
|
381
|
+
</MsalAuthProvider>
|
|
271
382
|
```
|
|
272
383
|
|
|
273
|
-
###
|
|
384
|
+
### Custom Scopes
|
|
274
385
|
|
|
275
|
-
|
|
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
|
|
276
401
|
|
|
277
402
|
```tsx
|
|
278
|
-
|
|
279
|
-
|
|
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
|
+
|
|
280
427
|
return (
|
|
281
|
-
<
|
|
282
|
-
<
|
|
283
|
-
|
|
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>
|
|
284
436
|
);
|
|
285
437
|
}
|
|
286
438
|
```
|
|
287
439
|
|
|
288
|
-
|
|
440
|
+
### Role-Based Access Control
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
'use client';
|
|
444
|
+
|
|
445
|
+
import { useRoles, AuthGuard } from '@chemmangat/msal-next';
|
|
446
|
+
|
|
447
|
+
function AdminPanel() {
|
|
448
|
+
const { hasRole, hasAnyRole, hasAllRoles } = useRoles();
|
|
449
|
+
|
|
450
|
+
if (!hasRole('Admin')) {
|
|
451
|
+
return <div>Access denied</div>;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return <div>Admin content</div>;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export default function AdminPage() {
|
|
458
|
+
return (
|
|
459
|
+
<AuthGuard>
|
|
460
|
+
<AdminPanel />
|
|
461
|
+
</AuthGuard>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Configuration Options
|
|
467
|
+
|
|
468
|
+
### MsalAuthConfig
|
|
469
|
+
|
|
470
|
+
| Option | Type | Default | Description |
|
|
471
|
+
|--------|------|---------|-------------|
|
|
472
|
+
| `clientId` | `string` | **Required** | Azure AD Application (client) ID |
|
|
473
|
+
| `tenantId` | `string` | `undefined` | Azure AD Directory (tenant) ID |
|
|
474
|
+
| `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | `'common'` | Authority type |
|
|
475
|
+
| `redirectUri` | `string` | `window.location.origin` | Redirect URI after auth |
|
|
476
|
+
| `scopes` | `string[]` | `['User.Read']` | Default scopes |
|
|
477
|
+
| `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | `'sessionStorage'` | Token cache location |
|
|
478
|
+
| `enableLogging` | `boolean` | `false` | Enable debug logging |
|
|
479
|
+
|
|
480
|
+
## Troubleshooting
|
|
481
|
+
|
|
482
|
+
### Common Issues
|
|
483
|
+
|
|
484
|
+
**Issue**: "No active account" error
|
|
485
|
+
**Solution**: Ensure user is logged in before calling `acquireToken`
|
|
486
|
+
|
|
487
|
+
**Issue**: Token acquisition fails
|
|
488
|
+
**Solution**: Check that required scopes are granted in Azure AD
|
|
489
|
+
|
|
490
|
+
**Issue**: SSR hydration mismatch
|
|
491
|
+
**Solution**: Use `'use client'` directive for components using auth hooks
|
|
492
|
+
|
|
493
|
+
**Issue**: Middleware not protecting routes
|
|
494
|
+
**Solution**: Ensure session cookies are being set after login
|
|
495
|
+
|
|
496
|
+
### Debug Mode
|
|
497
|
+
|
|
498
|
+
Enable debug logging to troubleshoot issues:
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
<MsalAuthProvider
|
|
502
|
+
clientId="YOUR_CLIENT_ID"
|
|
503
|
+
enableLogging={true}
|
|
504
|
+
>
|
|
505
|
+
{children}
|
|
506
|
+
</MsalAuthProvider>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Migration Guide
|
|
510
|
+
|
|
511
|
+
### From v1.x to v2.x
|
|
512
|
+
|
|
513
|
+
v2.0 is backward compatible with v1.x. New features are additive:
|
|
514
|
+
|
|
515
|
+
```tsx
|
|
516
|
+
// v1.x - Still works!
|
|
517
|
+
import { MsalAuthProvider, useMsalAuth } from '@chemmangat/msal-next';
|
|
518
|
+
|
|
519
|
+
// v2.x - New features
|
|
520
|
+
import {
|
|
521
|
+
AuthGuard,
|
|
522
|
+
SignOutButton,
|
|
523
|
+
UserAvatar,
|
|
524
|
+
useGraphApi,
|
|
525
|
+
useUserProfile,
|
|
526
|
+
useRoles,
|
|
527
|
+
withAuth,
|
|
528
|
+
createAuthMiddleware,
|
|
529
|
+
} from '@chemmangat/msal-next';
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
## Contributing
|
|
533
|
+
|
|
534
|
+
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
535
|
+
|
|
536
|
+
## License
|
|
537
|
+
|
|
538
|
+
MIT © [Chemmangat](https://github.com/chemmangat)
|
|
539
|
+
|
|
540
|
+
## Support
|
|
541
|
+
|
|
542
|
+
- 📖 [Documentation](https://github.com/chemmangat/msal-next#readme)
|
|
543
|
+
- 🐛 [Issue Tracker](https://github.com/chemmangat/msal-next/issues)
|
|
544
|
+
- 💬 [Discussions](https://github.com/chemmangat/msal-next/discussions)
|
|
545
|
+
|
|
546
|
+
## Acknowledgments
|
|
289
547
|
|
|
290
|
-
|
|
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/)
|