ITADN
workos/authkit-nextjs
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

AuthKit Next.js 库

AuthKit 的 Next.js 库提供了便捷的辅助功能,用于使用 WorkOS 和 AuthKit 在 Next.js 中进行身份验证和会话管理。

注意:此库旨在与 Next.js App Router 配合使用。

安装

请同时安装该包和 WorkOS Node SDK(作为同级依赖项):

pnpm i @workos-inc/authkit-nextjs @workos-inc/node

yarn add @workos-inc/authkit-nextjs @workos-inc/node

视频教程

YouTube tutorial: Next.js App Router Authentication with AuthKit

预检

请确保以下值存在于您的 .env.local 环境变量文件中。客户端 ID 和 API 密钥可在 WorkOS 仪表板 中找到,重定向 URI 也可以在那里进行配置。

WORKOS_CLIENT_ID="client_..." # retrieved from the WorkOS dashboard
WORKOS_API_KEY="sk_test_..." # retrieved from the WorkOS dashboard
WORKOS_COOKIE_PASSWORD="<your password>" # generate a secure password here
NEXT_PUBLIC_WORKOS_REDIRECT_URI="http://localhost:3000/callback" # configured in the WorkOS dashboard

WORKOS_COOKIE_PASSWORD 是用于加密会话 Cookie 的私钥。其长度必须至少为 32 个字符。您可以使用 1Password 生成器openssl 库通过命令行生成强密码:

openssl rand -base64 24

要使用 signOut 方法,您需要在 WorkOS 仪表板设置的 "Redirects" 下设置一个默认的 Logout URI。

可选配置

某些环境变量是可选的,可用于调试或配置 cookie 设置。

环境变量默认值描述
WORKOS_COOKIE_MAX_AGE34560000 (400 天)Cookie 的最大有效期(以秒为单位)
WORKOS_COOKIE_DOMAINNoneCookie 的域名。当为空时,cookie 仅对当前域名有效
WORKOS_COOKIE_NAME'wos-session'会话 cookie 的名称
WORKOS_API_HOSTNAME'api.workos.com'基础 WorkOS API URL
WORKOS_API_HTTPStrue是否在 API 调用中使用 HTTPS
WORKOS_API_PORTNone用于 API 调用的端口。当未设置时,使用标准端口(HTTPS 为 443,HTTP 为 80)
WORKOS_COOKIE_SAMESITE'lax'Cookie 的 SameSite 属性。选项:'lax''strict''none'

使用示例:

WORKOS_COOKIE_MAX_AGE='600'
WORKOS_COOKIE_DOMAIN='example.com'
WORKOS_COOKIE_NAME='my-auth-cookie'

[!WARNING] 设置 WORKOS_COOKIE_SAMESITE='none' 允许在跨源上下文(如 iframe)中发送 Cookie,但会降低对 CSRF 攻击的防护。此设置强制 Cookie 为安全(仅限 HTTPS),且仅应在您的应用架构绝对必要时使用。

[!TIP] >WORKOS_COOKIE_DOMAIN 可用于在应用/域之间共享 WorkOS 会话。注意:WORKOS_COOKIE_PASSWORD 在应用/域之间必须相同。大多数用例无需此设置。

设置

回调路由

WorkOS 要求您提供一个回调 URL,以便在用户完成身份验证后将其重定向回您的应用。在您的 Next.js 应用中,公开一个 API 路由 并添加以下内容。

import { handleAuth } from '@workos-inc/authkit-nextjs';

export const GET = handleAuth();

请确保此路由与 WORKOS_REDIRECT_URI 变量以及 WorkOS 仪表板中配置的 redirect URI 相匹配。例如,如果您的 redirect URI 是 http://localhost:3000/auth/callback,则应将上述代码放在 /app/auth/callback/route.ts 中。

您还可以通过向 handleAuth 传递 returnPathname 选项来控制用户登录后将被发送到的 pathname,如下所示:

export const GET = handleAuth({ returnPathname: '/dashboard' });

如果您的应用程序需要在成功认证后持久化数据,例如来自上游提供者的 oauthTokens,您可以传入一个 onSuccess 函数,该函数将在用户成功认证后被调用:

export const GET = handleAuth({
  onSuccess: async ({ user, oauthTokens, authenticationMethod, organizationId, state }) => {
    await saveTokens(oauthTokens);
    if (authenticationMethod) {
      await saveAuthMethod(user.id, authenticationMethod);
    }
    // Access custom state data passed through the auth flow
    const customData = state ? JSON.parse(state) : null;
    if (customData?.teamId) {
      await addUserToTeam(user.id, customData.teamId);
    }
  },
});

在 Docker 等环境中运行时,请显式设置 baseURL,以确保重定向指向正确的位置。

export const GET = handleAuth({
  baseURL: 'http://localhost:3000',
});

handleAuth 可与以下选项配合使用。

