跳到正文
cccskills
免费在 GitHub 公开

mfa

Build multi-factor authentication flows with @auth0/nextjs-auth0 v4. The skill covers redirect and server-triggered step-up checks, managing authenticators, and popup challenges, including how to pass MFA tokens safely and handle common errors.

中文简介正在准备中。当前显示英语简介。

原文查看原文说明

Multi-factor authentication with @auth0/nextjs-auth0 v4: redirect step-up via Universal Login (amr gating), server-side step-up on mfa_required, the MFA management API (getAuthenticators, enroll, challenge, verify), and reactive step-up via mfa.challengeWithPopup(). Covers the beforeSessionSaved amr hook, the secure mfa_token handoff, and MFA error classes.

auth0/nextjs-auth0星标 2,312(GitHub 2,312 + cccskills 0)

实测评分 未实测

查看安装方法

适用场景

  • Requiring MFA before a sensitive action
  • Redirecting users to complete an MFA challenge
  • Enrolling or verifying an authenticator
  • Triggering an MFA challenge in a popup

包含的文件(1)

  • SKILL.md8.2 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

@auth0/nextjs-auth0 — MFA (v4)

Minimum version: 4.15.0 for base MFA + the MFA management APIs; reactive popup step-up (mfa.challengeWithPopup) requires 4.19.0+.

Framework-specific surface only. The shared mechanic, tenant config, amr/error tables, and full MFA API detail live in guides/mfa.md at the package root.

Singleton client (lib/auth0.ts):

import { Auth0Client } from "@auth0/nextjs-auth0/server";
export const auth0 = new Auth0Client({ mfaTokenTtl: 300 }); // seconds; matches Auth0's mfa_token expiry. Or AUTH0_MFA_TOKEN_TTL env var.

MFA methods are on auth0.mfa (server, @auth0/nextjs-auth0/server) and the mfa named export (client, @auth0/nextjs-auth0/client). Errors import from @auth0/nextjs-auth0/errors.

Flow 0 — redirect step-up via Universal Login (use this when the task says "redirect them to step up")

Gate the action on the amr claim; when MFA is missing, redirect through v4's login route with the PAPE acr_values + max_age=0. v4 has no handleLogin export — login is middleware-driven, so pass the params one of two ways (both verified on 4.30.0). Do not grep node_modules to rediscover this.

Step 1 — persist amr so the gate can read it. Add the beforeSessionSaved hook (see "Reading amr" below); without it session.user.amr is always undefined and the redirect loops.

Step 2 — trigger the step-up. Either:

(a) Redirect to /auth/login with query params (simplest). handleLogin forwards every query param except challengeMode/returnTo straight to /authorize:

import { redirect } from "next/navigation";

const MFA_ACR = "http://schemas.openid.net/pape/policies/2007/06/multi-factor";
redirect(`/auth/login?acr_values=${encodeURIComponent(MFA_ACR)}&max_age=0&returnTo=/dashboard/transfer`);

(b) Custom Route Handler calling auth0.startInteractiveLogin (type-safe). Signature: startInteractiveLogin(options?: StartInteractiveLoginOptions): Promise<NextResponse>:

// app/auth/step-up/route.ts
import type { NextResponse } from "next/server";
import type { StartInteractiveLoginOptions } from "@auth0/nextjs-auth0/types";
import { auth0 } from "@/lib/auth0";

export async function GET(): Promise<NextResponse> {
  const options: StartInteractiveLoginOptions = {
    authorizationParameters: {          // AuthorizationParameters from "@auth0/nextjs-auth0/types"
      acr_values: "http://schemas.openid.net/pape/policies/2007/06/multi-factor",
      max_age: 0,                       // number (seconds); 0 forces a fresh challenge
    },
    returnTo: "/dashboard/transfer",
  };
  return auth0.startInteractiveLogin(options); // returns the redirect NextResponse
}

Step 3 — gate the sensitive action (Server Component or Server Action), enforced server-side:

// app/dashboard/transfer/page.tsx
import { redirect } from "next/navigation";
import { auth0 } from "@/lib/auth0";

export default async function TransferPage() {
  const session = await auth0.getSession(); // SessionData | null
  if (!session?.user.amr?.includes("mfa")) {
    redirect("/auth/step-up"); // or the /auth/login?... URL from step 2(a)
  }
  // render the transfer form; re-check the same amr in the Server Action before moving funds
}

Step 4 — after Universal Login completes MFA it returns to returnTo; beforeSessionSaved writes amr, the gate now passes, and the transfer runs. A frontend amr check is UX only — always enforce on the server.

Flow 1 — server-side step-up (Route Handler / Server Action)

Force a refresh so the post-login Action runs and can throw MfaRequiredError:

const { token } = await auth0.getAccessToken({ audience: "https://my-api", refresh: true }); // refresh required; default returns cached token

Pass mfa_token to the MFA page via an httpOnly cookie, never the URL:

import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { MfaRequiredError } from "@auth0/nextjs-auth0/server";

