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