OptionDefaultDescription
returnPathname/用户登录成功后重定向到的路径名
baseURLundefined用于重定向 URI 的基础 URL,以替代请求中的 URL。如果应用程序在 docker 等容器中运行,且主机名可能与请求中的不同,则必须提供此选项
onSuccessundefined一个接收成功认证数据的函数,可用于执行持久化令牌等副作用操作
onErrorundefined一个可以接收错误和请求,并以自定义方式处理错误的函数。

onSuccess 回调数据

onSuccess 回调接收以下数据:

属性类型描述
userUser已认证的用户对象
accessTokenstringJWT 访问令牌
refreshTokenstring用于会话续期的刷新令牌
impersonatorImpersonator | undefined如果用户正在被模拟则存在
oauthTokensOauthTokens | undefined来自上游提供方的 OAuth 令牌
authenticationMethodstring | undefined用户的认证方式(例如,'password'、'google-oauth')。仅在初始登录时可用
organizationIdstring | undefined认证的组织上下文
statestring | undefined通过认证流程传递的自定义状态字符串(如有需要,使用 JSON.parse 解析)

注意authenticationMethod 仅在初始身份验证回调时提供。在后续请求或会话刷新中不可用。

登录 URL

创建一个用于启动 AuthKit 登录流程的路由。该路由用作 WorkOS 仪表板设置中的 登录 URL(也称为 initiate_login_uri)。

// app/sign-in/route.ts (or app/login/route.ts)
import { getSignInUrl } from '@workos-inc/authkit-nextjs';
import { redirect } from 'next/navigation';

export const GET = async () => {
  const signInUrl = await getSignInUrl();
  return redirect(signInUrl);
};