try {
  const { token } = await auth0.getAccessToken({ audience: "https://my-api", refresh: true });
  // proceed with token
} catch (error) {
  if (error instanceof MfaRequiredError) {
    const session = await auth0.getSession();
    (await cookies()).set("mfa_token",
      JSON.stringify({ sub: session?.user.sub, token: error.mfa_token }),
      { httpOnly: true, secure: true, sameSite: "lax", maxAge: 300 });
    redirect("/mfa");
  }
  throw error;
}

On /mfa, read the cookie back. If auth0.getSession() returns null (user logged out or session expired), redirect to login and delete the MFA cookie. Otherwise re-check sub === session.user.sub to prevent one user completing another's MFA challenge, drive auth0.mfa.*, then (await cookies()).delete("mfa_token") after a successful verify.

Flow 2 — MFA management API (auth0.mfa.*)

All take { mfaToken }:

  • getAuthenticators({ mfaToken }) → Authenticator[] Authenticator: { id: string, authenticatorType: 'otp'|'oob', oobChannel?: 'sms'|'voice'|'auth0'|'email', active: boolean }
  • enroll({ mfaToken, authenticatorTypes, oobChannels?, phoneNumber?, email? }) — OTP: ["otp"] → { barcodeUri: string, secret: string, recoveryCodes?: string[] }; OOB: ["oob"] + oobChannels → { oobCode: string, bindingMethod?: string }.
  • challenge({ mfaToken, challengeType: 'oob', authenticatorId }) → { oobCode: string, bindingMethod?: string } (OOB only — not needed for OTP).
  • verify({ mfaToken, otp: string }) / ({ mfaToken, oobCode, bindingCode: string }) / ({ mfaToken, recoveryCode }) → MfaVerifyResponse (tokens written to session). bindingCode is required when using OOB. When recoveryCode is used, the response may include a new recovery_code — show it to the user once.

Callable from a Server Component/Action (auth0.mfa.verify) or a client component (import { mfa } from "@auth0/nextjs-auth0/client").

Flow 3 — reactive popup step-up (client components)

mfa.challengeWithPopup() calls window.open(), so it must run from a direct user-gesture handler (a button onClick), never from an async catch:

"use client";
import { getAccessToken, mfa } from "@auth0/nextjs-auth0/client";
import { MfaRequiredError } from "@auth0/nextjs-auth0/errors";

async function handleStepUp() {
  const { token } = await mfa.challengeWithPopup({ audience: "https://api.example.com" });
  await callApi(token);
}

Options: audience (required), scope, acr_values (defaults to the PAPE MFA URI), timeout (ms, default 60000), popupWidth/popupHeight. Popup errors: PopupBlockedError, PopupCancelledError, PopupTimeoutError, PopupInProgressError, ExecutionContextError (called server-side). If a CSP blocks inline scripts, set cspNonce on Auth0Client or the popup completes but postMessage never returns (PopupTimeoutError).

Reading amr

v4 persists only a default claim subset, so amr is dropped by default. Opt it in on the client with a beforeSessionSaved hook — copy the claim from the ID token onto session.user, then read session.user.amr server-side:

export const auth0 = new Auth0Client({
  async beforeSessionSaved(session, idToken) {
    if (!idToken) return session;
    const claims = decodeJwt(idToken); // e.g. jose's decodeJwt; idToken is the raw JWT string
    return { ...session, user: { ...session.user, amr: claims.amr } };
  },
});

session is SessionData ({ user, tokenSet, internal, ... }); the hook's return type is the same. amr lands at session.user.amr and survives to auth0.getSession().

Server error classes

MfaRequiredError, MfaNoAvailableFactorsError, MfaTokenExpiredError, MfaTokenInvalidError, MfaGetAuthenticatorsError, MfaEnrollmentError, MfaChallengeError, MfaVerifyError (from @auth0/nextjs-auth0/errors).

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Set up Auth0 authentication in Next.js with @auth0/nextjs-auth0 v4 for App Router or Pages Router. Configure the client and middleware, add login and user context, protect pages and API routes, and retrieve sessions or access tokens for API calls.

中文简介正在准备中。当前显示英语简介。

  • Adding Auth0 login to a Next.js application
  • Configuring middleware and authentication routes
  • Protecting pages and API endpoints
auth0/nextjs-auth023122026年10月26日 更新

Use this skill to build B2B authentication for multiple customer organizations with Auth0 and @auth0/nextjs-auth0 v4. It covers passing an organization at login, reading and validating its ID, accepting invitations, and configuring organizations and connections.

中文简介正在准备中。当前显示英语简介。

  • Separating login and access by customer organization
  • Passing an organization ID through a login flow
  • Accepting an organization invitation in a Next.js app
auth0/nextjs-auth023122026年10月26日 更新

passkeys

免费

Implement passkey signup, sign-in, and enrollment for signed-in users with @auth0/nextjs-auth0 v4. The skill explains how server handlers create challenges and exchange credentials, while the browser performs WebAuthn and serializes the result.

中文简介正在准备中。当前显示英语简介。

  • Adding passkey signup to a Next.js application
  • Building passkey sign-in with browser WebAuthn
  • Letting signed-in users enroll another passkey
auth0/nextjs-auth023122026年10月26日 更新

查看 auth0 的全部技能

报告此技能的问题