@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 CHANGED
@@ -1,14 +1,29 @@
1
1
  # @chemmangat/msal-next
2
2
 
3
- Fully configurable MSAL (Microsoft Authentication Library) package for Next.js App Router with TypeScript support.
3
+ Production-grade MSAL authentication library for Next.js App Router with minimal boilerplate.
4
4
 
5
- ## 🚀 Quick Start
5
+ [![npm version](https://badge.fury.io/js/@chemmangat%2Fmsal-next.svg)](https://www.npmjs.com/package/@chemmangat/msal-next)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- > Supports both v3 and v4 of `@azure/msal-browser` and v2/v3 of `@azure/msal-react`
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="your-client-id">
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
- import { useMsalAuth } from '@chemmangat/msal-next';
50
+
51
+ import { MicrosoftSignInButton, useMsalAuth } from '@chemmangat/msal-next';
34
52
 
35
53
  export default function Home() {
36
- const { isAuthenticated, loginPopup } = useMsalAuth();
37
-
38
- if (!isAuthenticated) {
39
- return <button onClick={() => loginPopup()}>Sign In</button>;
54
+ const { isAuthenticated } = useMsalAuth();
55
+
56
+ if (isAuthenticated) {
57
+ return <div>Welcome! You're signed in.</div>;
40
58
  }
41
-
42
- return <div>Welcome!</div>;
59
+
60
+ return <MicrosoftSignInButton />;
43
61
  }
44
62
  ```
45
63
 
46
- ## 📚 Documentation
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
- ## 📖 API
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="required"
68
- tenantId="optional"
69
- authorityType="common" // 'common' | 'organizations' | 'consumers' | 'tenant'
70
- scopes={['User.Read']}
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-built button component with official Microsoft branding:
85
+ Pre-styled sign-in button with Microsoft branding.
99
86
 
100
87
  ```tsx
101
88
  <MicrosoftSignInButton
102
- variant="dark" // 'dark' | 'light'
103
- size="medium" // 'small' | 'medium' | 'large'
104
- text="Sign in with Microsoft"
105
- useRedirect={false}
106
- scopes={['User.Read']}
107
- onSuccess={() => console.log('Success!')}
108
- onError={(error) => console.error(error)}
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
- ### useMsalAuth Hook
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
- accounts,
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
- #### Return Values
214
+ ### useUserProfile
133
215
 
134
- | Property | Type | Description |
135
- |----------|------|-------------|
136
- | `isAuthenticated` | `boolean` | Whether user is authenticated |
137
- | `account` | `AccountInfo \| null` | Current authenticated account |
138
- | `accounts` | `AccountInfo[]` | All accounts in cache |
139
- | `inProgress` | `boolean` | Whether MSAL is performing an interaction |
140
- | `loginPopup` | `(scopes?: string[]) => Promise<void>` | Login using popup |
141
- | `loginRedirect` | `(scopes?: string[]) => Promise<void>` | Login using redirect |
142
- | `logoutPopup` | `() => Promise<void>` | Logout using popup |
143
- | `logoutRedirect` | `() => Promise<void>` | Logout using redirect |
144
- | `acquireToken` | `(scopes: string[]) => Promise<string>` | Acquire token silently with popup fallback |
145
- | `acquireTokenSilent` | `(scopes: string[]) => Promise<string>` | Acquire token silently only |
146
- | `acquireTokenPopup` | `(scopes: string[]) => Promise<string>` | Acquire token using popup |
147
- | `acquireTokenRedirect` | `(scopes: string[]) => Promise<void>` | Acquire token using redirect |
148
- | `clearSession` | `() => Promise<void>` | Clear MSAL cache without Microsoft logout |
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
- ### getMsalInstance()
233
+ ### useRoles
151
234
 
152
- Access the MSAL instance outside of React components:
235
+ Access user's Azure AD roles and groups.
153
236
 
154
237
  ```tsx
155
- import { getMsalInstance } from '@chemmangat/msal-next';
238
+ const { roles, groups, hasRole, hasAnyRole } = useRoles();
239
+
240
+ if (hasRole('Admin')) {
241
+ return <AdminPanel />;
242
+ }
156
243
 
157
- // In API clients, middleware, etc.
158
- const instance = getMsalInstance();
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
- ## 🔧 Advanced Usage
251
+ ## Utilities
165
252
 
166
- ### Using onInitialized for Axios Interceptors
253
+ ### withAuth
167
254
 
168
- Access the MSAL instance to set up API interceptors:
255
+ Higher-order component for protecting pages.
169
256
 
170
257
  ```tsx
171
- // app/layout.tsx
172
- 'use client';
173
- import { MsalAuthProvider } from '@chemmangat/msal-next';
174
- import { setupAxiosInterceptors } from '@/lib/axios';
258
+ const ProtectedPage = withAuth(MyPage, {
259
+ useRedirect: true,
260
+ scopes: ['User.Read']
261
+ });
175
262
 
176
- export default function RootLayout({ children }) {
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
- // lib/axios.ts
193
- import axios from 'axios';
194
- import { IPublicClientApplication } from '@azure/msal-browser';
195
-
196
- export function setupAxiosInterceptors(msalInstance: IPublicClientApplication) {
197
- axios.interceptors.request.use(async (config) => {
198
- const accounts = msalInstance.getAllAccounts();
199
- if (accounts.length > 0) {
200
- try {
201
- const response = await msalInstance.acquireTokenSilent({
202
- scopes: ['User.Read'],
203
- account: accounts[0],
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
- ### Using getMsalInstance() in API Clients
288
+ ### createAuthMiddleware
216
289
 
217
- For non-React code like API clients or middleware:
290
+ Protect routes at the edge with middleware.
218
291
 
219
292
  ```tsx
220
- // lib/api-client.ts
221
- import { getMsalInstance } from '@chemmangat/msal-next';
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
- export async function fetchUserData() {
224
- const instance = getMsalInstance();
225
- if (!instance) {
226
- throw new Error('MSAL not initialized');
227
- }
308
+ ### Retry Logic
309
+
310
+ Built-in exponential backoff for token acquisition.
228
311
 
229
- const accounts = instance.getAllAccounts();
230
- if (accounts.length === 0) {
231
- throw new Error('No authenticated user');
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
- const response = await instance.acquireTokenSilent({
235
- scopes: ['User.Read'],
236
- account: accounts[0],
237
- });
326
+ // Create a reusable retry wrapper
327
+ const acquireTokenWithRetry = createRetryWrapper(acquireToken, {
328
+ maxRetries: 3
329
+ });
330
+ ```
238
331
 
239
- return fetch('/api/user', {
240
- headers: {
241
- Authorization: `Bearer ${response.accessToken}`,
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
- ### Silent Logout with clearSession()
349
+ ## TypeScript Support
350
+
351
+ ### Custom Token Claims
248
352
 
249
- Clear MSAL cache without redirecting to Microsoft logout:
353
+ Extend the `CustomTokenClaims` interface for type-safe custom claims.
250
354
 
251
355
  ```tsx
252
- 'use client';
253
- import { useMsalAuth } from '@chemmangat/msal-next';
254
-
255
- export function LogoutButton() {
256
- const { clearSession } = useMsalAuth();
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
- ### SSR/SSG Notes
384
+ ### Custom Scopes
274
385
 
275
- This package is safe to use in server-rendered pages. The `MsalAuthProvider` automatically detects server-side rendering and skips MSAL initialization on the server, rendering the loading component instead. MSAL will initialize normally on the client side.
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
- // This works in both SSR and SSG pages
279
- export default function Page() {
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
- <MsalAuthProvider clientId="...">
282
- <YourApp />
283
- </MsalAuthProvider>
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
- ## 📄 License
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
- MIT © Chemmangat
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/)