WorkOS 控制台中,前往 Redirects 并将 Sign-in URL 设置为与该路由匹配(例如,http://localhost:3000/sign-in)。

[!IMPORTANT] Sign-in URL 是 impersonation 等功能正常运行的必要条件。如果没有它,由 WorkOS 发起的流程(例如从控制台模拟用户)将会失败,因为它们无法完成该库在每个回调上强制执行的 PKCE/CSRF 验证。

Proxy / Middleware

该库依赖 Next.js 代理(在 Next.js ≤15 中称为 "middleware")来为路由提供会话管理。

对于 Next.js 16+: 在项目根目录创建一个 proxy.ts 文件。 对于 Next.js ≤15: 在项目根目录创建一个 middleware.ts 文件。

// proxy.ts (Next.js 16+)
import { authkitProxy } from '@workos-inc/authkit-nextjs';

export default authkitProxy();

// Match against pages that require auth
export const config = { matcher: ['/', '/admin'] };
// middleware.ts (Next.js ≤15)
import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

export default authkitMiddleware();

// Match against pages that require auth
export const config = { matcher: ['/', '/admin'] };

[!WARNING] 使用通配符匹配器模式可能会拦截静态资源(CSS、图片、字体),导致样式失效——尤其是在使用 Tailwind CSS v4 时。如果需要宽泛的匹配器,请排除 Next.js 静态路径:

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

代理/中间件可以通过多个选项进行配置。

选项默认值描述
redirectUriundefined用于需要动态设置重定向 URI 的场景(例如 Vercel 预览部署)
middlewareAuthundefined用于配置代理/中间件认证选项。有关更多详细信息,请参阅 middleware auth
debugfalse启用调试日志。
signUpPaths[]用于指定在重定向到 AuthKit 时应使用 'sign-up' 屏幕提示的路径。
eagerAuthfalse为第三方服务启用同步访问令牌可用性。有关更多详细信息,请参阅 eager auth
refreshBufferSeconds60 (对于生命周期为 5 分钟或更短的令牌,为 30)在访问令牌过期前多少秒主动刷新会话。有关更多详细信息,请参阅 proactive session refresh

自定义重定向 URI

在需要动态设置重定向 URI 的情况下(例如 Vercel 预览部署),请在 authkitMiddleware 中使用 redirectUri 选项:

import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

export default authkitMiddleware({
  redirectUri: 'https://foo.example.com/callback',
});

// Match against pages that require auth
export const config = { matcher: ['/', '/admin'] };

自定义重定向 URI 将优先于环境变量中配置的重定向 URI 使用。

可组合的代理/中间件

如果需要将 AuthKit 与其他代理逻辑(速率限制、重定向等)结合使用,请使用 authkit() 函数与 handleAuthkitHeaders() 辅助函数:

// proxy.ts (Next.js 16+) or middleware.ts (Next.js ≤15)
import { NextRequest } from 'next/server';
import { authkit, handleAuthkitHeaders } from '@workos-inc/authkit-nextjs';

export default async function proxy(request: NextRequest) {
  // For Next.js ≤15, use: export default async function middleware(request: NextRequest) {
  // Get session, headers, and the WorkOS authorization URL for sign-in redirects
  const { session, headers, authorizationUrl } = await authkit(request);

  const { pathname } = request.nextUrl;

  // Redirect unauthenticated users on protected routes
  if (pathname.startsWith('/app') && !session.user && authorizationUrl) {
    return handleAuthkitHeaders(request, headers, { redirect: authorizationUrl });
  }

  // Custom redirects (relative URLs supported)
  if (pathname === '/old-path') {
    return handleAuthkitHeaders(request, headers, { redirect: '/new-path' });
  }

  // Continue request with properly merged headers
  return handleAuthkitHeaders(request, headers);
}

export const config = { matcher: ['/', '/app/:path*'] };

[!IMPORTANT] 在返回响应时,始终使用 handleAuthkitHeaders()。此辅助函数确保:

  • AuthKit 请求头被正确传递到您的页面(因此 withAuth() 可以正常工作)
  • 内部请求头(会话数据、URL)永远不会泄露到浏览器
  • 仅转发安全的响应头(Set-CookieCache-ControlVary
  • 当存在 Cookie 时,自动设置 Cache-Control: no-store
  • 当存在多个值时,正确合并 Vary 请求头
  • 相对重定向 URL 自动规范化为绝对 URL
  • POST/PUT 重定向使用 303 状态码以防止表单重新提交

[!NOTE] redirect 选项仅应与受信任的值一起使用(例如,来自 authkit()authorizationUrl 或硬编码路径)。切勿在未经验证的情况下将用户控制的输入直接传递给 redirect,因为这可能导致开放重定向攻击。

重定向选项
handleAuthkitHeaders(request, headers, {
  redirect: '/login', // URL to redirect to (string or URL object)
  redirectStatus: 307, // 302 | 303 | 307 | 308 (default: 307 for GET, 303 for POST)
});
高级:使用 rewrites 进行组合

对于 rewrites 等高级用例,请使用更底层的 partitionAuthkitHeaders()applyResponseHeaders()

// proxy.ts (Next.js 16+) or middleware.ts (Next.js ≤15)
import { NextRequest, NextResponse } from 'next/server';
import { authkit, partitionAuthkitHeaders, applyResponseHeaders } from '@workos-inc/authkit-nextjs';

export default async function proxy(request: NextRequest) {
  // For Next.js ≤15, use: export default async function middleware(request: NextRequest) {
  const { headers } = await authkit(request);
  const { requestHeaders, responseHeaders } = partitionAuthkitHeaders(request, headers);

  // Create your own response (rewrite, etc.)
  const response = NextResponse.rewrite(new URL('/app/dashboard', request.url), {
    request: { headers: requestHeaders },
  });

  // Apply AuthKit response headers (Set-Cookie, etc.)
  applyResponseHeaders(response, responseHeaders);

  return response;
}
内部请求头参考

AuthKit 使用内部请求头在代理/中间件与服务器组件之间传递数据。这些请求头由上述辅助函数自动处理,但了解它们有助于调试。

请求头(传递给服务器组件,绝不会发送到浏览器):

请求头用途
x-workos-middleware指示 AuthKit 代理/中间件处于活动状态的标志。withAuth() 功能所需。
x-workos-session加密的会话数据。包含用户信息、访问令牌和刷新令牌。
x-url当前请求 URL。用于登录后的重定向和生成登录 URL。
x-redirect-uriOAuth 回调 URI。由 getAuthorizationUrl() 用于 OAuth 流程。
x-sign-up-paths配置为触发注册而非登录流程的路径。

安全性: 这些请求头包含敏感的会话数据。handleAuthkitHeaders() 辅助函数确保它们被转发到您的页面(以便 withAuth() 正常工作),但绝不会泄露到浏览器。客户端注入的 x-workos-* 请求头会被剥离并替换为可信值。

响应头(可安全地发送到浏览器):

标头用途
Set-Cookie会话 Cookie(例如 wos-session)。多个 Cookie 会被正确追加。
Cache-Control缓存指令。当存在 Cookie 时,自动设置为 no-store
Vary缓存变体键。合并时,值会进行去重。
WWW-Authenticate401 响应的身份验证质询(API 身份验证流程)。
Proxy-Authenticate代理身份验证的身份验证质询。
Link分页、预加载提示等。
x-middleware-cacheNext.js 代理/中间件结果缓存。设置为 no-cache 以防止陈旧响应。

仅这些允许列表中的标头会被转发到浏览器。来自 authkit() 的其他任何标头(包括未来的 x-workos-* 标头)出于安全考虑会被过滤掉。

用法

使用 AuthKitProvider 包裹您的应用

使用 AuthKitProvider 包裹您的应用布局,它提供客户端身份验证方法,并为身份验证边缘情况添加保护。

import { AuthKitProvider } from '@workos-inc/authkit-nextjs/components';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <AuthKitProvider>{children}</AuthKitProvider>
      </body>
    </html>
  );
}

