@chemmangat/msal-next 4.0.2 โ 4.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/CHANGELOG.md +198 -0
- package/README.md +561 -723
- package/SECURITY.md +422 -110
- package/dist/index.d.mts +124 -5
- package/dist/index.d.ts +124 -5
- package/dist/index.js +2302 -43
- package/dist/index.mjs +2240 -43
- package/dist/server.js +89 -1
- package/dist/server.mjs +86 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,313 +4,231 @@ Production-grade MSAL authentication library for Next.js App Router with minimal
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@chemmangat/msal-next)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](./SECURITY.md)
|
|
7
8
|
|
|
8
|
-
> **๐ฆ Current Version: 4.0
|
|
9
|
+
> **๐ฆ Current Version: 4.1.0** - Production-ready with automatic token refresh and enhanced security
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## ๐ What's New in v4.0.2
|
|
13
|
-
|
|
14
|
-
### Complete TypeScript Types
|
|
15
|
-
```tsx
|
|
16
|
-
const { profile } = useUserProfile();
|
|
17
|
-
|
|
18
|
-
// Now available with full type safety!
|
|
19
|
-
console.log(profile?.department);
|
|
20
|
-
console.log(profile?.preferredLanguage);
|
|
21
|
-
console.log(profile?.employeeId);
|
|
22
|
-
console.log(profile?.companyName);
|
|
23
|
-
console.log(profile?.country);
|
|
24
|
-
// ... and 20+ more fields!
|
|
25
|
-
```
|
|
11
|
+
---
|
|
26
12
|
|
|
27
|
-
|
|
28
|
-
```tsx
|
|
29
|
-
// Before: Cryptic error
|
|
30
|
-
Error: AADSTS50011
|
|
13
|
+
## โญ Why Choose @chemmangat/msal-next?
|
|
31
14
|
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
### ๐ **5-Minute Setup**
|
|
16
|
+
Get Azure AD authentication running in your Next.js app in just 5 minutes. No complex configuration, no boilerplate.
|
|
34
17
|
|
|
35
|
-
|
|
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)
|
|
36
20
|
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
### ๐ฏ **Production-Ready**
|
|
22
|
+
Used by 2,200+ developers in production. Automatic token refresh prevents unexpected logouts. Complete TypeScript support.
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
2. Select your app โ Authentication
|
|
43
|
-
3. Under "Single-page application", add your redirect URI:
|
|
44
|
-
โข http://localhost:3000 (for development)
|
|
45
|
-
โข https://yourdomain.com (for production)
|
|
46
|
-
4. Click "Save"
|
|
24
|
+
### ๐ค **AI-Friendly**
|
|
25
|
+
Complete documentation optimized for AI assistants. Setup instructions that work on the first try.
|
|
47
26
|
|
|
48
|
-
|
|
27
|
+
### โก **Zero Boilerplate**
|
|
28
|
+
```tsx
|
|
29
|
+
<MSALProvider clientId="...">
|
|
30
|
+
<MicrosoftSignInButton />
|
|
31
|
+
</MSALProvider>
|
|
49
32
|
```
|
|
33
|
+
That's it. You're done.
|
|
50
34
|
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
// Development mode automatically checks for:
|
|
54
|
-
โ ๏ธ Warnings (should fix)
|
|
35
|
+
---
|
|
55
36
|
|
|
56
|
-
|
|
57
|
-
Client ID appears to be a placeholder
|
|
37
|
+
## ๐ฏ Top Features
|
|
58
38
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 |
|
|
65
49
|
|
|
66
50
|
---
|
|
67
51
|
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
### โ Mistake #1: 'use client' in the wrong place
|
|
52
|
+
## ๐ Security First
|
|
71
53
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
75
61
|
|
|
76
|
-
|
|
62
|
+
**[Read Complete Security Policy โ](./SECURITY.md)**
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
const { isAuthenticated } = useMsalAuth();
|
|
80
|
-
// ...
|
|
81
|
-
}
|
|
82
|
-
```
|
|
64
|
+
---
|
|
83
65
|
|
|
84
|
-
|
|
85
|
-
// CORRECT - 'use client' comes FIRST
|
|
86
|
-
'use client';
|
|
66
|
+
## ๐ Quick Start (5 Minutes)
|
|
87
67
|
|
|
88
|
-
|
|
68
|
+
### Step 1: Install the Package
|
|
89
69
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// ...
|
|
93
|
-
}
|
|
70
|
+
```bash
|
|
71
|
+
npm install @chemmangat/msal-next @azure/msal-browser @azure/msal-react
|
|
94
72
|
```
|
|
95
73
|
|
|
96
|
-
###
|
|
74
|
+
### Step 2: Get Your Azure AD Credentials
|
|
97
75
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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**
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
return <MsalAuthProvider>{children}</MsalAuthProvider>;
|
|
104
|
-
}
|
|
105
|
-
```
|
|
85
|
+
### Step 3: Configure Environment Variables
|
|
106
86
|
|
|
107
|
-
|
|
108
|
-
// CORRECT - Use MSALProvider instead
|
|
109
|
-
import { MSALProvider } from '@chemmangat/msal-next';
|
|
87
|
+
Create `.env.local` in your project root:
|
|
110
88
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
114
93
|
```
|
|
115
94
|
|
|
116
|
-
|
|
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)
|
|
117
99
|
|
|
118
|
-
|
|
119
|
-
// WRONG - Placeholder values
|
|
120
|
-
<MSALProvider
|
|
121
|
-
clientId="your-client-id-here"
|
|
122
|
-
tenantId="your-tenant-id-here"
|
|
123
|
-
>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
```tsx
|
|
127
|
-
// CORRECT - Actual GUIDs from Azure Portal
|
|
128
|
-
<MSALProvider
|
|
129
|
-
clientId="12345678-1234-1234-1234-123456789012"
|
|
130
|
-
tenantId="87654321-4321-4321-4321-210987654321"
|
|
131
|
-
>
|
|
132
|
-
```
|
|
100
|
+
**Security Note:** All authentication happens in the browser. Your tokens never touch your Next.js server. [Learn more โ](./SECURITY.md)
|
|
133
101
|
|
|
134
|
-
###
|
|
102
|
+
### Step 4: Add Provider to Layout
|
|
135
103
|
|
|
136
104
|
```tsx
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
```
|
|
105
|
+
// app/layout.tsx (Server Component - no 'use client' needed!)
|
|
106
|
+
import { MSALProvider } from '@chemmangat/msal-next';
|
|
140
107
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
108
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
109
|
+
return (
|
|
110
|
+
<html lang="en">
|
|
111
|
+
<body>
|
|
112
|
+
<MSALProvider
|
|
113
|
+
clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
|
|
114
|
+
tenantId={process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID!}
|
|
115
|
+
>
|
|
116
|
+
{children}
|
|
117
|
+
</MSALProvider>
|
|
118
|
+
</body>
|
|
119
|
+
</html>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
147
122
|
```
|
|
148
123
|
|
|
149
|
-
|
|
150
|
-
# .env.local
|
|
151
|
-
NEXT_PUBLIC_AZURE_AD_CLIENT_ID=12345678-1234-1234-1234-123456789012
|
|
152
|
-
NEXT_PUBLIC_AZURE_AD_TENANT_ID=87654321-4321-4321-4321-210987654321
|
|
153
|
-
```
|
|
124
|
+
**Note:** MSALProvider is already marked as 'use client' internally. You don't need to add 'use client' to your layout.tsx!
|
|
154
125
|
|
|
155
|
-
###
|
|
126
|
+
### Step 5: Add Sign-In Button
|
|
156
127
|
|
|
157
128
|
```tsx
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// CORRECT - HTTPS in production, HTTP only for localhost
|
|
162
|
-
redirectUri: process.env.NODE_ENV === 'production'
|
|
163
|
-
? "https://myapp.com"
|
|
164
|
-
: "http://localhost:3000"
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
## ๐ What's New in v4.0.1
|
|
129
|
+
// app/page.tsx
|
|
130
|
+
'use client';
|
|
170
131
|
|
|
171
|
-
|
|
132
|
+
import { MicrosoftSignInButton, useMsalAuth } from '@chemmangat/msal-next';
|
|
172
133
|
|
|
173
|
-
|
|
134
|
+
export default function HomePage() {
|
|
135
|
+
const { isAuthenticated, account } = useMsalAuth();
|
|
174
136
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
}
|
|
178
145
|
|
|
179
|
-
|
|
180
|
-
|
|
146
|
+
return (
|
|
147
|
+
<div>
|
|
148
|
+
<h1>Welcome to My App</h1>
|
|
149
|
+
<MicrosoftSignInButton />
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
181
152
|
}
|
|
182
153
|
```
|
|
183
154
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| Before (v3.x) | After (v4.0) |
|
|
187
|
-
|---------------|--------------|
|
|
188
|
-
| 50+ lines of middleware | 1 line |
|
|
189
|
-
| Manual redirect logic | Automatic |
|
|
190
|
-
| Boilerplate in every page | Zero boilerplate |
|
|
191
|
-
| 30 min setup | 30 sec setup |
|
|
192
|
-
|
|
193
|
-
### More Examples
|
|
155
|
+
### Step 6: Run Your App
|
|
194
156
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
export const auth = {
|
|
198
|
-
required: true,
|
|
199
|
-
roles: ['admin', 'editor']
|
|
200
|
-
};
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
**Custom Validation:**
|
|
204
|
-
```tsx
|
|
205
|
-
export const auth = {
|
|
206
|
-
required: true,
|
|
207
|
-
validate: (account) => account.username.endsWith('@company.com')
|
|
208
|
-
};
|
|
157
|
+
```bash
|
|
158
|
+
npm run dev
|
|
209
159
|
```
|
|
210
160
|
|
|
211
|
-
|
|
212
|
-
```tsx
|
|
213
|
-
export const auth = {
|
|
214
|
-
required: true,
|
|
215
|
-
loading: <Spinner />,
|
|
216
|
-
unauthorized: <AccessDenied />
|
|
217
|
-
};
|
|
218
|
-
```
|
|
161
|
+
Visit `http://localhost:3000` and click the sign-in button!
|
|
219
162
|
|
|
220
163
|
---
|
|
221
164
|
|
|
222
|
-
##
|
|
223
|
-
|
|
224
|
-
โจ **Zero-Config Protection** - One line to protect any route
|
|
225
|
-
๐ฏ **Role-Based Access** - Built-in Azure AD role checking
|
|
226
|
-
๐ **Custom Validation** - Add your own auth logic
|
|
227
|
-
โก **Automatic Redirects** - Smart return URL handling
|
|
228
|
-
๐จ **Custom UI** - Override loading/unauthorized states
|
|
229
|
-
๐ฆ **TypeScript First** - Full type safety
|
|
230
|
-
๐ **Next.js 14+** - Built for App Router
|
|
165
|
+
## ๐ Complete Setup Guide for AI Assistants
|
|
231
166
|
|
|
232
|
-
|
|
167
|
+
When implementing MSAL authentication in a Next.js project, follow these steps:
|
|
233
168
|
|
|
234
|
-
|
|
169
|
+
### 1. Installation
|
|
235
170
|
|
|
236
|
-
### ๐ CLI Tool (NEW)
|
|
237
171
|
```bash
|
|
238
|
-
|
|
239
|
-
npx @chemmangat/msal-next init
|
|
172
|
+
npm install @chemmangat/msal-next @azure/msal-browser@^4.0.0 @azure/msal-react@^3.0.0
|
|
240
173
|
```
|
|
241
174
|
|
|
242
|
-
|
|
243
|
-
- Detects your Next.js structure (App Router/Pages Router)
|
|
244
|
-
- Installs dependencies
|
|
245
|
-
- Creates configuration files
|
|
246
|
-
- Generates example pages
|
|
247
|
-
- Sets up middleware
|
|
175
|
+
### 2. Azure AD Configuration
|
|
248
176
|
|
|
249
|
-
**
|
|
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)
|
|
250
182
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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`
|
|
254
186
|
|
|
255
|
-
|
|
256
|
-
enabled: true,
|
|
257
|
-
enablePerformance: true, // Track operation timing
|
|
258
|
-
enableNetworkLogs: true, // Log all requests/responses
|
|
259
|
-
});
|
|
187
|
+
### 3. Environment Variables
|
|
260
188
|
|
|
261
|
-
|
|
262
|
-
logger.startTiming('token-acquisition');
|
|
263
|
-
await acquireToken(['User.Read']);
|
|
264
|
-
logger.endTiming('token-acquisition'); // Logs: "โฑ๏ธ Completed: token-acquisition (45ms)"
|
|
189
|
+
Create `.env.local`:
|
|
265
190
|
|
|
266
|
-
|
|
267
|
-
|
|
191
|
+
```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
|
|
268
194
|
```
|
|
269
195
|
|
|
270
|
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
- Requires Node.js 18+ (was 16+)
|
|
276
|
-
- Requires Next.js 14.1+ (was 14.0+)
|
|
277
|
-
- Requires @azure/msal-browser v4+ (was v3+)
|
|
278
|
-
- Removed `ServerSession.accessToken` (use client-side `acquireToken()`)
|
|
279
|
-
|
|
280
|
-
[See Migration Guide](./MIGRATION_GUIDE_v3.md) for details.
|
|
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
|
|
281
201
|
|
|
282
|
-
|
|
202
|
+
### 4. Project Structure
|
|
283
203
|
|
|
284
|
-
### Option 1: CLI Setup (Recommended)
|
|
285
|
-
```bash
|
|
286
|
-
# Create Next.js app
|
|
287
|
-
npx create-next-app@latest my-app
|
|
288
|
-
cd my-app
|
|
289
|
-
|
|
290
|
-
# Initialize MSAL
|
|
291
|
-
npx @chemmangat/msal-next init
|
|
292
204
|
```
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
297
213
|
```
|
|
298
214
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
> **Important:** Use `MSALProvider` (not `MsalAuthProvider`) in your layout.tsx to avoid the "createContext only works in Client Components" error.
|
|
215
|
+
### 5. Implementation Files
|
|
302
216
|
|
|
303
|
-
|
|
217
|
+
**File 1: `app/layout.tsx` (Server Component)**
|
|
304
218
|
|
|
305
219
|
```tsx
|
|
306
|
-
// app/layout.tsx
|
|
307
220
|
import { MSALProvider } from '@chemmangat/msal-next';
|
|
221
|
+
import './globals.css';
|
|
308
222
|
|
|
309
|
-
export default function RootLayout({
|
|
223
|
+
export default function RootLayout({
|
|
224
|
+
children,
|
|
225
|
+
}: {
|
|
226
|
+
children: React.ReactNode;
|
|
227
|
+
}) {
|
|
310
228
|
return (
|
|
311
|
-
<html>
|
|
229
|
+
<html lang="en">
|
|
312
230
|
<body>
|
|
313
|
-
<MSALProvider
|
|
231
|
+
<MSALProvider
|
|
314
232
|
clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
|
|
315
233
|
tenantId={process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID!}
|
|
316
234
|
>
|
|
@@ -322,94 +240,237 @@ export default function RootLayout({ children }) {
|
|
|
322
240
|
}
|
|
323
241
|
```
|
|
324
242
|
|
|
325
|
-
|
|
243
|
+
**File 2: `app/page.tsx` (Client Component)**
|
|
326
244
|
|
|
327
245
|
```tsx
|
|
328
|
-
// app/page.tsx
|
|
329
246
|
'use client';
|
|
330
247
|
|
|
331
|
-
import { MicrosoftSignInButton, useMsalAuth } from '@chemmangat/msal-next';
|
|
248
|
+
import { MicrosoftSignInButton, SignOutButton, useMsalAuth } from '@chemmangat/msal-next';
|
|
332
249
|
|
|
333
|
-
export default function
|
|
334
|
-
const { isAuthenticated } = useMsalAuth();
|
|
250
|
+
export default function HomePage() {
|
|
251
|
+
const { isAuthenticated, account } = useMsalAuth();
|
|
335
252
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
+
);
|
|
341
271
|
}
|
|
342
272
|
```
|
|
343
273
|
|
|
344
|
-
|
|
274
|
+
**File 3: `app/dashboard/page.tsx` (Protected Page)**
|
|
345
275
|
|
|
346
|
-
|
|
276
|
+
```tsx
|
|
277
|
+
'use client';
|
|
347
278
|
|
|
348
|
-
|
|
279
|
+
import { AuthGuard, useUserProfile } from '@chemmangat/msal-next';
|
|
349
280
|
|
|
350
|
-
|
|
281
|
+
export default function DashboardPage() {
|
|
282
|
+
return (
|
|
283
|
+
<AuthGuard>
|
|
284
|
+
<DashboardContent />
|
|
285
|
+
</AuthGuard>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
351
288
|
|
|
352
|
-
|
|
289
|
+
function DashboardContent() {
|
|
290
|
+
const { profile, loading } = useUserProfile();
|
|
353
291
|
|
|
354
|
-
|
|
355
|
-
// app/layout.tsx (Server Component)
|
|
356
|
-
import { MSALProvider } from '@chemmangat/msal-next';
|
|
292
|
+
if (loading) return <div>Loading profile...</div>;
|
|
357
293
|
|
|
358
|
-
export default function RootLayout({ children }) {
|
|
359
294
|
return (
|
|
360
|
-
<
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
>
|
|
368
|
-
{children}
|
|
369
|
-
</MSALProvider>
|
|
370
|
-
</body>
|
|
371
|
-
</html>
|
|
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>
|
|
372
302
|
);
|
|
373
303
|
}
|
|
374
304
|
```
|
|
375
305
|
|
|
376
|
-
###
|
|
306
|
+
### 6. Common Patterns
|
|
377
307
|
|
|
378
|
-
|
|
308
|
+
**Pattern 1: Check Authentication Status**
|
|
379
309
|
|
|
380
310
|
```tsx
|
|
381
|
-
|
|
382
|
-
|
|
311
|
+
'use client';
|
|
312
|
+
|
|
313
|
+
import { useMsalAuth } from '@chemmangat/msal-next';
|
|
314
|
+
|
|
315
|
+
export default function MyComponent() {
|
|
316
|
+
const { isAuthenticated, account, inProgress } = useMsalAuth();
|
|
383
317
|
|
|
384
|
-
|
|
318
|
+
if (inProgress) return <div>Loading...</div>;
|
|
319
|
+
if (!isAuthenticated) return <div>Please sign in</div>;
|
|
385
320
|
|
|
386
|
-
|
|
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() {
|
|
387
370
|
return (
|
|
388
|
-
<
|
|
389
|
-
|
|
390
|
-
|
|
371
|
+
<AuthGuard
|
|
372
|
+
loadingComponent={<div>Checking authentication...</div>}
|
|
373
|
+
fallbackComponent={<div>Redirecting to login...</div>}
|
|
391
374
|
>
|
|
392
|
-
|
|
393
|
-
</
|
|
375
|
+
<div>This content is protected</div>
|
|
376
|
+
</AuthGuard>
|
|
394
377
|
);
|
|
395
378
|
}
|
|
396
379
|
```
|
|
397
380
|
|
|
398
|
-
###
|
|
381
|
+
### 7. Configuration Options
|
|
399
382
|
|
|
400
|
-
|
|
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>
|
|
412
|
+
```
|
|
413
|
+
|
|
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
|
|
436
|
+
|
|
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)
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## ๐ API Reference
|
|
449
|
+
|
|
450
|
+
### Components
|
|
451
|
+
|
|
452
|
+
#### MSALProvider
|
|
453
|
+
Wrap your app to provide authentication context.
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
<MSALProvider clientId="..." tenantId="...">
|
|
457
|
+
{children}
|
|
458
|
+
</MSALProvider>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### MicrosoftSignInButton
|
|
462
|
+
Pre-styled sign-in button with Microsoft branding.
|
|
401
463
|
|
|
402
464
|
```tsx
|
|
403
465
|
<MicrosoftSignInButton
|
|
404
466
|
variant="dark" // or "light"
|
|
405
|
-
size="medium"
|
|
467
|
+
size="medium" // "small", "medium", "large"
|
|
406
468
|
onSuccess={() => console.log('Signed in!')}
|
|
407
469
|
/>
|
|
408
470
|
```
|
|
409
471
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
Pre-styled sign-out button matching the sign-in button style.
|
|
472
|
+
#### SignOutButton
|
|
473
|
+
Pre-styled sign-out button.
|
|
413
474
|
|
|
414
475
|
```tsx
|
|
415
476
|
<SignOutButton
|
|
@@ -419,22 +480,20 @@ Pre-styled sign-out button matching the sign-in button style.
|
|
|
419
480
|
/>
|
|
420
481
|
```
|
|
421
482
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
Protect pages/components that require authentication.
|
|
483
|
+
#### AuthGuard
|
|
484
|
+
Protect components that require authentication.
|
|
425
485
|
|
|
426
486
|
```tsx
|
|
427
487
|
<AuthGuard
|
|
428
488
|
loadingComponent={<div>Loading...</div>}
|
|
429
|
-
fallbackComponent={<div>
|
|
489
|
+
fallbackComponent={<div>Please sign in</div>}
|
|
430
490
|
>
|
|
431
491
|
<ProtectedContent />
|
|
432
492
|
</AuthGuard>
|
|
433
493
|
```
|
|
434
494
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
Display user photo from MS Graph with fallback initials.
|
|
495
|
+
#### UserAvatar
|
|
496
|
+
Display user photo from Microsoft Graph.
|
|
438
497
|
|
|
439
498
|
```tsx
|
|
440
499
|
<UserAvatar
|
|
@@ -444,65 +503,44 @@ Display user photo from MS Graph with fallback initials.
|
|
|
444
503
|
/>
|
|
445
504
|
```
|
|
446
505
|
|
|
447
|
-
###
|
|
506
|
+
### Hooks
|
|
448
507
|
|
|
449
|
-
|
|
508
|
+
#### useMsalAuth()
|
|
509
|
+
Main authentication hook.
|
|
450
510
|
|
|
451
511
|
```tsx
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
Catch and handle authentication errors gracefully.
|
|
463
|
-
|
|
464
|
-
```tsx
|
|
465
|
-
<ErrorBoundary
|
|
466
|
-
fallback={(error, reset) => (
|
|
467
|
-
<div>
|
|
468
|
-
<p>Error: {error.message}</p>
|
|
469
|
-
<button onClick={reset}>Try Again</button>
|
|
470
|
-
</div>
|
|
471
|
-
)}
|
|
472
|
-
>
|
|
473
|
-
<App />
|
|
474
|
-
</ErrorBoundary>
|
|
512
|
+
const {
|
|
513
|
+
account, // Current user account
|
|
514
|
+
accounts, // All cached accounts
|
|
515
|
+
isAuthenticated, // Boolean: is user signed in?
|
|
516
|
+
inProgress, // Boolean: is auth in progress?
|
|
517
|
+
loginRedirect, // Function: sign in
|
|
518
|
+
logoutRedirect, // Function: sign out
|
|
519
|
+
acquireToken, // Function: get access token
|
|
520
|
+
} = useMsalAuth();
|
|
475
521
|
```
|
|
476
522
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
### useMsalAuth
|
|
480
|
-
|
|
481
|
-
Main authentication hook with all auth operations.
|
|
523
|
+
#### useUserProfile()
|
|
524
|
+
Fetch user profile from Microsoft Graph.
|
|
482
525
|
|
|
483
526
|
```tsx
|
|
484
527
|
const {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const token = await acquireToken(['User.Read']);
|
|
498
|
-
|
|
499
|
-
// Logout (redirects to Microsoft)
|
|
500
|
-
await logoutRedirect();
|
|
528
|
+
profile, // User profile with 30+ fields
|
|
529
|
+
loading, // Boolean: is loading?
|
|
530
|
+
error, // Error object if failed
|
|
531
|
+
refetch, // Function: refetch profile
|
|
532
|
+
clearCache, // Function: clear cached profile
|
|
533
|
+
} = useUserProfile();
|
|
534
|
+
|
|
535
|
+
// Access profile fields
|
|
536
|
+
console.log(profile?.displayName);
|
|
537
|
+
console.log(profile?.department);
|
|
538
|
+
console.log(profile?.preferredLanguage);
|
|
539
|
+
console.log(profile?.employeeId);
|
|
501
540
|
```
|
|
502
541
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
Pre-configured fetch wrapper for MS Graph API.
|
|
542
|
+
#### useGraphApi()
|
|
543
|
+
Pre-configured Microsoft Graph API client.
|
|
506
544
|
|
|
507
545
|
```tsx
|
|
508
546
|
const graph = useGraphApi();
|
|
@@ -515,112 +553,119 @@ const message = await graph.post('/me/messages', {
|
|
|
515
553
|
subject: 'Hello',
|
|
516
554
|
body: { content: 'World' }
|
|
517
555
|
});
|
|
518
|
-
|
|
519
|
-
// Custom request
|
|
520
|
-
const data = await graph.request('/me/drive', {
|
|
521
|
-
scopes: ['Files.Read'],
|
|
522
|
-
version: 'beta'
|
|
523
|
-
});
|
|
524
556
|
```
|
|
525
557
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
Fetch and cache user profile from MS Graph with complete TypeScript types.
|
|
558
|
+
#### useRoles()
|
|
559
|
+
Access user's Azure AD roles.
|
|
529
560
|
|
|
530
561
|
```tsx
|
|
531
|
-
const {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
<h1>{profile.displayName}</h1>
|
|
539
|
-
<p>{profile.mail}</p>
|
|
540
|
-
<p>{profile.jobTitle}</p>
|
|
541
|
-
<p>{profile.department}</p>
|
|
542
|
-
<p>{profile.preferredLanguage}</p>
|
|
543
|
-
<p>{profile.employeeId}</p>
|
|
544
|
-
<p>{profile.companyName}</p>
|
|
545
|
-
<p>{profile.officeLocation}</p>
|
|
546
|
-
</div>
|
|
547
|
-
);
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
**New in v4.0.2:** Complete TypeScript types with 30+ fields from Microsoft Graph:
|
|
551
|
-
- `department`, `preferredLanguage`, `employeeId`, `companyName`
|
|
552
|
-
- `country`, `city`, `state`, `streetAddress`, `postalCode`
|
|
553
|
-
- `manager`, `aboutMe`, `birthday`, `interests`, `skills`
|
|
554
|
-
- And many more!
|
|
555
|
-
|
|
556
|
-
**Generic type support:**
|
|
557
|
-
```tsx
|
|
558
|
-
interface MyProfile extends UserProfile {
|
|
559
|
-
customField: string;
|
|
560
|
-
}
|
|
562
|
+
const {
|
|
563
|
+
roles, // Array of role names
|
|
564
|
+
groups, // Array of group IDs
|
|
565
|
+
hasRole, // Function: check single role
|
|
566
|
+
hasAnyRole, // Function: check multiple roles
|
|
567
|
+
hasAllRoles, // Function: check all roles
|
|
568
|
+
} = useRoles();
|
|
561
569
|
|
|
562
|
-
|
|
563
|
-
|
|
570
|
+
if (hasRole('Admin')) {
|
|
571
|
+
// Show admin content
|
|
572
|
+
}
|
|
564
573
|
```
|
|
565
574
|
|
|
566
|
-
|
|
575
|
+
---
|
|
567
576
|
|
|
568
|
-
|
|
577
|
+
## ๐ Advanced Usage
|
|
578
|
+
|
|
579
|
+
### Automatic Token Refresh (NEW in v4.1.0)
|
|
580
|
+
|
|
581
|
+
Prevent unexpected logouts by automatically refreshing tokens before they expire:
|
|
569
582
|
|
|
570
583
|
```tsx
|
|
571
|
-
|
|
584
|
+
<MSALProvider
|
|
585
|
+
clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
|
|
586
|
+
autoRefreshToken={true} // Enable automatic refresh
|
|
587
|
+
refreshBeforeExpiry={300} // Refresh 5 minutes before expiry
|
|
588
|
+
>
|
|
589
|
+
{children}
|
|
590
|
+
</MSALProvider>
|
|
591
|
+
```
|
|
572
592
|
|
|
573
|
-
|
|
574
|
-
return <AdminPanel />;
|
|
575
|
-
}
|
|
593
|
+
**Monitor token expiry:**
|
|
576
594
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
595
|
+
```tsx
|
|
596
|
+
'use client';
|
|
580
597
|
|
|
581
|
-
|
|
582
|
-
|
|
598
|
+
import { useTokenRefresh } from '@chemmangat/msal-next';
|
|
599
|
+
|
|
600
|
+
export default function SessionWarning() {
|
|
601
|
+
const { expiresIn, isExpiringSoon } = useTokenRefresh();
|
|
602
|
+
|
|
603
|
+
if (isExpiringSoon) {
|
|
604
|
+
return (
|
|
605
|
+
<div className="warning">
|
|
606
|
+
โ ๏ธ Your session will expire in {Math.floor(expiresIn / 60)} minutes
|
|
607
|
+
</div>
|
|
608
|
+
);
|
|
609
|
+
}
|
|
583
610
|
|
|
584
|
-
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
```
|
|
585
614
|
|
|
586
|
-
###
|
|
615
|
+
### Multi-Tenant Applications
|
|
587
616
|
|
|
588
|
-
|
|
617
|
+
For apps that support any Azure AD tenant:
|
|
589
618
|
|
|
590
619
|
```tsx
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
620
|
+
<MSALProvider
|
|
621
|
+
clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
|
|
622
|
+
authorityType="common" // No tenantId needed
|
|
623
|
+
>
|
|
624
|
+
{children}
|
|
625
|
+
</MSALProvider>
|
|
597
626
|
```
|
|
598
627
|
|
|
599
|
-
###
|
|
628
|
+
### Custom Scopes
|
|
600
629
|
|
|
601
|
-
|
|
630
|
+
Request additional Microsoft Graph permissions:
|
|
602
631
|
|
|
603
|
-
|
|
632
|
+
```tsx
|
|
633
|
+
<MSALProvider
|
|
634
|
+
clientId={process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID!}
|
|
635
|
+
scopes={['User.Read', 'Mail.Read', 'Calendars.Read', 'Files.Read']}
|
|
636
|
+
>
|
|
637
|
+
{children}
|
|
638
|
+
</MSALProvider>
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Server-Side Session
|
|
642
|
+
|
|
643
|
+
Access authentication state in Server Components:
|
|
604
644
|
|
|
605
645
|
```tsx
|
|
606
|
-
// app/
|
|
646
|
+
// app/profile/page.tsx (Server Component)
|
|
607
647
|
import { getServerSession } from '@chemmangat/msal-next/server';
|
|
608
648
|
import { redirect } from 'next/navigation';
|
|
609
649
|
|
|
610
|
-
export default async function
|
|
650
|
+
export default async function ProfilePage() {
|
|
611
651
|
const session = await getServerSession();
|
|
612
652
|
|
|
613
653
|
if (!session.isAuthenticated) {
|
|
614
654
|
redirect('/login');
|
|
615
655
|
}
|
|
616
656
|
|
|
617
|
-
return
|
|
657
|
+
return (
|
|
658
|
+
<div>
|
|
659
|
+
<h1>Profile</h1>
|
|
660
|
+
<p>Welcome, {session.username}</p>
|
|
661
|
+
</div>
|
|
662
|
+
);
|
|
618
663
|
}
|
|
619
664
|
```
|
|
620
665
|
|
|
621
|
-
###
|
|
666
|
+
### Middleware Protection
|
|
622
667
|
|
|
623
|
-
Protect routes at the edge
|
|
668
|
+
Protect routes at the edge:
|
|
624
669
|
|
|
625
670
|
```tsx
|
|
626
671
|
// middleware.ts
|
|
@@ -638,380 +683,173 @@ export const config = {
|
|
|
638
683
|
};
|
|
639
684
|
```
|
|
640
685
|
|
|
641
|
-
###
|
|
686
|
+
### Error Handling
|
|
642
687
|
|
|
643
|
-
|
|
688
|
+
Use enhanced error handling for better debugging:
|
|
644
689
|
|
|
645
690
|
```tsx
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
// Wrap any async function with retry logic
|
|
649
|
-
const token = await retryWithBackoff(
|
|
650
|
-
() => acquireToken(['User.Read']),
|
|
651
|
-
{
|
|
652
|
-
maxRetries: 3,
|
|
653
|
-
initialDelay: 1000,
|
|
654
|
-
backoffMultiplier: 2,
|
|
655
|
-
debug: true
|
|
656
|
-
}
|
|
657
|
-
);
|
|
658
|
-
|
|
659
|
-
// Create a reusable retry wrapper
|
|
660
|
-
const acquireTokenWithRetry = createRetryWrapper(acquireToken, {
|
|
661
|
-
maxRetries: 3
|
|
662
|
-
});
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
### Debug Logger
|
|
666
|
-
|
|
667
|
-
Comprehensive logging for troubleshooting with enhanced v3.0 features.
|
|
668
|
-
|
|
669
|
-
```tsx
|
|
670
|
-
import { getDebugLogger } from '@chemmangat/msal-next';
|
|
671
|
-
|
|
672
|
-
const logger = getDebugLogger({
|
|
673
|
-
enabled: true,
|
|
674
|
-
level: 'debug',
|
|
675
|
-
showTimestamp: true,
|
|
676
|
-
enablePerformance: true, // NEW in v3.0
|
|
677
|
-
enableNetworkLogs: true, // NEW in v3.0
|
|
678
|
-
maxHistorySize: 100, // NEW in v3.0
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
// Basic logging
|
|
682
|
-
logger.info('User logged in', { username: 'user@example.com' });
|
|
683
|
-
logger.error('Authentication failed', { error });
|
|
684
|
-
|
|
685
|
-
// NEW: Performance tracking
|
|
686
|
-
logger.startTiming('token-acquisition');
|
|
687
|
-
const token = await acquireToken(['User.Read']);
|
|
688
|
-
logger.endTiming('token-acquisition'); // Logs duration
|
|
689
|
-
|
|
690
|
-
// NEW: Network logging
|
|
691
|
-
logger.logRequest('GET', '/me');
|
|
692
|
-
logger.logResponse('GET', '/me', 200, userData);
|
|
693
|
-
|
|
694
|
-
// NEW: Export logs
|
|
695
|
-
const logs = logger.exportLogs();
|
|
696
|
-
logger.downloadLogs('debug-logs.json'); // Download as file
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
## TypeScript Support
|
|
700
|
-
|
|
701
|
-
### Custom Token Claims
|
|
702
|
-
|
|
703
|
-
Extend the `CustomTokenClaims` interface for type-safe custom claims.
|
|
704
|
-
|
|
705
|
-
```tsx
|
|
706
|
-
import { CustomTokenClaims } from '@chemmangat/msal-next';
|
|
691
|
+
'use client';
|
|
707
692
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
693
|
+
import { useMsalAuth, wrapMsalError } from '@chemmangat/msal-next';
|
|
694
|
+
|
|
695
|
+
export default function LoginPage() {
|
|
696
|
+
const { loginRedirect } = useMsalAuth();
|
|
697
|
+
|
|
698
|
+
const handleLogin = async () => {
|
|
699
|
+
try {
|
|
700
|
+
await loginRedirect();
|
|
701
|
+
} catch (error) {
|
|
702
|
+
const msalError = wrapMsalError(error);
|
|
703
|
+
|
|
704
|
+
// Check if user cancelled (not a real error)
|
|
705
|
+
if (msalError.isUserCancellation()) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Display actionable error message
|
|
710
|
+
console.error(msalError.toConsoleString());
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
return <button onClick={handleLogin}>Sign In</button>;
|
|
712
715
|
}
|
|
713
|
-
|
|
714
|
-
const { account } = useMsalAuth();
|
|
715
|
-
const claims = account?.idTokenClaims as MyCustomClaims;
|
|
716
|
-
|
|
717
|
-
console.log(claims.roles); // Type-safe!
|
|
718
|
-
console.log(claims.department); // Type-safe!
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
## Advanced Examples
|
|
722
|
-
|
|
723
|
-
### Multi-Tenant Support
|
|
724
|
-
|
|
725
|
-
```tsx
|
|
726
|
-
<MsalAuthProvider
|
|
727
|
-
clientId="YOUR_CLIENT_ID"
|
|
728
|
-
authorityType="common" // Supports any Azure AD tenant
|
|
729
|
-
>
|
|
730
|
-
{children}
|
|
731
|
-
</MsalAuthProvider>
|
|
732
716
|
```
|
|
733
717
|
|
|
734
|
-
### Custom
|
|
735
|
-
|
|
736
|
-
```tsx
|
|
737
|
-
<MsalAuthProvider
|
|
738
|
-
clientId="YOUR_CLIENT_ID"
|
|
739
|
-
scopes={[
|
|
740
|
-
'User.Read',
|
|
741
|
-
'Mail.Read',
|
|
742
|
-
'Calendars.Read',
|
|
743
|
-
'Files.Read.All'
|
|
744
|
-
]}
|
|
745
|
-
>
|
|
746
|
-
{children}
|
|
747
|
-
</MsalAuthProvider>
|
|
748
|
-
```
|
|
718
|
+
### Custom Profile Fields
|
|
749
719
|
|
|
750
|
-
|
|
720
|
+
Extend UserProfile with organization-specific fields:
|
|
751
721
|
|
|
752
722
|
```tsx
|
|
753
|
-
|
|
723
|
+
'use client';
|
|
754
724
|
|
|
755
|
-
|
|
756
|
-
await loginPopup(scopes, {
|
|
757
|
-
prompt: 'select_account'
|
|
758
|
-
});
|
|
725
|
+
import { useUserProfile, UserProfile } from '@chemmangat/msal-next';
|
|
759
726
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
{account.username}
|
|
764
|
-
</div>
|
|
765
|
-
));
|
|
766
|
-
```
|
|
767
|
-
|
|
768
|
-
### Server-Side Rendering
|
|
727
|
+
interface MyCompanyProfile extends UserProfile {
|
|
728
|
+
customField: string;
|
|
729
|
+
}
|
|
769
730
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
import { getServerSession } from '@chemmangat/msal-next';
|
|
773
|
-
|
|
774
|
-
export default async function ProfilePage() {
|
|
775
|
-
const session = await getServerSession();
|
|
731
|
+
export default function ProfilePage() {
|
|
732
|
+
const { profile } = useUserProfile<MyCompanyProfile>();
|
|
776
733
|
|
|
777
734
|
return (
|
|
778
735
|
<div>
|
|
779
|
-
<
|
|
780
|
-
|
|
781
|
-
<p>Welcome {session.username}</p>
|
|
782
|
-
) : (
|
|
783
|
-
<p>Please sign in</p>
|
|
784
|
-
)}
|
|
736
|
+
<p>Department: {profile?.department}</p>
|
|
737
|
+
<p>Custom Field: {profile?.customField}</p>
|
|
785
738
|
</div>
|
|
786
739
|
);
|
|
787
740
|
}
|
|
788
741
|
```
|
|
789
742
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
```tsx
|
|
793
|
-
'use client';
|
|
794
|
-
|
|
795
|
-
import { useRoles, AuthGuard } from '@chemmangat/msal-next';
|
|
796
|
-
|
|
797
|
-
function AdminPanel() {
|
|
798
|
-
const { hasRole, hasAnyRole, hasAllRoles } = useRoles();
|
|
799
|
-
|
|
800
|
-
if (!hasRole('Admin')) {
|
|
801
|
-
return <div>Access denied</div>;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
return <div>Admin content</div>;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
export default function AdminPage() {
|
|
808
|
-
return (
|
|
809
|
-
<AuthGuard>
|
|
810
|
-
<AdminPanel />
|
|
811
|
-
</AuthGuard>
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
```
|
|
815
|
-
|
|
816
|
-
## Configuration Options
|
|
817
|
-
|
|
818
|
-
### MsalAuthConfig
|
|
819
|
-
|
|
820
|
-
| Option | Type | Default | Description |
|
|
821
|
-
|--------|------|---------|-------------|
|
|
822
|
-
| `clientId` | `string` | **Required** | Azure AD Application (client) ID |
|
|
823
|
-
| `tenantId` | `string` | `undefined` | Azure AD Directory (tenant) ID |
|
|
824
|
-
| `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | `'common'` | Authority type |
|
|
825
|
-
| `redirectUri` | `string` | `window.location.origin` | Redirect URI after auth |
|
|
826
|
-
| `scopes` | `string[]` | `['User.Read']` | Default scopes |
|
|
827
|
-
| `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | `'sessionStorage'` | Token cache location |
|
|
828
|
-
| `enableLogging` | `boolean` | `false` | Enable debug logging |
|
|
829
|
-
|
|
830
|
-
## Troubleshooting
|
|
831
|
-
|
|
832
|
-
### Common Issues
|
|
833
|
-
|
|
834
|
-
**Issue**: "No active account" error
|
|
835
|
-
**Solution**: Ensure user is logged in before calling `acquireToken`
|
|
836
|
-
|
|
837
|
-
**Issue**: Token acquisition fails
|
|
838
|
-
**Solution**: Check that required scopes are granted in Azure AD
|
|
839
|
-
|
|
840
|
-
**Issue**: SSR hydration mismatch
|
|
841
|
-
**Solution**: Use `'use client'` directive for components using auth hooks
|
|
842
|
-
|
|
843
|
-
**Issue**: Middleware not protecting routes
|
|
844
|
-
**Solution**: Ensure session cookies are being set after login
|
|
845
|
-
|
|
846
|
-
**Issue**: "createContext only works in Client Components"
|
|
847
|
-
**Solution**: Use `MSALProvider` (not `MsalAuthProvider`) in layout.tsx
|
|
848
|
-
|
|
849
|
-
**Issue**: Redirect URI mismatch (AADSTS50011)
|
|
850
|
-
**Solution**: Add your redirect URI to Azure Portal โ App registrations โ Authentication
|
|
851
|
-
|
|
852
|
-
**Issue**: Missing environment variables
|
|
853
|
-
**Solution**: Create `.env.local` with `NEXT_PUBLIC_AZURE_AD_CLIENT_ID` and `NEXT_PUBLIC_AZURE_AD_TENANT_ID`
|
|
854
|
-
|
|
855
|
-
For more detailed troubleshooting, see [TROUBLESHOOTING.md](./TROUBLESHOOTING.md)
|
|
856
|
-
|
|
857
|
-
### Debug Mode
|
|
858
|
-
|
|
859
|
-
Enable debug logging to troubleshoot issues:
|
|
860
|
-
|
|
861
|
-
```tsx
|
|
862
|
-
<MsalAuthProvider
|
|
863
|
-
clientId="YOUR_CLIENT_ID"
|
|
864
|
-
enableLogging={true}
|
|
865
|
-
>
|
|
866
|
-
{children}
|
|
867
|
-
</MsalAuthProvider>
|
|
868
|
-
```
|
|
869
|
-
|
|
870
|
-
### Enhanced Error Handling (v4.0.2)
|
|
743
|
+
---
|
|
871
744
|
|
|
872
|
-
|
|
745
|
+
## ๐ง Configuration Reference
|
|
873
746
|
|
|
874
|
-
|
|
875
|
-
import { wrapMsalError } from '@chemmangat/msal-next';
|
|
747
|
+
### MSALProvider Props
|
|
876
748
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
// Get actionable error message with fix instructions
|
|
888
|
-
console.error(msalError.toConsoleString());
|
|
889
|
-
|
|
890
|
-
// Access error details
|
|
891
|
-
console.log('Error code:', msalError.code);
|
|
892
|
-
console.log('Fix instructions:', msalError.fix);
|
|
893
|
-
console.log('Documentation:', msalError.docs);
|
|
894
|
-
}
|
|
895
|
-
```
|
|
749
|
+
| Prop | Type | Required | Default | Description |
|
|
750
|
+
|------|------|----------|---------|-------------|
|
|
751
|
+
| `clientId` | `string` | โ
Yes | - | Azure AD Application (client) ID |
|
|
752
|
+
| `tenantId` | `string` | No | - | Azure AD Directory (tenant) ID (for single-tenant) |
|
|
753
|
+
| `authorityType` | `'common' \| 'organizations' \| 'consumers' \| 'tenant'` | No | `'common'` | Authority type |
|
|
754
|
+
| `redirectUri` | `string` | No | `window.location.origin` | Redirect URI after authentication |
|
|
755
|
+
| `scopes` | `string[]` | No | `['User.Read']` | Default scopes |
|
|
756
|
+
| `cacheLocation` | `'sessionStorage' \| 'localStorage' \| 'memoryStorage'` | No | `'sessionStorage'` | Token cache location |
|
|
757
|
+
| `enableLogging` | `boolean` | No | `false` | Enable debug logging |
|
|
896
758
|
|
|
897
|
-
|
|
759
|
+
### Authority Types
|
|
898
760
|
|
|
899
|
-
|
|
761
|
+
- **`common`** - Multi-tenant (any Azure AD tenant or Microsoft account)
|
|
762
|
+
- **`organizations`** - Any organizational Azure AD tenant
|
|
763
|
+
- **`consumers`** - Microsoft personal accounts only
|
|
764
|
+
- **`tenant`** - Single-tenant (requires `tenantId`)
|
|
900
765
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
**New feature:** Zero-Config Protected Routes (optional, but recommended)
|
|
766
|
+
---
|
|
904
767
|
|
|
905
|
-
|
|
906
|
-
```tsx
|
|
907
|
-
// middleware.ts
|
|
908
|
-
export async function middleware(request) {
|
|
909
|
-
const session = await getServerSession();
|
|
910
|
-
if (!session) return redirect('/login');
|
|
911
|
-
}
|
|
768
|
+
## ๐ Additional Resources
|
|
912
769
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
```
|
|
770
|
+
### Documentation
|
|
771
|
+
- [SECURITY.md](./SECURITY.md) - **Security policy and best practices** โญ
|
|
772
|
+
- [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) - Common issues and solutions
|
|
773
|
+
- [CHANGELOG.md](./CHANGELOG.md) - Version history
|
|
774
|
+
- [MIGRATION_GUIDE_v3.md](./MIGRATION_GUIDE_v3.md) - Migrating from v2.x
|
|
775
|
+
- [EXAMPLES_v4.0.2.md](./EXAMPLES_v4.0.2.md) - Code examples
|
|
920
776
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
777
|
+
### Security
|
|
778
|
+
- ๐ [Security Policy](./SECURITY.md) - Complete security documentation
|
|
779
|
+
- ๐ก๏ธ [Best Practices](./SECURITY.md#-best-practices) - Security guidelines
|
|
780
|
+
- โ ๏ธ [Common Mistakes](./SECURITY.md#-common-security-mistakes) - What to avoid
|
|
781
|
+
- โ
[Security Checklist](./SECURITY.md#-security-checklist) - Pre-deployment checklist
|
|
925
782
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
783
|
+
### Links
|
|
784
|
+
- ๐ฆ [npm Package](https://www.npmjs.com/package/@chemmangat/msal-next)
|
|
785
|
+
- ๐ [Report Issues](https://github.com/chemmangat/msal-next/issues)
|
|
786
|
+
- ๐ฌ [Discussions](https://github.com/chemmangat/msal-next/discussions)
|
|
787
|
+
- โญ [GitHub Repository](https://github.com/chemmangat/msal-next)
|
|
930
788
|
|
|
931
|
-
|
|
789
|
+
### Microsoft Resources
|
|
790
|
+
- [Azure AD Documentation](https://learn.microsoft.com/en-us/azure/active-directory/)
|
|
791
|
+
- [MSAL.js Documentation](https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-overview)
|
|
792
|
+
- [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview)
|
|
932
793
|
|
|
933
794
|
---
|
|
934
795
|
|
|
935
|
-
##
|
|
936
|
-
|
|
937
|
-
### From v2.x to v3.0
|
|
938
|
-
|
|
939
|
-
v3.0 includes breaking changes. See [MIGRATION_GUIDE_v3.md](./MIGRATION_GUIDE_v3.md) for complete details.
|
|
796
|
+
## โ FAQ
|
|
940
797
|
|
|
941
|
-
**
|
|
798
|
+
**Q: Do I need to create an Azure AD app registration?**
|
|
799
|
+
A: Yes, you need an app registration in Azure Portal to get the client ID and tenant ID.
|
|
942
800
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
npm install @chemmangat/msal-next@3.0.0
|
|
946
|
-
npm install @azure/msal-browser@^4.0.0
|
|
947
|
-
npm install @azure/msal-react@^3.0.0
|
|
948
|
-
npm install next@^14.1.0
|
|
801
|
+
**Q: Can I use this with Next.js Pages Router?**
|
|
802
|
+
A: This package is designed for App Router. For Pages Router, use v2.x or consider migrating to App Router.
|
|
949
803
|
|
|
950
|
-
|
|
951
|
-
|
|
804
|
+
**Q: Is this free to use?**
|
|
805
|
+
A: Yes, the package is MIT licensed and free. Azure AD has a free tier for up to 50,000 users.
|
|
952
806
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const session = await getServerSession();
|
|
956
|
-
const token = session.accessToken; // โ Removed
|
|
807
|
+
**Q: Can I use this for multi-tenant SaaS apps?**
|
|
808
|
+
A: Yes! Set `authorityType="common"` and omit `tenantId`.
|
|
957
809
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const { acquireToken } = useMsalAuth();
|
|
961
|
-
const token = await acquireToken(['User.Read']); // โ
|
|
962
|
-
```
|
|
810
|
+
**Q: How do I get additional user information?**
|
|
811
|
+
A: Use `useUserProfile()` hook which provides 30+ fields from Microsoft Graph.
|
|
963
812
|
|
|
964
|
-
|
|
813
|
+
**Q: Can I customize the sign-in button?**
|
|
814
|
+
A: Yes, `MicrosoftSignInButton` accepts `variant`, `size`, `className`, and `style` props.
|
|
965
815
|
|
|
966
|
-
|
|
816
|
+
**Q: Does this work with Azure AD B2C?**
|
|
817
|
+
A: This package is designed for Azure AD. For B2C, you may need additional configuration.
|
|
967
818
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
import { MsalAuthProvider, useMsalAuth } from '@chemmangat/msal-next';
|
|
819
|
+
**Q: How do I protect API routes?**
|
|
820
|
+
A: Use `getServerSession()` in API routes or `createAuthMiddleware()` for edge protection.
|
|
971
821
|
|
|
972
|
-
|
|
973
|
-
import {
|
|
974
|
-
AuthGuard,
|
|
975
|
-
SignOutButton,
|
|
976
|
-
UserAvatar,
|
|
977
|
-
useGraphApi,
|
|
978
|
-
useUserProfile,
|
|
979
|
-
useRoles,
|
|
980
|
-
withAuth,
|
|
981
|
-
createAuthMiddleware,
|
|
982
|
-
} from '@chemmangat/msal-next';
|
|
983
|
-
```
|
|
822
|
+
---
|
|
984
823
|
|
|
985
|
-
## Contributing
|
|
824
|
+
## ๐ค Contributing
|
|
986
825
|
|
|
987
826
|
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
988
827
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
MIT ยฉ [Chemmangat](https://github.com/chemmangat)
|
|
992
|
-
|
|
993
|
-
## Support
|
|
994
|
-
|
|
995
|
-
- ๐ [Documentation](https://github.com/chemmangat/msal-next#readme)
|
|
996
|
-
- ๐ [Issue Tracker](https://github.com/chemmangat/msal-next/issues)
|
|
997
|
-
- ๐ฌ [Discussions](https://github.com/chemmangat/msal-next/discussions)
|
|
998
|
-
- ๐ [CLI Tool](https://www.npmjs.com/package/@chemmangat/msal-next-cli)
|
|
999
|
-
- ๐ [Migration Guide](./MIGRATION_GUIDE_v3.md)
|
|
1000
|
-
- ๐งช [Testing Guide](./TESTING_GUIDE.md)
|
|
828
|
+
---
|
|
1001
829
|
|
|
1002
|
-
##
|
|
830
|
+
## ๐ License
|
|
1003
831
|
|
|
1004
|
-
|
|
1005
|
-
- ๐ 6+ additional examples
|
|
1006
|
-
- โก Performance optimizations
|
|
1007
|
-
- ๐ Security audit
|
|
1008
|
-
- ๐ New hooks and components
|
|
832
|
+
MIT ยฉ [Chemmangat](https://github.com/chemmangat)
|
|
1009
833
|
|
|
1010
|
-
|
|
834
|
+
---
|
|
1011
835
|
|
|
1012
|
-
## Acknowledgments
|
|
836
|
+
## ๐ Acknowledgments
|
|
1013
837
|
|
|
1014
838
|
Built with:
|
|
1015
839
|
- [@azure/msal-browser](https://github.com/AzureAD/microsoft-authentication-library-for-js)
|
|
1016
840
|
- [@azure/msal-react](https://github.com/AzureAD/microsoft-authentication-library-for-js)
|
|
1017
841
|
- [Next.js](https://nextjs.org/)
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## ๐ Stats
|
|
846
|
+
|
|
847
|
+
- ๐ฆ 2,200+ weekly downloads
|
|
848
|
+
- โญ Used in production by developers worldwide
|
|
849
|
+
- ๐ Security-focused with regular updates
|
|
850
|
+
- ๐ Comprehensive documentation
|
|
851
|
+
- ๐ฏ TypeScript-first with complete type safety
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
**Need help?** Check [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) or [open an issue](https://github.com/chemmangat/msal-next/issues)!
|