You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -1342,8 +1351,9 @@ export async function GET() {
1342
1351
```
1343
1352
1344
1353
**Client Side:**
1345
-
When the client receives the 403 with `mfa_required`, you should redirect the user to complete the step-up challenge.
1354
+
When the client receives the 403 with `mfa_required`, you can either redirect the user to a dedicated MFA page or use the popup-based approach to complete MFA without a full-page redirect.
1346
1355
1356
+
**Option 1: Full-page redirect**
1347
1357
```javascript
1348
1358
constresponse=awaitfetch("/api/protected");
1349
1359
if (response.status===403) {
@@ -1356,6 +1366,10 @@ if (response.status === 403) {
1356
1366
}
1357
1367
```
1358
1368
1369
+
**Option 2: Popup (no redirect)**
1370
+
1371
+
Use `mfa.challengeWithPopup()` to complete MFA in a popup without leaving the current page. See [Reactive MFA Step-Up (Popup)](#reactive-mfa-step-up-popup) for full documentation.
1372
+
1359
1373
### MFA Tenant Configuration
1360
1374
1361
1375
The SDK relies on background token refreshes to maintain user sessions. For these non-interactive requests to succeed, it is important to configure your MFA policies to allow `refresh_token` exchanges without immediate user challenge.
@@ -3705,10 +3719,194 @@ The SDK provides typed error classes for all MFA operations:
The SDK supports **reactive MFA step-up** via a browser popup using Auth0 Universal Login. When an API call fails with `mfa_required`, the client-side `mfa.challengeWithPopup()` method opens a popup window where the user completes MFA through Auth0's Universal Login. After completion, the token is cached in the server-side session and returned directly to the caller — no full-page redirect required.
3727
+
3728
+
This is useful for applications that need to protect specific actions (e.g., transferring funds, changing settings) with MFA without disrupting the user's current page state.
3729
+
3730
+
**Flow summary:**
3731
+
1. App calls an API that requires MFA → receives `MfaRequiredError`
### Handling MfaRequiredError from Client Components
3798
+
3799
+
The client-side `getAccessToken()` helper automatically detects 403 responses with `error: "mfa_required"` and throws `MfaRequiredError`. This allows you to use `instanceof` checks to trigger the popup flow:
3800
+
3801
+
```tsx
3802
+
import { getAccessToken } from '@auth0/nextjs-auth0/client';
3803
+
import { MfaRequiredError } from '@auth0/nextjs-auth0/errors';
> The `MfaRequiredError` detection works for both server-side and client-side `getAccessToken()` calls. On the client, it is reconstructed from the 403 JSON response returned by the `/auth/access-token` endpoint.
3819
+
3820
+
### Configuration Options
3821
+
3822
+
`challengeWithPopup()` accepts the following options:
> Popup timeout is configured per-call only. There is no server-side configuration option or environment variable for this — timeout is a client-side runtime concern. If you need a consistent default across your app, define an application-level constant and pass it to every call.
3848
+
3849
+
### CSP Nonce Support
3850
+
3851
+
If your application uses a strict Content Security Policy that blocks inline scripts, configure a CSP nonce on the server-side `Auth0Client`:
3852
+
3853
+
```typescript
3854
+
// lib/auth0.ts
3855
+
import { Auth0Client } from '@auth0/nextjs-auth0/server';
3856
+
3857
+
export const auth0 = new Auth0Client({
3858
+
cspNonce: 'your-generated-nonce'
3859
+
});
3860
+
```
3861
+
3862
+
The nonce is injected into the `<script>` tag of the popup callback HTML response, making it compliant with `script-src 'nonce-...'` CSP policies.
3863
+
3864
+
> [!IMPORTANT]
3865
+
> The nonce must contain only base64 characters (`A-Za-z0-9+/=-_`). Invalid characters will throw an `InvalidConfigurationError`.
3866
+
3867
+
> [!NOTE]
3868
+
> The `cspNonce` is set at `Auth0Client` construction time and remains static for the lifetime of the instance. Since `Auth0Client` is typically a singleton, this means the same nonce is reused across requests. This still provides protection over `'unsafe-inline'` (the script must know the nonce), but is weaker than per-request nonce rotation. If your security policy requires per-request nonces, you would need to create the `Auth0Client` per-request or use middleware to inject a fresh nonce via a custom header.
3869
+
3870
+
If you do **not** configure a `cspNonce` and your CSP blocks inline scripts, the popup will complete the MFA flow but the parent window will never receive the `postMessage`. This manifests as a `PopupTimeoutError` after the configured timeout.
3871
+
3872
+
### Error Handling
3873
+
3874
+
`challengeWithPopup()` can throw several typed errors. Handle them to provide appropriate user feedback:
3875
+
3876
+
```tsx
3877
+
import { mfa } from '@auth0/nextjs-auth0/client';
3878
+
import {
3879
+
PopupBlockedError,
3880
+
PopupCancelledError,
3881
+
PopupTimeoutError,
3882
+
PopupInProgressError,
3883
+
ExecutionContextError
3884
+
} from '@auth0/nextjs-auth0/errors';
3885
+
3886
+
try {
3887
+
const { token } = await mfa.challengeWithPopup({
3888
+
audience: 'https://api.example.com'
3889
+
});
3890
+
} catch (err) {
3891
+
if (err instanceof PopupBlockedError) {
3892
+
// Browser blocked the popup — prompt user to allow popups
3893
+
alert('Please allow popups for this site and try again.');
3894
+
} else if (err instanceof PopupCancelledError) {
3895
+
// User closed the popup before completing MFA
3896
+
console.log('MFA cancelled by user.');
3897
+
} else if (err instanceof PopupTimeoutError) {
3898
+
// Popup did not complete within the timeout
3899
+
console.log('MFA timed out. Please try again.');
3900
+
} else if (err instanceof PopupInProgressError) {
3901
+
// Another popup is already open
3902
+
console.log('Please complete the current MFA prompt first.');
3903
+
} else if (err instanceof ExecutionContextError) {
3904
+
// Called from server-side code (SSR, middleware)
3905
+
console.error('challengeWithPopup() can only be called in browser context.');
3906
+
} else {
3907
+
// AccessTokenError or other errors
3908
+
console.error('MFA failed:', err.message);
3909
+
3712
3910
Multiple Custom Domains (MCD) enables a single `@auth0/nextjs-auth0` instance to authenticate users against different Auth0 custom domains on the same tenant. This is useful for:
3713
3911
3714
3912
- **B2C Multi-Brand**: Multiple branded auth domains (`auth.brand1.com`, `auth.brand2.com`) on a single Auth0 tenant
@@ -3995,6 +4193,35 @@ export default async function Page() {
| `AccessTokenError` | Various | Token retrieval failed after popup completed |
4206
+
4207
+
### Security Considerations
4208
+
4209
+
- **Same-origin postMessage:** The popup listener only accepts messages from `window.location.origin`. Cross-origin messages are silently ignored.
4210
+
- **No tokens in postMessage:** The popup's `postMessage` payload contains only `{ sub, email }` metadata — never raw access tokens. Tokens remain server-side in the encrypted session cookie.
4211
+
- **PKCE:** The popup flow uses the same PKCE-based authorization code exchange as standard login. No security downgrade.
4212
+
- **State encryption:** The `returnStrategy` flag is stored in the encrypted transaction cookie alongside other OAuth state (AES-256-GCM).
4213
+
- **XSS prevention:** The callback HTML uses `JSON.stringify()` with `<` escaping (`\u003c`) to prevent script injection via user-controlled values.
4214
+
4215
+
### Known Limitations
4216
+
4217
+
| Limitation | Details |
4218
+
|------------|---------|
4219
+
| **One popup at a time** | Only one `challengeWithPopup()` call is allowed concurrently. A second call throws `PopupInProgressError` regardless of audience. |
4220
+
| **Same-origin only** | The postMessage validation requires same-origin. Cross-origin popup flows are not supported. |
4221
+
| **Browser popup policies** | Most browsers block popups unless triggered by a direct user action (click handler). Ensure `challengeWithPopup()` is called within a user-initiated event handler. |
4222
+
| **`beforeSessionSaved` idempotency** | The `beforeSessionSaved` hook runs again when the popup token is merged into the existing session. Ensure your hook is idempotent when using popup flows. |
4223
+
| **Session cookie size** | Each cached MRRT token increases session cookie size. For applications with many audiences, consider using a [database session store](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#database-sessions). |
4224
+
3998
4225
**Handling issuer validation errors:**
3999
4226
4000
4227
`IssuerValidationError` is thrown during the authentication callback when the ID token's issuer doesn't match the transaction's expected issuer. This indicates a potential cross-domain token confusion attack or misconfiguration.
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -224,6 +224,7 @@ You can customize the client by using the options below:
224
224
| useDPoP |`boolean`| Enable DPoP (Demonstration of Proof-of-Possession) for enhanced security. When enabled, the client will generate DPoP proofs for token requests and protected resource requests. Defaults to `false`. |
225
225
| dpopKeyPair |`DpopKeyPair`| ES256 key pair for DPoP proof generation. If not provided, the SDK will attempt to load keys from `AUTH0_DPOP_PUBLIC_KEY` and `AUTH0_DPOP_PRIVATE_KEY` environment variables. Keys must be in PEM format. |
226
226
| dpopOptions |`DpopOptions`| Configure DPoP timing validation. Supports `clockSkew` (adjust assumed current time) and `clockTolerance` (validation tolerance). Can also be configured via `AUTH0_DPOP_CLOCK_SKEW` and `AUTH0_DPOP_CLOCK_TOLERANCE` environment variables. See [DPoP Clock Validation](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#dpop-clock-validation) for details. |
227
+
| cspNonce |`string`| CSP nonce for inline scripts in popup callback HTML responses. When provided, `<script>` tags in the MFA popup callback include a `nonce="..."` attribute for strict Content Security Policy compliance. Only required when using `mfa.challengeWithPopup()` with a CSP that blocks inline scripts. |
227
228
| discoveryCache |`DiscoveryCacheOptions`| Configure the OIDC discovery metadata cache for [Multiple Custom Domains](https://github.com/auth0/nextjs-auth0/blob/main/EXAMPLES.md#discovery-cache-configuration). Controls TTL and maximum cached issuers. |
0 commit comments