使用服务端认证数据进行优化

为避免在挂载时调用服务器操作,您可以将初始认证数据从服务器传递到 AuthKitProvider

import { AuthKitProvider } from '@workos-inc/authkit-nextjs/components';
import { withAuth } from '@workos-inc/authkit-nextjs';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  // Fetch auth data on the server
  const auth = await withAuth();

  // Remove the accessToken from the auth object as it is not needed on the client side
  const { accessToken, ...initialAuth } = auth;

  return (
    <html lang="en">
      <body>
        <AuthKitProvider initialAuth={initialAuth}>{children}</AuthKitProvider>
      </body>
    </html>
  );
}

在服务器组件中获取当前用户

对于需要显示已登录和未登录视图的页面,请使用 withAuth 从 WorkOS 获取用户会话。

import Link from 'next/link';
import { getSignInUrl, getSignUpUrl, withAuth, signOut } from '@workos-inc/authkit-nextjs';

export default async function HomePage() {
  // Retrieves the user from the session or returns `null` if no user is signed in
  const { user } = await withAuth();

  if (!user) {
    // Get the URL to redirect the user to AuthKit to sign in
    const signInUrl = await getSignInUrl();

    // Get the URL to redirect the user to AuthKit to sign up
    const signUpUrl = await getSignUpUrl();

    // You can also pass custom state data through the auth flow
    const signInUrlWithState = await getSignInUrl({
      state: JSON.stringify({
        teamId: 'team_123',
        referrer: 'homepage',
      }),
    });

    return (
      <>
        <Link href={signInUrl}>Log in</Link>
        <Link href={signUpUrl}>Sign Up</Link>
      </>
    );
  }

  return (
    <form
      action={async () => {
        'use server';
        await signOut();
      }}
    >
      <p>Welcome back {user?.firstName && `, ${user?.firstName}`}</p>
      <button type="submit">Sign out</button>
    </form>
  );
}

在客户端组件中获取当前用户

对于客户端组件,使用 useAuth 钩子来获取当前用户会话。

'use client';
// Note the updated import path
import { useAuth } from '@workos-inc/authkit-nextjs/components';

export default function MyComponent() {
  // Retrieves the user from the session or returns `null` if no user is signed in
  const { user, loading } = useAuth();

  if (loading) {
    return <div>Loading...</div>;
  }

  return <div>{user?.firstName}</div>;
}

评估功能标志

在 AuthKit Next.js 中,有两种方式可以评估功能标志:

  • 当你需要获取已登录用户的简单会话范围启用标志列表时,使用 feature_flags 访问令牌声明。
  • 当你希望在不将每个活动标志存储在用户会话 Cookie 中的情况下进行服务端评估时,使用功能标志运行时客户端。

选项 1:使用 feature_flags 声明

在需要访问已认证用户当前活动功能标志,且你的环境包含 feature_flags 访问令牌声明的情况下,使用 withAuth 从 WorkOS 会话中检索这些标志。

const { featureFlags } = await withAuth();

对于小型标志集,这种方式很方便,因为标志与用户的会话一起可用。标志更改会在用户下次登录或会话刷新时生效。

选项 2:使用运行时客户端

当您的应用拥有许多功能标志、当 feature_flags 声明使访问令牌过大,或者当您需要独立于用户会话保持同步的服务器端标志评估时,请使用运行时客户端。运行时客户端在内存中保留标志配置,并在后台同步更改,因此请为每个服务器进程创建一个共享实例,而不是为每个请求创建一个客户端。

import { getFeatureFlagsRuntimeClient, withAuth } from '@workos-inc/authkit-nextjs';

const featureFlags = getFeatureFlagsRuntimeClient();

export default async function DashboardPage() {
  const { user, organizationId } = await withAuth({ ensureSignedIn: true });

  try {
    await featureFlags.waitUntilReady({ timeoutMs: 5000 });
  } catch (error) {
    console.error('Feature flags client failed to initialize:', error);
  }

  const enabled = featureFlags.isEnabled('advanced-analytics', {
    userId: user.id,
    organizationId,
  });

  return enabled ? <AdvancedAnalytics /> : <BasicAnalytics />;
}

getFeatureFlagsRuntimeClient 辅助函数在当前服务器进程的每次调用中返回相同的运行时客户端。传递给 getFeatureFlagsRuntimeClient(options) 的选项仅在客户端首次创建时使用。

要求身份验证

对于必须登录用户的页面,您可以使用 ensureSignedIn 选项:

// Server component
const { user } = await withAuth({ ensureSignedIn: true });

// Client component
const { user, loading } = useAuth({ ensureSignedIn: true });

启用 ensureSignedIn 后,如果用户未通过身份验证就尝试访问该页面,他们将被重定向到 AuthKit。

重新身份验证

