clerk-auth by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill clerk-auth包版本 : @clerk/nextjs@6.36.7, @clerk/backend@2.29.2, @clerk/clerk-react@5.59.2, @clerk/testing@1.13.26 重大变更 : 2025年11月 - API 版本 2025-11-10, 2024年10月 - Next.js v6 异步 auth() 最后更新 : 2026-01-09
为您的应用程序提供用户范围和组织范围的 API 密钥。零代码 UI 组件。
// 1. 添加用于自助式 API 密钥管理的组件
import { APIKeys } from '@clerk/nextjs'
export default function SettingsPage() {
return (
<div>
<h2>API 密钥</h2>
<APIKeys /> {/* 用户 API 密钥的完整 CRUD UI */}
</div>
)
}
后端验证:
import { verifyToken } from '@clerk/backend'
// API 密钥的验证方式与会话令牌类似
const { data, error } = await verifyToken(apiKey, {
secretKey: process.env.CLERK_SECRET_KEY,
authorizedParties: ['https://yourdomain.com'],
})
// 检查令牌类型
if (data?.tokenType === 'api_key') {
// 处理 API 密钥认证
}
clerkMiddleware 令牌类型:
// v6.36.0+: 中间件可以区分令牌类型
clerkMiddleware((auth, req) => {
const { userId, tokenType } = auth()
if (tokenType === 'api_key') {
// API 密钥认证 - 编程式访问
} else if (tokenType === 'session_token') {
// 常规会话 - Web UI 访问
}
})
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
定价 (Beta 版 = 免费):
⚠️ 重大变更 : Next.js 16 因关键安全漏洞 (2025年3月披露的 CVE) 更改了中间件文件名。
背景 : 2025年3月的漏洞 (影响 Next.js 11.1.4-15.2.2) 允许攻击者通过添加单个 HTTP 标头 x-middleware-subrequest: true 完全绕过基于中间件的授权。这影响了所有身份验证库 (NextAuth、Clerk、自定义解决方案)。
为何重命名 : middleware.ts → proxy.ts 的更改不仅仅是表面上的 - 这是 Next.js 发出的信号,表明中间件优先的安全模式是危险的。未来的身份验证实现不应仅依赖中间件进行授权。
Next.js 15 及更早版本: middleware.ts
Next.js 16+: proxy.ts
Next.js 16 的正确设置:
// src/proxy.ts (不是 middleware.ts!)
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware()
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
}
最低版本 : Next.js 16 需要 @clerk/nextjs@6.35.0+ (修复了 Turbopack 构建错误和退出登录时的缓存失效问题)。
管理员可以将密码标记为已泄露并强制重置:
import { clerkClient } from '@clerk/backend'
// 强制用户重置密码
await clerkClient.users.updateUser(userId, {
passwordDigest: 'compromised', // 下次登录时触发重置
})
仪表板现在包含组织创建指标以及按名称/slug/日期过滤的功能。
影响: 使用 Clerk 计费/商务 API 的应用程序
关键变更:
端点 URL: /commerce/ → /billing/ (30+ 个端点)
GET /v1/commerce/plans → GET /v1/billing/plans
GET /v1/commerce/statements → GET /v1/billing/statements
POST /v1/me/commerce/checkouts → POST /v1/me/billing/checkouts
字段术语: payment_source → payment_method
// 旧 (已弃用)
{ payment_source_id: "...", payment_source: {...} }
// 新 (必需)
{ payment_method_id: "...", payment_method: {...} }
移除的字段: 计划响应不再包含:
amount, amount_formatted (改用 fee.amount)currency, currency_symbol (使用费用对象)payer_type (使用 for_payer_type)annual_monthly_amount, annual_amount移除的端点:
空值处理: 明确规则 - null 表示"不存在",省略表示"不声明存在"
迁移: 更新 SDK 至 v6.35.0+,该版本包含对 API 版本 2025-11-10 的支持。
影响: 所有使用 auth() 的 Next.js 服务器组件
// ❌ 旧 (v5 - 同步)
const { userId } = auth()
// ✅ 新 (v6 - 异步)
const { userId } = await auth()
同样影响: auth.protect() 现在在中间件中是异步的
// ❌ 旧 (v5)
auth.protect()
// ✅ 新 (v6)
await auth.protect()
兼容性: 支持 Next.js 15、16。默认静态渲染。
自定义 OIDC 提供商和社交连接现在支持 PKCE (Proof Key for Code Exchange),以增强无法安全存储客户端密钥的原生/移动应用程序的安全性。
使用场景: 移动应用、原生应用、无法安全存储密钥的公共客户端。
当用户从未识别的设备登录时自动进行二次身份验证:
工作原理: 当检测到来自新设备的登录时,Clerk 会自动提示进行额外验证 (邮箱验证码、备用验证码)。
@clerk/nextjs v6.35.2+ 包含针对 Next.js 16 在退出登录期间的缓存失效改进。
模式:
import { auth } from '@clerk/nextjs/server'
export default async function Page() {
const { userId } = await auth() // ← 必须 await
if (!userId) {
return <div>未授权</div>
}
return <div>用户 ID: {userId}</div>
}
关键: 始终设置 authorizedParties 以防止 CSRF 攻击
import { verifyToken } from '@clerk/backend'
const { data, error } = await verifyToken(token, {
secretKey: c.env.CLERK_SECRET_KEY,
// 必需: 防止 CSRF 攻击
authorizedParties: ['https://yourdomain.com'],
})
原因: 没有 authorizedParties,攻击者可以使用来自其他域的有效令牌。
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
// 定义受保护的路由
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/api/private(.*)',
])
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
export default clerkMiddleware(async (auth, req) => {
// 保护路由
if (isProtectedRoute(req)) {
await auth.protect() // 重定向未认证用户
}
// 要求特定权限
if (isAdminRoute(req)) {
await auth.protect({
role: 'org:admin', // 需要组织管理员角色
})
}
})
| 选项 | 类型 | 描述 |
|---|---|---|
debug | boolean | 启用调试日志 |
jwtKey | string | 用于无网络验证的 JWKS 公钥 |
clockSkewInMs | number | 令牌时间偏差 (默认: 5000ms) |
organizationSyncOptions | object | 基于 URL 的组织激活 |
signInUrl | string | 自定义登录 URL |
signUpUrl | string | 自定义注册 URL |
⚠️ 仅限 Next.js : 此功能目前仅适用于 Next.js 中的 clerkMiddleware()。由于 Sec-Fetch-Dest 标头检查,它不适用于其他运行时 (Cloudflare Workers、Express 等) 中的 authenticateRequest()。
来源 : GitHub Issue #7178
clerkMiddleware({
organizationSyncOptions: {
organizationPatterns: ['/orgs/:slug', '/orgs/:slug/(.*)'],
personalAccountPatterns: ['/personal', '/personal/(.*)'],
},
})
import { Webhook } from 'svix'
export async function POST(req: Request) {
const payload = await req.text()
const headers = {
'svix-id': req.headers.get('svix-id')!,
'svix-timestamp': req.headers.get('svix-timestamp')!,
'svix-signature': req.headers.get('svix-signature')!,
}
const wh = new Webhook(process.env.CLERK_WEBHOOK_SIGNING_SECRET!)
try {
const event = wh.verify(payload, headers)
// 处理事件
return Response.json({ success: true })
} catch (err) {
return Response.json({ error: '无效签名' }, { status: 400 })
}
}
| 事件 | 触发条件 |
|---|---|
user.created | 新用户注册 |
user.updated | 用户资料变更 |
user.deleted | 用户账户删除 |
session.created | 新登录 |
session.ended | 退出登录 |
organization.created | 新组织创建 |
organization.membership.created | 用户加入组织 |
⚠️ 重要: Webhook 路由必须是公开的 (无需认证)。添加到中间件排除列表:
const isPublicRoute = createRouteMatcher([
'/api/webhooks/clerk(.*)', // Clerk webhooks 是公开的
])
clerkMiddleware((auth, req) => {
if (!isPublicRoute(req)) {
auth.protect()
}
})
| 组件 | 用途 |
|---|---|
<SignIn /> | 完整登录流程 |
<SignUp /> | 完整注册流程 |
<SignInButton /> | 触发登录模态框 |
<SignUpButton /> | 触发注册模态框 |
<SignedIn> | 仅在认证时渲染 |
<SignedOut> | 仅在未认证时渲染 |
<UserButton /> | 包含退出登录的用户菜单 |
<UserProfile /> | 完整的资料管理 |
<OrganizationSwitcher /> | 在组织间切换 |
<OrganizationProfile /> | 组织设置 |
<CreateOrganization /> | 创建新组织 |
<APIKeys /> | API 密钥管理 (新功能) |
| Hook | 返回内容 |
|---|---|
useAuth() | { userId, sessionId, isLoaded, isSignedIn, getToken } |
useUser() | { user, isLoaded, isSignedIn } |
useClerk() | 包含方法的 Clerk 实例 |
useSession() | 当前会话对象 |
useOrganization() | 当前组织上下文 |
useOrganizationList() | 用户的所有组织 |
问题 : 浏览器 Cookie 限制为 4KB。Clerk 的默认声明占用约 2.8KB,为自定义声明留下 1.2KB。
⚠️ 开发说明 : 在 Vite 开发模式下测试自定义 JWT 声明时,您可能会遇到 "431 请求标头字段过大" 错误。这是由 URL 中的 Clerk 握手令牌超过 Vite 的 8KB 限制引起的。解决方案请参见 Issue #11。
解决方案:
// ✅ 良好: 最小化声明
{
"user_id": "{{user.id}}",
"email": "{{user.primary_email_address}}",
"role": "{{user.public_metadata.role}}"
}
// ❌ 不良: 超出限制
{
"bio": "{{user.public_metadata.bio}}", // 6KB 字段
"all_metadata": "{{user.public_metadata}}" // 整个对象
}
最佳实践 : 将大数据存储在数据库中,JWT 中仅包含标识符/角色。
| 类别 | 短代码 | 示例 |
|---|---|---|
| 用户 ID 与姓名 | {{user.id}}, {{user.first_name}}, {{user.last_name}}, {{user.full_name}} | "John Doe" |
| 联系方式 | {{user.primary_email_address}}, {{user.primary_phone_address}} | "john@example.com" |
| 个人资料 | {{user.image_url}}, {{user.username}}, {{user.created_at}} | "https://..." |
| 验证状态 | {{user.email_verified}}, {{user.phone_number_verified}} | true |
| 元数据 | {{user.public_metadata}}, {{user.public_metadata.FIELD}} | {"role": "admin"} |
| 组织 | org_id, org_slug, org_role (在 sessionClaims 中) | "org:admin" |
高级功能:
"{{user.last_name}} {{user.first_name}}""{{user.public_metadata.role || 'user'}}""{{user.public_metadata.profile.interests}}"测试邮箱 (不发送邮件,固定 OTP):
john+clerk_test@example.com
jane+clerk_test@gmail.com
测试电话号码 (不发送短信,固定 OTP):
+12015550100
+19735550133
固定 OTP 验证码 : 424242 (适用于所有测试凭据)
脚本 (scripts/generate-session-token.js):
# 生成令牌
CLERK_SECRET_KEY=sk_test_... node scripts/generate-session-token.js
# 创建新的测试用户
CLERK_SECRET_KEY=sk_test_... node scripts/generate-session-token.js --create-user
# 每 50 秒自动刷新令牌
CLERK_SECRET_KEY=sk_test_... node scripts/generate-session-token.js --refresh
手动流程 :
POST /v1/usersPOST /v1/sessionsPOST /v1/sessions/{session_id}/tokensAuthorization: Bearer <token>安装 @clerk/testing 以自动管理测试令牌:
npm install -D @clerk/testing
全局设置 (global.setup.ts):
import { clerkSetup } from '@clerk/testing/playwright'
import { test as setup } from '@playwright/test'
setup('global setup', async ({}) => {
await clerkSetup()
})
测试文件 (auth.spec.ts):
import { setupClerkTestingToken } from '@clerk/testing/playwright'
import { test } from '@playwright/test'
test('注册', async ({ page }) => {
await setupClerkTestingToken({ page })
await page.goto('/sign-up')
await page.fill('input[name="emailAddress"]', 'test+clerk_test@example.com')
await page.fill('input[name="password"]', 'TestPassword123!')
await page.click('button[type="submit"]')
// 使用固定 OTP 验证
await page.fill('input[name="code"]', '424242')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('/dashboard')
})
此技能预防 15 个已记录的问题 :
错误 : "缺少 Clerk 密钥或 API 密钥" 来源 : https://stackoverflow.com/questions/77620604 预防 : 始终在 .env.local 中设置或通过 wrangler secret put 设置
错误 : "apiKey 已弃用,请使用 secretKey" 来源 : https://clerk.com/docs/upgrade-guides/core-2/backend 预防 : 在所有调用中将 apiKey 替换为 secretKey
错误 : "没有可用的 JWK" 来源 : https://github.com/clerk/javascript/blob/main/packages/backend/CHANGELOG.md 预防 : 使用 @clerk/backend@2.17.2 或更高版本 (已修复)
错误 : 无错误,但存在 CSRF 漏洞 来源 : https://clerk.com/docs/reference/backend/verify-token 预防 : 始终设置 authorizedParties: ['https://yourdomain.com']
错误 : "找不到模块" 来源 : https://clerk.com/docs/upgrade-guides/core-2/backend 预防 : 更新 Core 2 的导入路径
错误 : 令牌超出大小限制 来源 : https://clerk.com/docs/backend-requests/making/custom-session-token 预防 : 保持自定义声明在 1.2KB 以下
错误 : "API 版本 v1 已弃用" 来源 : https://clerk.com/docs/upgrade-guides/core-2/backend 预防 : 使用最新的 SDK 版本 (API v2025-11-10)
错误 : "不能用作 JSX 组件" 来源 : https://stackoverflow.com/questions/79265537 预防 : 确保 React 19 兼容性,使用 @clerk/clerk-react@5.59.2+
错误 : "auth() 不是函数" 来源 : https://clerk.com/changelog/2024-10-22-clerk-nextjs-v6 预防 : 始终 await: const { userId } = await auth()
错误 : "缺少可发布密钥" 或密钥泄露 预防 : 使用正确的前缀 (NEXT_PUBLIC_, VITE_),切勿提交密钥
错误 : 登录时出现 "431 请求标头字段过大" 来源 : 在测试自定义 JWT 声明时,Vite 开发模式中常见 原因 : URL 中的 Clerk __clerk_handshake 令牌超过 Vite 的 8KB 标头限制 预防 :
添加到 package.json:
{
"scripts": {
"dev": "NODE_OPTIONS='--max-http-header-size=32768' vite"
}
}
临时解决方案 : 清除浏览器缓存,退出登录,重新登录
原因 : Clerk 开发令牌比生产环境大;自定义 JWT 声明增加了握手令牌的大小
注意 : 这与问题 #6 (会话令牌大小) 不同。问题 #6 是关于 Cookie (1.2KB),而这是关于开发模式中的 URL 参数 (8KB → 32KB)。
错误 : 在客户端/服务器之间共享用户工具时出现 TypeScript 错误 来源 : GitHub Issue #2176 发生原因 : useUser() 返回 UserResource (客户端),其属性与 currentUser() 返回的 User (服务器端) 不同。客户端有 fullName、primaryEmailAddress 对象;服务器端有 primaryEmailAddressId 和 privateMetadata。 预防 : 仅使用共享属性,或为客户端与服务器上下文创建单独的工具函数。
// ✅ 正确: 使用两者都存在的属性
const primaryEmailAddress = user.emailAddresses.find(
({ id }) => id === user.primaryEmailAddressId
)
// ✅ 正确: 分离类型
type ClientUser = ReturnType<typeof useUser>['user']
type ServerUser = Awaited<ReturnType<typeof currentUser>>
错误 : 使用多个令牌类型的 authenticateRequest() 时出现 "token-type-mismatch" 来源 : GitHub Issue #7520 发生原因 : 当使用带有多个 acceptsToken 值 (例如 ['session_token', 'api_key']) 的 authenticateRequest() 时,Clerk 错误地抛出 token-type-mismatch 错误。 预防 : 升级到 @clerk/backend@2.29.2+ (修复已在快照中提供,即将发布)。
// 在 @clerk/backend@2.29.2+ 中现在可以正常工作
const result = await authenticateRequest(request, {
acceptsToken: ['session_token', 'api_key'], // 已修复!
})
错误 : 服务器因 URL 解析错误而崩溃 来源 : GitHub Issue #7275 发生原因 : 内部的 deriveUrlFromHeaders() 函数执行不安全的 URL 解析,当接收到标头中的畸形 URL (例如 x-forwarded-host: 'example.com[invalid]') 时,会导致整个服务器崩溃。这是一个拒绝服务漏洞。 预防 : 升级到 @clerk/backend@2.29.0+ (已修复)。
错误 : 无 - 用于边缘情况处理的可选参数 来源 : Changelog @clerk/nextjs@6.32.0 存在原因 : 在某些流程中 (例如凭证填充防御的二次认证),会话可能处于 pending 状态。默认情况下,待处理会话被视为已退出登录 (用户为 null)。 用法 : 设置 treatPendingAsSignedOut: false 以将待处理视为已登录 (在 @clerk/nextjs@6.32.0+ 中可用)。
// 默认: 待处理 = 已退出登录
const user = await currentUser() // 如果状态为 'pending' 则为 null
// 将待处理视为已登录
const user = await currentUser({ treatPendingAsSignedOut: false }) // 如果待处理则有定义
背景 : Clerk 在 2025年5月至6月期间经历了 3 次重大服务中断,归因于 Google Cloud Platform (GCP) 中断。2025年6月26日的中断持续了 45 分钟 (UTC 时间 6:16-7:01),影响了所有 Clerk 客户。
缓解策略 :
监控 Clerk 状态 获取实时更新
在 Clerk API 不可用时实施优雅降级
尽可能在本地缓存身份验证令牌
对于现有会话,使用 jwtKey 选项进行无网络验证:
clerkMiddleware({ jwtKey: process.env.CLERK_JWT_KEY, // 允许离线令牌验证 })
注意 : 在完全中断期间,无法创建新会话 (身份验证需要 Clerk API)。但是,如果您使用 jwtKey 本地验证 JWT,现有会话可以继续工作。Clerk 承诺探索多云冗余以减少单一供应商依赖风险。
/clerk/clerk-docs最新版本 (2025年11月22日):
{
"dependencies": {
"@clerk/nextjs": "^6.36.7",
"@clerk/clerk-react": "^5.59.2",
"@clerk/backend": "^2.29.2",
"@clerk/testing": "^1.13.26"
}
}
令牌效率 :
预防的错误 : 15 个已记录的问题,附带精确解决方案 关键价值 : API 密钥 Beta 版、Next.js 16 proxy.ts (附带 2025年3月 CVE 背景)、clerkMiddleware() 选项、webhooks、组件参考、API 2025-11-10 重大变更、JWT 大小限制、用户类型不匹配、生产环境注意事项 (GCP 中断、jwtKey 离线验证)
最后验证 : 2026-01-20 | 技能版本 : 3.1.0 | 变更 : 新增 4 个已知问题 (#12-15: 用户类型不匹配、acceptsToken 类型不匹配、deriveUrlFromHeaders 崩溃、treatPendingAsSignedOut 选项),扩展了 proxy.ts 部分并添加了 2025年3月 CVE 安全背景,新增了生产环境注意事项部分 (GCP 中断 + 缓解措施),添加了 organizationSyncOptions 仅限 Next.js 的说明,更新了 Next.js 16 的最低版本要求 (6.35.0+)。
每周安装数
310
代码仓库
GitHub 星标数
643
首次出现
2026年1月20日
安全审计
安装于
claude-code259
gemini-cli209
opencode207
cursor204
antigravity192
codex180
Package Versions : @clerk/nextjs@6.36.7, @clerk/backend@2.29.2, @clerk/clerk-react@5.59.2, @clerk/testing@1.13.26 Breaking Changes : Nov 2025 - API version 2025-11-10, Oct 2024 - Next.js v6 async auth() Last Updated : 2026-01-09
User-scoped and organization-scoped API keys for your application. Zero-code UI component.
// 1. Add the component for self-service API key management
import { APIKeys } from '@clerk/nextjs'
export default function SettingsPage() {
return (
<div>
<h2>API Keys</h2>
<APIKeys /> {/* Full CRUD UI for user's API keys */}
</div>
)
}
Backend Verification:
import { verifyToken } from '@clerk/backend'
// API keys are verified like session tokens
const { data, error } = await verifyToken(apiKey, {
secretKey: process.env.CLERK_SECRET_KEY,
authorizedParties: ['https://yourdomain.com'],
})
// Check token type
if (data?.tokenType === 'api_key') {
// Handle API key auth
}
clerkMiddleware Token Types:
// v6.36.0+: Middleware can distinguish token types
clerkMiddleware((auth, req) => {
const { userId, tokenType } = auth()
if (tokenType === 'api_key') {
// API key auth - programmatic access
} else if (tokenType === 'session_token') {
// Regular session - web UI access
}
})
Pricing (Beta = Free):
⚠️ BREAKING : Next.js 16 changed middleware filename due to critical security vulnerability (CVE disclosed March 2025).
Background : The March 2025 vulnerability (affecting Next.js 11.1.4-15.2.2) allowed attackers to completely bypass middleware-based authorization by adding a single HTTP header: x-middleware-subrequest: true. This affected all auth libraries (NextAuth, Clerk, custom solutions).
Why the Rename : The middleware.ts → proxy.ts change isn't just cosmetic - it's Next.js signaling that middleware-first security patterns are dangerous. Future auth implementations should not rely solely on middleware for authorization.
Next.js 15 and earlier: middleware.ts
Next.js 16+: proxy.ts
Correct Setup for Next.js 16:
// src/proxy.ts (NOT middleware.ts!)
import { clerkMiddleware } from '@clerk/nextjs/server'
export default clerkMiddleware()
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
}
Minimum Version : @clerk/nextjs@6.35.0+ required for Next.js 16 (fixes Turbopack build errors and cache invalidation on sign-out).
Administrators can mark passwords as compromised and force reset:
import { clerkClient } from '@clerk/backend'
// Force password reset for a user
await clerkClient.users.updateUser(userId, {
passwordDigest: 'compromised', // Triggers reset on next sign-in
})
Dashboard now includes org creation metrics and filtering by name/slug/date.
Affects: Applications using Clerk Billing/Commerce APIs
Critical Changes:
Endpoint URLs: /commerce/ → /billing/ (30+ endpoints)
GET /v1/commerce/plans → GET /v1/billing/plans
GET /v1/commerce/statements → GET /v1/billing/statements
POST /v1/me/commerce/checkouts → POST /v1/me/billing/checkouts
Field Terminology: payment_source → payment_method
// OLD (deprecated)
{ payment_source_id: "...", payment_source: {...} }
// NEW (required)
{ payment_method_id: "...", payment_method: {...} }
Removed Fields: Plans responses no longer include:
amount, (use instead)Migration: Update SDK to v6.35.0+ which includes support for API version 2025-11-10.
Official Guide: https://clerk.com/docs/guides/development/upgrading/upgrade-guides/2025-11-10
Affects: All Next.js Server Components using auth()
// ❌ OLD (v5 - synchronous)
const { userId } = auth()
// ✅ NEW (v6 - asynchronous)
const { userId } = await auth()
Also affects: auth.protect() is now async in middleware
// ❌ OLD (v5)
auth.protect()
// ✅ NEW (v6)
await auth.protect()
Compatibility: Next.js 15, 16 supported. Static rendering by default.
Custom OIDC providers and social connections now support PKCE (Proof Key for Code Exchange) for enhanced security in native/mobile applications where client secrets cannot be safely stored.
Use case: Mobile apps, native apps, public clients that can't securely store secrets.
Automatic secondary authentication when users sign in from unrecognized devices:
How it works: Clerk automatically prompts for additional verification (email code, backup code) when detecting sign-in from new device.
@clerk/nextjs v6.35.2+ includes cache invalidation improvements for Next.js 16 during sign-out.
Pattern:
import { auth } from '@clerk/nextjs/server'
export default async function Page() {
const { userId } = await auth() // ← Must await
if (!userId) {
return <div>Unauthorized</div>
}
return <div>User ID: {userId}</div>
}
CRITICAL: Always set authorizedParties to prevent CSRF attacks
import { verifyToken } from '@clerk/backend'
const { data, error } = await verifyToken(token, {
secretKey: c.env.CLERK_SECRET_KEY,
// REQUIRED: Prevent CSRF attacks
authorizedParties: ['https://yourdomain.com'],
})
Why: Without authorizedParties, attackers can use valid tokens from other domains.
Source: https://clerk.com/docs/reference/backend/verify-token
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
// Define protected routes
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/api/private(.*)',
])
const isAdminRoute = createRouteMatcher(['/admin(.*)'])
export default clerkMiddleware(async (auth, req) => {
// Protect routes
if (isProtectedRoute(req)) {
await auth.protect() // Redirects unauthenticated users
}
// Require specific permissions
if (isAdminRoute(req)) {
await auth.protect({
role: 'org:admin', // Requires organization admin role
})
}
})
| Option | Type | Description |
|---|---|---|
debug | boolean | Enable debug logging |
jwtKey | string | JWKS public key for networkless verification |
clockSkewInMs | number | Token time variance (default: 5000ms) |
organizationSyncOptions |
⚠️ Next.js Only : This feature currently only works with clerkMiddleware() in Next.js. It does NOT work with authenticateRequest() in other runtimes (Cloudflare Workers, Express, etc.) due to Sec-Fetch-Dest header checks.
Source : GitHub Issue #7178
clerkMiddleware({
organizationSyncOptions: {
organizationPatterns: ['/orgs/:slug', '/orgs/:slug/(.*)'],
personalAccountPatterns: ['/personal', '/personal/(.*)'],
},
})
import { Webhook } from 'svix'
export async function POST(req: Request) {
const payload = await req.text()
const headers = {
'svix-id': req.headers.get('svix-id')!,
'svix-timestamp': req.headers.get('svix-timestamp')!,
'svix-signature': req.headers.get('svix-signature')!,
}
const wh = new Webhook(process.env.CLERK_WEBHOOK_SIGNING_SECRET!)
try {
const event = wh.verify(payload, headers)
// Process event
return Response.json({ success: true })
} catch (err) {
return Response.json({ error: 'Invalid signature' }, { status: 400 })
}
}
| Event | Trigger |
|---|---|
user.created | New user signs up |
user.updated | User profile changes |
user.deleted | User account deleted |
session.created | New sign-in |
session.ended | Sign-out |
organization.created | New org created |
⚠️ Important: Webhook routes must be PUBLIC (no auth). Add to middleware exclude list:
const isPublicRoute = createRouteMatcher([
'/api/webhooks/clerk(.*)', // Clerk webhooks are public
])
clerkMiddleware((auth, req) => {
if (!isPublicRoute(req)) {
auth.protect()
}
})
| Component | Purpose |
|---|---|
<SignIn /> | Full sign-in flow |
<SignUp /> | Full sign-up flow |
<SignInButton /> | Trigger sign-in modal |
<SignUpButton /> | Trigger sign-up modal |
<SignedIn> | Render only when authenticated |
<SignedOut> | Render only when unauthenticated |
| Hook | Returns |
|---|---|
useAuth() | { userId, sessionId, isLoaded, isSignedIn, getToken } |
useUser() | { user, isLoaded, isSignedIn } |
useClerk() | Clerk instance with methods |
useSession() | Current session object |
useOrganization() |
Problem : Browser cookies limited to 4KB. Clerk's default claims consume ~2.8KB, leaving 1.2KB for custom claims.
⚠️ Development Note : When testing custom JWT claims in Vite dev mode, you may encounter "431 Request Header Fields Too Large" error. This is caused by Clerk's handshake token in the URL exceeding Vite's 8KB limit. See Issue #11 for solution.
Solution:
// ✅ GOOD: Minimal claims
{
"user_id": "{{user.id}}",
"email": "{{user.primary_email_address}}",
"role": "{{user.public_metadata.role}}"
}
// ❌ BAD: Exceeds limit
{
"bio": "{{user.public_metadata.bio}}", // 6KB field
"all_metadata": "{{user.public_metadata}}" // Entire object
}
Best Practice : Store large data in database, include only identifiers/roles in JWT.
| Category | Shortcodes | Example |
|---|---|---|
| User ID & Name | {{user.id}}, {{user.first_name}}, {{user.last_name}}, {{user.full_name}} | "John Doe" |
| Contact | {{user.primary_email_address}}, {{user.primary_phone_address}} |
Advanced Features:
"{{user.last_name}} {{user.first_name}}""{{user.public_metadata.role || 'user'}}""{{user.public_metadata.profile.interests}}"Official Docs : https://clerk.com/docs/guides/sessions/jwt-templates
Test Emails (no emails sent, fixed OTP):
john+clerk_test@example.com
jane+clerk_test@gmail.com
Test Phone Numbers (no SMS sent, fixed OTP):
+12015550100
+19735550133
Fixed OTP Code : 424242 (works for all test credentials)
Script (scripts/generate-session-token.js):
# Generate token
CLERK_SECRET_KEY=sk_test_... node scripts/generate-session-token.js
# Create new test user
CLERK_SECRET_KEY=sk_test_... node scripts/generate-session-token.js --create-user
# Auto-refresh token every 50 seconds
CLERK_SECRET_KEY=sk_test_... node scripts/generate-session-token.js --refresh
Manual Flow :
POST /v1/usersPOST /v1/sessionsPOST /v1/sessions/{session_id}/tokensAuthorization: Bearer <token>Install @clerk/testing for automatic Testing Token management:
npm install -D @clerk/testing
Global Setup (global.setup.ts):
import { clerkSetup } from '@clerk/testing/playwright'
import { test as setup } from '@playwright/test'
setup('global setup', async ({}) => {
await clerkSetup()
})
Test File (auth.spec.ts):
import { setupClerkTestingToken } from '@clerk/testing/playwright'
import { test } from '@playwright/test'
test('sign up', async ({ page }) => {
await setupClerkTestingToken({ page })
await page.goto('/sign-up')
await page.fill('input[name="emailAddress"]', 'test+clerk_test@example.com')
await page.fill('input[name="password"]', 'TestPassword123!')
await page.click('button[type="submit"]')
// Verify with fixed OTP
await page.fill('input[name="code"]', '424242')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('/dashboard')
})
Official Docs : https://clerk.com/docs/guides/development/testing/overview
This skill prevents 15 documented issues :
Error : "Missing Clerk Secret Key or API Key" Source : https://stackoverflow.com/questions/77620604 Prevention : Always set in .env.local or via wrangler secret put
Error : "apiKey is deprecated, use secretKey" Source : https://clerk.com/docs/upgrade-guides/core-2/backend Prevention : Replace apiKey with secretKey in all calls
Error : "No JWK available" Source : https://github.com/clerk/javascript/blob/main/packages/backend/CHANGELOG.md Prevention : Use @clerk/backend@2.17.2 or later (fixed)
Error : No error, but CSRF vulnerability Source : https://clerk.com/docs/reference/backend/verify-token Prevention : Always set authorizedParties: ['https://yourdomain.com']
Error : "Cannot find module" Source : https://clerk.com/docs/upgrade-guides/core-2/backend Prevention : Update import paths for Core 2
Error : Token exceeds size limit Source : https://clerk.com/docs/backend-requests/making/custom-session-token Prevention : Keep custom claims under 1.2KB
Error : "API version v1 is deprecated" Source : https://clerk.com/docs/upgrade-guides/core-2/backend Prevention : Use latest SDK versions (API v2025-11-10)
Error : "cannot be used as a JSX component" Source : https://stackoverflow.com/questions/79265537 Prevention : Ensure React 19 compatibility with @clerk/clerk-react@5.59.2+
Error : "auth() is not a function" Source : https://clerk.com/changelog/2024-10-22-clerk-nextjs-v6 Prevention : Always await: const { userId } = await auth()
Error : "Missing Publishable Key" or secret leaked Prevention : Use correct prefixes (NEXT_PUBLIC_, VITE_), never commit secrets
Error : "431 Request Header Fields Too Large" when signing in Source : Common in Vite dev mode when testing custom JWT claims Cause : Clerk's __clerk_handshake token in URL exceeds Vite's 8KB header limit Prevention :
Add to package.json:
{
"scripts": {
"dev": "NODE_OPTIONS='--max-http-header-size=32768' vite"
}
}
Temporary Workaround : Clear browser cache, sign out, sign back in
Why : Clerk dev tokens are larger than production; custom JWT claims increase handshake token size
Note : This is different from Issue #6 (session token size). Issue #6 is about cookies (1.2KB), this is about URL parameters in dev mode (8KB → 32KB).
Error : TypeScript errors when sharing user utilities across client/server Source : GitHub Issue #2176 Why It Happens : useUser() returns UserResource (client-side) with different properties than currentUser() returns User (server-side). Client has fullName, primaryEmailAddress object; server has primaryEmailAddressId and privateMetadata instead. Prevention : Use shared properties only, or create separate utility functions for client vs server contexts.
// ✅ CORRECT: Use properties that exist in both
const primaryEmailAddress = user.emailAddresses.find(
({ id }) => id === user.primaryEmailAddressId
)
// ✅ CORRECT: Separate types
type ClientUser = ReturnType<typeof useUser>['user']
type ServerUser = Awaited<ReturnType<typeof currentUser>>
Error : "token-type-mismatch" when using authenticateRequest() with multiple token types Source : GitHub Issue #7520 Why It Happens : When using authenticateRequest() with multiple acceptsToken values (e.g., ['session_token', 'api_key']), Clerk incorrectly throws token-type-mismatch error. Prevention : Upgrade to @clerk/backend@2.29.2+ (fix available in snapshot, releasing soon).
// This now works in @clerk/backend@2.29.2+
const result = await authenticateRequest(request, {
acceptsToken: ['session_token', 'api_key'], // Fixed!
})
Error : Server crashes with URL parsing error Source : GitHub Issue #7275 Why It Happens : Internal deriveUrlFromHeaders() function performs unsafe URL parsing and crashes the entire server when receiving malformed URLs in headers (e.g., x-forwarded-host: 'example.com[invalid]'). This is a denial-of-service vulnerability. Prevention : Upgrade to @clerk/backend@2.29.0+ (fixed).
Error : None - optional parameter for edge case handling Source : Changelog @clerk/nextjs@6.32.0 Why It Exists : Sessions can have a pending status during certain flows (e.g., credential stuffing defense secondary auth). By default, pending sessions are treated as signed-out (user is null). Usage : Set treatPendingAsSignedOut: false to treat pending as signed-in (available in @clerk/nextjs@6.32.0+).
// Default: pending = signed out
const user = await currentUser() // null if status is 'pending'
// Treat pending as signed in
const user = await currentUser({ treatPendingAsSignedOut: false }) // defined if pending
Context : Clerk experienced 3 major service disruptions in May-June 2025 attributed to Google Cloud Platform (GCP) outages. The June 26, 2025 outage lasted 45 minutes (6:16-7:01 UTC) and affected all Clerk customers.
Source : Clerk Postmortem: June 26, 2025
Mitigation Strategies :
Monitor Clerk Status for real-time updates
Implement graceful degradation when Clerk API is unavailable
Cache auth tokens locally where possible
For existing sessions, use jwtKey option for networkless verification:
clerkMiddleware({ jwtKey: process.env.CLERK_JWT_KEY, // Allows offline token verification })
Note : During total outage, no new sessions can be created (auth requires Clerk API). However, existing sessions can continue working if you verify JWTs locally with jwtKey. Clerk committed to exploring multi-cloud redundancy to reduce single-vendor dependency risk.
/clerk/clerk-docsLatest (Nov 22, 2025):
{
"dependencies": {
"@clerk/nextjs": "^6.36.7",
"@clerk/clerk-react": "^5.59.2",
"@clerk/backend": "^2.29.2",
"@clerk/testing": "^1.13.26"
}
}
Token Efficiency :
Errors prevented : 15 documented issues with exact solutions Key value : API Keys beta, Next.js 16 proxy.ts (with March 2025 CVE context), clerkMiddleware() options, webhooks, component reference, API 2025-11-10 breaking changes, JWT size limits, user type mismatches, production considerations (GCP outages, jwtKey offline verification)
Last verified : 2026-01-20 | Skill version : 3.1.0 | Changes : Added 4 new Known Issues (#12-15: user type mismatch, acceptsToken type mismatch, deriveUrlFromHeaders crash, treatPendingAsSignedOut option), expanded proxy.ts section with March 2025 CVE security context, added Production Considerations section (GCP outages + mitigation), added organizationSyncOptions Next.js-only limitation note, updated minimum version requirements for Next.js 16 (6.35.0+).
Weekly Installs
310
Repository
GitHub Stars
643
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykWarn
Installed on
claude-code259
gemini-cli209
opencode207
cursor204
antigravity192
codex180
Linux云主机安全托管指南:从SSH加固到HTTPS部署
27,400 周安装
amount_formattedfee.amountcurrency, currency_symbol (use fee objects)payer_type (use for_payer_type)annual_monthly_amount, annual_amountRemoved Endpoints:
Null Handling: Explicit rules - null means "doesn't exist", omitted means "not asserting existence"
object |
| URL-based org activation |
signInUrl | string | Custom sign-in URL |
signUpUrl | string | Custom sign-up URL |
organization.membership.created | User joins org |
<UserButton /> | User menu with sign-out |
<UserProfile /> | Full profile management |
<OrganizationSwitcher /> | Switch between orgs |
<OrganizationProfile /> | Org settings |
<CreateOrganization /> | Create new org |
<APIKeys /> | API key management (NEW) |
| Current org context |
useOrganizationList() | All user's orgs |
"john@example.com"| Profile | {{user.image_url}}, {{user.username}}, {{user.created_at}} | "https://..." |
| Verification | {{user.email_verified}}, {{user.phone_number_verified}} | true |
| Metadata | {{user.public_metadata}}, {{user.public_metadata.FIELD}} | {"role": "admin"} |
| Organization | org_id, org_slug, org_role (in sessionClaims) | "org:admin" |