对于敏感操作,您可能希望要求用户 最近 已通过身份验证,而不仅仅是拥有会话。使用 checkRecentAuth 读取访问令牌的 auth_time 声明,并决定是否强制重新身份验证。

checkRecentAuth 仅返回数据,从不重定向,因此可以安全地作为服务器操作或服务器组件中的执行步骤进行调用。它失败时关闭:没有可用 auth_time 的会话(或未登录的用户)将被报告为 isStale: true

'use server';

import { checkRecentAuth, getSignInUrl } from '@workos-inc/authkit-nextjs';
import { redirect } from 'next/navigation';

export async function deleteAccount() {
  // Require that the user authenticated within the last 5 minutes
  const { isStale } = await checkRecentAuth({ maxAge: 300 });

  if (isStale) {
    // Send the user through re-authentication. Passing `maxAge` forwards the OIDC
    // `max_age` parameter, so AuthKit forces a fresh login when the most recent
    // authentication is older than `maxAge` seconds.
    redirect(await getSignInUrl({ maxAge: 300 }));
  }

  // ...perform the sensitive action
}

对于客户端组件,使用 useRecentAuth 钩子在 UI 中反映新鲜度。这仅用于展示 — 始终在服务器端使用 checkRecentAuth 来强制实施新鲜度。

'use client';

import { useRecentAuth } from '@workos-inc/authkit-nextjs/components';

function SensitiveActionButton() {
  const { loading, isStale } = useRecentAuth({ maxAge: 300 });

  if (loading) {
    return <button disabled>Loading…</button>;
  }

  return <button>{isStale ? 'Re-authenticate to continue' : 'Delete account'}</button>;
}

刷新会话

在服务器操作或路由处理程序中使用 refreshSession 方法以获取最新的会话详情,包括用户角色或权限的任何变更。

可以将 organizationId 参数传递给 refreshSession,以将会话切换到不同的组织。如果当前会话未获得下一个组织的授权,将返回相应的 authentication error

在客户端组件中,您可以使用 refreshAuth 钩子来刷新会话。

'use client';

import { useAuth } from '@workos-inc/authkit-nextjs/components';
import React, { useEffect } from 'react';

export function SwitchOrganizationButton() {
  const { user, organizationId, loading, refreshAuth } = useAuth();

  useEffect(() => {
    // This will log out the new organizationId after refreshing the session
    console.log('organizationId', organizationId);
  }, [organizationId]);

  if (loading) {
    return <div>Loading...</div>;
  }

  const handleRefreshSession = async () => {
    const result = await refreshAuth({
      // Provide the organizationId to switch to
      organizationId: 'org_123',
    });
    if (result?.error) {
      console.log('Error refreshing session:', result.error);
    }
  };

  if (user) {
    return <button onClick={handleRefreshSession}>Refresh session</button>;
  } else {
    return <div>Not signed in</div>;
  }
}

访问令牌管理

useAccessToken Hook

本库提供了一个 useAccessToken hook,用于客户端访问令牌管理,并具备自动刷新功能。

功能
  • 在过期前自动刷新令牌
  • 支持手动刷新
  • 加载和错误状态
  • 与主认证会话同步
  • 防止竞态条件
何时使用

当您需要直接访问 JWT 令牌以执行以下操作时,请使用此 hook:

  • 发起经过身份验证的 API 调用
  • 配置依赖外部认证的库
  • 实现自定义认证逻辑
基本用法
function ApiClient() {
  const { accessToken, loading, error, refresh } = useAccessToken();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!accessToken) return <div>Not authenticated</div>;

  return (
    <div>
      <p>Token available: {accessToken.substring(0, 10)}...</p>
      <button onClick={refresh}>Refresh token</button>
    </div>
  );
}
API 参考
属性类型描述
accessTokenstring | undefined当前访问令牌
loadingboolean当令牌正在获取或刷新时为 True
errorError | null令牌获取/刷新期间的错误,或 null
refresh() => Promise<string | undefined>手动刷新令牌
与 useAuth 集成

useAccessToken 钩子会自动与主认证会话同步。当您从 useAuth 调用 refreshAuth() 时,访问令牌将相应更新。同样,使用 useAccessToken 中的 refresh() 方法将更新整个认证会话。

安全注意事项

JWT 令牌是敏感凭证,应谨慎处理:

  • 仅在必要时使用令牌
  • 不要将令牌存储在 localStorage 或 sessionStorage 中
  • 注意在应用程序状态中暴露令牌

通过认证传递自定义状态

您可以使用 state 参数通过认证流程传递自定义状态数据。state 参数是一个字符串值,通过 OAuth 传递并在回调中返回。要传递复杂数据,请将其序列化为 JSON:

// When generating sign-in/sign-up URLs, serialize your data as JSON
const signInUrl = await getSignInUrl({
  state: JSON.stringify({
    teamId: 'team_123',
    feature: 'billing',
    referrer: 'pricing-page',
    timestamp: Date.now(),
  }),
});

// The state data is available in the callback handler
export const GET = handleAuth({
  onSuccess: async ({ user, state }) => {
    // Parse the state string back to an object
    const customData = state ? JSON.parse(state) : null;

    // Access your custom state data
    if (customData?.teamId) {
      await addUserToTeam(user.id, customData.teamId);
    }

    if (customData?.feature) {
      await trackFeatureActivation(user.id, customData.feature);
    }

    // Track where the user came from
    await analytics.track('sign_in_completed', {
      userId: user.id,
      referrer: customData?.referrer,
      timestamp: customData?.timestamp,
    });
  },
});

注意state 参数是 OAuth 2.0 (RFC 6749) 中定义的不透明字符串。如果需要传递结构化数据,您必须使用 JSON.stringify() 自行序列化,并在回调中使用 JSON.parse() 进行解析。

这适用于:

  • 跟踪用户旅程和推荐来源
  • 维护用户在认证前正在尝试执行的操作的上下文
  • 实现自定义入门流程
  • 分析和归因跟踪

会话刷新回调

直接使用 authkit 函数时,您可以提供回调以在会话刷新时收到通知:

const { session, headers } = await authkit(request, {
  onSessionRefreshSuccess: async ({ accessToken, user, impersonator }) => {
    // Log successful refresh
    console.log(`Session refreshed for ${user.email}.`);
  },
  onSessionRefreshError: async ({ error, request }) => {
    // Log refresh failure
    console.error('Session refresh failed:', error);
    // Notify monitoring system
    await notifyMonitoring('session_refresh_failed', {
      url: request.url,
      error: error.message,
    });
  },
});

这些回调提供了一种在代理/中间件中刷新会话时执行副作用的方式。常见用例包括:

  • 记录认证事件
  • 更新最后活动时间戳
  • 触发特定组织的数据预取
  • 记录失败的刷新尝试

代理 / 中间件认证

该库的默认行为是通过 withAuth 方法按页面请求认证。在某些用例中,您可能不希望调用 withAuth(例如,您的页面不需要用户数据),或者您更倾向于采用“默认安全”的方法,即代理/中间件匹配器中定义的每个路由都受到保护,除非另有说明。在这些情况下,您可以选择使用 middlewareAuth 代替:

import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

export default authkitMiddleware({
  middlewareAuth: {
    enabled: true,
    unauthenticatedPaths: ['/', '/about'],
  },
});

// Match against pages that require auth
// Leave this out if you want auth on every resource (including images, css etc.)
export const config = { matcher: ['/', '/admin/:path*', '/about'] };

在上面的示例中,/admin 页面要求用户已登录,而 //about 可以在未登录的情况下访问。

unauthenticatedPaths 使用与 Next.js matcher 相同的 glob 逻辑。

Eager auth

eagerAuth 选项启用初始页面加载时对认证令牌的同步访问,某些直接通过 WorkOS 验证令牌的第三方服务需要此功能。启用后,令牌可立即使用,无需异步获取。

How it works

当设置 eagerAuth: true 时,proxy/middleware 会将访问令牌临时存储在一个短生命周期的 cookie(30 秒)中,该 cookie:

  • 仅在初始页面加载时设置(不适用于 API 或 prefetch 请求)
  • 由客户端立即消费并删除
  • 在首次渲染时同步可用

Usage

在您的 proxy/middleware 配置中启用 eager auth:

import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

export default authkitMiddleware({
  eagerAuth: true,
});

然后在您的客户端组件中同步访问该令牌:

'use client';

import { useAccessToken } from '@workos-inc/authkit-nextjs/components';

function MyComponent() {
  const { getAccessToken } = useAccessToken();

  async function handleClick() {
    // Token is available immediately on initial page load
    const token = await getAccessToken();

    // Use with third-party services that need immediate token access
    if (token) {
      // Initialize your third-party client with the token
      thirdPartyClient.authenticate(token);
    }
  }

  return <button onClick={handleClick}>Authenticate</button>;
}

安全注意事项

Eager auth 会使令牌在短暂时间内(30 秒窗口)可通过 JavaScript 访问,以实现同步访问。这是许多身份验证库使用的常见模式,在标准 XSS 防护下通常是安全的。

最佳实践:

  • 如果处理敏感数据,请实施内容安全策略 (CSP)
  • 审查已认证页面上的第三方脚本
  • 当不需要同步访问时,使用标准的 getAccessToken() 方法

何时使用:

  • 需要同步令牌访问的第三方服务
  • 需要立即身份验证的实时功能
  • 当希望避免在初始渲染时出现加载状态时

何时使用标准异步令牌:

  • 大多数允许短暂加载状态的 API 调用
  • 当不需要在页面加载时立即访问令牌时

主动会话刷新

当访问令牌距离其过期时间在一个缓冲范围内时,代理/中间件会主动刷新会话,这与客户端令牌存储已使用的缓冲一致:60 秒,或者对于总生命周期为 5 分钟或更短的令牌为 30 秒。这确保了传递给 withAuth() 然后传递给服务器端消费者(Server Component fetch、验证令牌的 API 调用)的令牌不会因渲染延迟、网络往返或时钟偏差而在请求中途过期。

使用 refreshBufferSeconds 选项来调整缓冲,或将其设置为 0 以禁用主动刷新,仅在令牌过期后刷新:

export default authkitProxy({
  refreshBufferSeconds: 120,
});

如果主动刷新失败(例如,并发请求已经轮换了一次性刷新令牌),则使用当前访问令牌提供服务,前提是该令牌在该时刻仍然有效;只要访问令牌仍然有效,会话就永远不会被销毁。如果令牌在刷新尝试失败期间过期,则清除会话,并将请求重定向到登录页面,与任何过期会话的处理方式相同。

请注意,当多个请求同时落在缓冲窗口内时,只有一个请求会赢得刷新;其他每个请求在被使用当前令牌提供服务之前,可能需要向 WorkOS 支付一次刷新失败的往返时间。这会在缓冲窗口期间为这些请求增加一些延迟,但不会导致用户可见的故障。

注销

使用 signOut 方法注销当前登录用户,并重定向到应用程序的默认注销 URI。注销 URI 在 WorkOS 仪表板设置的“Redirect”下设置。

若要使用非默认注销 URI,可以使用 returnTo 参数。

await signOut({ returnTo: 'https://your-app.com/signed-out' });

可视化身份冒充

在您的应用中渲染 Impersonation 组件,以便在有人 冒充用户] 时清晰可见。 该组件将显示一个包含被冒充用户信息的框架,以及一个停止冒充的按钮。

[!IMPORTANT] 身份冒充需要在您的 WorkOS 仪表板中配置 Sign-in URL。请参阅 Sign-in URL 设置说明。如果没有配置,从 WorkOS 仪表板进行的身份冒充将因 Missing required auth parameter 错误而失败。

import { Impersonation, AuthKitProvider } from '@workos-inc/authkit-nextjs/components';

export default function App() {
  return (
    <div>
      <AuthKitProvider>
        <Impersonation />
        {/* Your app content */}
      </AuthKitProvider>
    </div>
  );
}

获取访问令牌

有时直接获取访问令牌会很有用,例如向其他服务发起 API 请求。

import { withAuth } from '@workos-inc/authkit-nextjs';

export default async function HomePage() {
  const { accessToken } = await withAuth();

  if (!accessToken) {
    return <div>Not signed in</div>;
  }

  const serviceData = await fetch('/api/path', {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  return <div>{serviceData}</div>;
}

注册路径

可以将 signUpPaths 选项传递给 authkitMiddleware,以指定在重定向到 AuthKit 时应使用“注册”屏幕提示的路径。这对于希望将强制要求身份验证的路径视为注册页面的场景非常有用。

import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

export default authkitMiddleware({
  signUpPaths: ['/account/sign-up', '/dashboard/:path*'],
});

验证 API 密钥

在您的应用程序的公共 API 端点中使用 validateApiKey 函数来解析 Bearer Authentication 请求头,并使用 WorkOS 验证 API key

import { NextResponse } from 'next/server';
import { validateApiKey } from '@workos-inc/authkit-nextjs';

export async function GET() {
  const { apiKey } = await validateApiKey();

  if (!apiKey) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  return NextResponse.json({ success: true });
}

高级:直接访问 WorkOS 客户端

对于高级用例或辅助方法未涵盖的功能,您可以直接访问底层的 WorkOS 客户端:

import { getWorkOS } from '@workos-inc/authkit-nextjs';

// Get the configured WorkOS client instance
const workos = getWorkOS();

// Use any WorkOS SDK method
const organizations = await workos.organizations.listOrganizations({
  limit: 10,
});

高级:自定义身份验证流程

虽然标准身份验证流程会自动处理会话管理,但某些用例需要手动创建和存储会话。这对于电子邮件验证或令牌交换等自定义身份验证流程非常有用。

对于这些场景,您可以使用 saveSession 函数:

import { saveSession } from '@workos-inc/authkit-nextjs';
import { getWorkOS } from '@workos-inc/authkit-nextjs';

// Example: Email verification flow
async function handleEmailVerification(req) {
  const { code } = await req.json();

  // Authenticate with the WorkOS API directly
  const authResponse = await getWorkOS().userManagement.authenticateWithEmailVerification({
    clientId: process.env.WORKOS_CLIENT_ID,
    code,
  });

  // Save the session data to a cookie
  await saveSession(
    {
      accessToken: authResponse.accessToken,
      refreshToken: authResponse.refreshToken,
      user: authResponse.user,
      impersonator: authResponse.impersonator,
    },
    req,
  );

  return Response.redirect('/dashboard');
}

[!NOTE] 这是一个面向特定集成场景的高级 API,例如使用自托管 AuthKit 的用户。如果您使用的是托管版 AuthKit,则无需使用此 API。

saveSession 函数的第二个参数可以接受一个 NextRequest 对象或一个 URL 字符串。

// With NextRequest
await saveSession(session, req);

// With URL string
await saveSession(session, 'https://example.com/callback');

CDN 部署与缓存

AuthKit 自动实施缓存安全措施,以保护 CDN 环境中的会话免受泄露。在部署到 AWS(使用 SST/OpenNext)、Cloudflare 或其他 CDN 配置时,这一点尤为重要。

工作原理

该库会自动为所有经过身份验证的请求设置适当的缓存头:

  • Cache-Control: private, no-cache, no-store, must-revalidate, max-age=0 - 通过多个指令进行积极的缓存预防
  • Pragma: no-cache - HTTP/1.0 兼容性
  • Expires: 0 - HTTP/1.0 缓存过期
  • Vary: Cookie - 确保 CDN 区分不同用户(纵深防御)
  • x-middleware-cache: no-cache - 防止 Next.js 代理/中间件结果缓存

在以下情况下,这些头会自动应用:

  • 请求中存在会话 Cookie
  • 检测到 Authorization 头
  • 存在活动的已认证会话

性能考量

已认证页面: 不会在 CDN 层面被缓存,并且始终会访问您的源服务器。这是基于会话的身份验证的正确且安全的行为。

公共页面: 不受这些安全措施的影响。没有认证上下文的公共路由仍然可以正常缓存。

调试

要启用调试日志,请在初始化代理/中间件时启用调试标志。

import { authkitMiddleware } from '@workos-inc/authkit-nextjs';

export default authkitMiddleware({ debug: true });

安全性

PKCE 和 CSRF 保护

本库在每个授权请求中使用 PKCE(代码交换证明密钥)和密封(加密)的 OAuth state 参数。该 state 包含根据 RFC 9700 用于 CSRF 保护的加密随机数,以及用于防止授权代码拦截的代码验证器。在登录期间,会设置一个包含密封 state 的短生命周期 wos-auth-verifier cookie。此 cookie 会在回调完成后自动清理。

[!NOTE] 升级到 v3: PKCE 现在始终启用。WORKOS_ENABLE_PKCE 环境变量不再需要,可以从您的配置中移除。

Cookie 要求

wos-auth-verifier cookie 必须从登录发起持续到回调。在回调时,库会验证 cookie 是否存在且与 URL state 参数匹配——这种双通道检查正是防止 CSRF 攻击的关键。

如果 cookie 缺失或不匹配,认证将失败,并出现以下错误之一:

  • Sign-in session could not be verified — cookie 未随回调请求返回。这通常发生在会话过期,或反向代理或 CDN 在重定向时剥离 Set-Cookie 头的情况下。
  • OAuth state mismatch — cookie 和 URL state 参数不匹配,表明可能存在 CSRF 攻击或 cookie 损坏。

[!IMPORTANT] 升级到 v3: 之前的版本在 cookie 缺失时会静默回退到仅验证 URL state 参数。由于该回退机制禁用了 CSRF 保护,因此已被移除。如果在升级后看到 Sign-in session could not be verified 错误,请确保在您的应用程序与用户浏览器之间的重定向中传递 Set-Cookie 头。

故障排除

从 WorkOS 仪表板进行模拟登录时出现 Missing required auth parameter

当 WorkOS 发起的流程(如仪表板模拟登录)直接重定向到您的回调 URL,而未经过您的应用程序的登录流程时,会出现此错误。由于该库在每个回调上都强制进行 PKCE/CSRF 验证,当缺少必需的 state 参数时,请求将被拒绝。

修复方法: 在您的 WorkOS 仪表板中配置 Sign-in URL,以便模拟登录流程首先通过您的应用程序路由,从而在重定向到 WorkOS 之前设置 PKCE/state。

使用 try/catch 块时出现 NEXT_REDIRECT 错误

withAuth({ ensureSignedIn: true }) 调用包裹在 try/catch 块中会导致 NEXT_REDIRECT 错误。这是因为如果未检测到会话,withAuth 会尝试将用户重定向到 AuthKit,而在 Next 中,重定向必须在 try/catch 之外调用

Module build failed: UnhandledSchemeError: Reading from "node:crypto" is not handled by plugins (Unhandled scheme)

如果您尝试从 authkit-nextjs 中导入服务端代码到客户端组件,可能会遇到此错误。您可能是在客户端组件中使用了 withAuth,而不是 useAuth 钩子。请将代码移至服务端组件,或使用 useAuth 钩子。