关于 clerk-chrome-extension-patterns
面向使用 Clerk 的 Chrome 扩展开发者,汇总弹出窗口、侧边栏、服务工作线程和内容脚本中的认证实现方式,并说明与网页应用同步认证的限制。
主要功能
- 01弹出窗口与侧边栏认证模式
- 02通过 syncHost 同步网页应用会话
- 03使用 createClerkClient 获取令牌
- 04内容脚本通过消息传递请求认证状态
- 05配置稳定的扩展 ID
适用场景
- 01为 Chrome 扩展添加电子邮件认证
- 02将 OAuth 认证委托给网页应用
- 03从服务工作线程获取会话令牌
- 04让内容脚本向后台请求认证状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ clerk/ skills --skill clerk-chrome-extension-patterns安装后在 Claude Code 中输入/clerk-chrome-extension-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/content-scripts.mdcreate-clerk-client.mdheadless-extension.mdsync-host.mdtemplates/chrome-ext-basic-auth/package.jsontsconfig.jsontemplates/chrome-ext-basic-auth/src/popup.tsx详细介绍
Chrome 扩展模式
关键规则
- 弹出窗口或侧边栏不支持 OAuth(Google、GitHub 等)和 SAML —— 使用
syncHost将身份验证委托给你的 Web 应用 - 邮件链接(魔法链接)在弹出窗口中不起作用 —— 用户点击外部区域时弹出窗口会关闭,导致登录状态重置
- 侧边栏不会自动刷新身份验证状态 —— 用户通过 Web 应用登录后,必须关闭并重新打开侧边栏
- 服务工作线程和内容脚本无法访问 Clerk React 钩子 —— 使用
createClerkClient()或消息传递 - 扩展 URL 使用
chrome-extension://,而非http://—— 所有重定向 URL 都必须使用chrome.runtime.getURL('.') - 没有稳定的 CRX ID 时,每次重新构建都会导致身份验证失效 —— 部署前请在清单中配置
key - 由于来源限制,内容脚本无法直接使用 Clerk —— Clerk 会严格限制允许的来源
- 必须在 Clerk Dashboard 中禁用机器人防护 —— Cloudflare 机器人检测在扩展环境中不受支持
身份验证选项
| 方式 | 弹出窗口 | 侧边栏 | syncHost(搭配 Web 应用) |
|---|---|---|---|
| 邮箱 + OTP | 是 | 是 | 是 |
| 邮箱 + 链接 | 否 | 否 | 是 |
| 邮箱 + 密码 | 是 | 是 | 是 |
| 用户名 + 密码 | 是 | 是 | 是 |
| SMS + OTP | 是 | 是 | 是 |
| OAuth(Google、GitHub 等) | 否 | 否 | 是 |
| SAML | 否 | 否 | 是 |
| 通行密钥 | 是 | 是 | 是 |
| Google One Tap | 否 | 否 | 是 |
| Web3 | 否 | 否 | 是 |
快速开始(Plasmo)
npx create-plasmo --with-tailwindcss --with-src my-extension
cd my-extension
npm install @clerk/chrome-extension
在 Clerk Dashboard 的 Native applications 下启用 Native API。所有扩展集成都必须启用。
.env.development:
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_FRONTEND_API=https://your-app.clerk.accounts.dev
src/popup.tsx:
import { ClerkProvider, Show, SignInButton, SignUpButton, UserButton } from '@clerk/chrome-extension'
const PUBLISHABLE_KEY = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
const EXTENSION_URL = chrome.runtime.getURL('.')
if (!PUBLISHABLE_KEY) {
throw new Error('Missing PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY')
}
function IndexPopup() {
return (
<ClerkProvider
publishableKey={PUBLISHABLE_KEY}
afterSignOutUrl={`${EXTENSION_URL}/popup.html`}
signInFallbackRedirectUrl={`${EXTENSION_URL}/popup.html`}
signUpFallbackRedirectUrl={`${EXTENSION_URL}/popup.html`}
>
<Show when="signed-out">
<SignInButton mode="modal" />
<SignUpButton mode="modal" />
</Show>
<Show when="signed-in">
<UserButton />
</Show>
</ClerkProvider>
)
}
export default IndexPopup
SignInButton 使用 mode="modal" —— 导航到单独页面会中断弹出窗口流程。
syncHost —— 与 Web 应用同步身份验证
当你需要 OAuth、SAML,或希望扩展反映你在 Web 应用中的登录状态时,请使用此方式。
工作原理:扩展通过 host_permissions 从 Web 应用的域名读取 Clerk 会话 Cookie。
步骤 1 —— 环境变量:
.env.development:
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_FRONTEND_API=https://your-app.clerk.accounts.dev
PLASMO_PUBLIC_CLERK_SYNC_HOST=http://localhost
.env.production:
PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_FRONTEND_API=https://clerk.your-domain.com
PLASMO_PUBLIC_CLERK_SYNC_HOST=https://clerk.your-domain.com
步骤 2 —— 添加 syncHost 属性:
const SYNC_HOST = process.env.PLASMO_PUBLIC_CLERK_SYNC_HOST
<ClerkProvider
publishableKey={PUBLISHABLE_KEY}
syncHost={SYNC_HOST}
afterSignOutUrl="/"
routerPush={(to) => navigate(to)}
routerReplace={(to) => navigate(to, { replace: true })}
>
步骤 3 —— 在 package.json 中配置 host_permissions:
{
"manifest": {
"key": "$CRX_PUBLIC_KEY",
"permissions": ["cookies", "storage"],
"host_permissions": [
"$PLASMO_PUBLIC_CLERK_SYNC_HOST/*",
"$CLERK_FRONTEND_API/*"
]
}
}
步骤 4 —— 通过 Clerk API 将扩展 ID 添加到 Web 应用的允许来源中:
curl -X PATCH https://api.clerk.com/v1/instance \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-type: application/json" \
-d '{"allowed_origins": ["chrome-extension://YOUR_EXTENSION_ID"]}'
使用 syncHost 时,在弹出窗口中隐藏不受支持的身份验证方式:
<SignIn
appearance={{
elements: {
socialButtonsRoot: 'plasmo-hidden',
dividerRow: 'plasmo-hidden',
},
}}
/>
完整指南:references/sync-host.md
在原生 JS / 服务工作线程中使用 createClerkClient()
从 @clerk/chrome-extension/client 导入(而非 @clerk/chrome-extension)。
后台服务工作线程(src/background/index.ts):
import { createClerkClient } from '@clerk/chrome-extension/client'
const publishableKey = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
async function getToken(): Promise<string | null> {
const clerk = await createClerkClient({
publishableKey,
background: true,
})
if (!clerk.session) return null
return await clerk.session.getToken()
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
getToken()
.then((token) => sendResponse({ token }))
.catch((error) => {
console.error('[Background] Error:', JSON.stringify(error))
sendResponse({ token: null })
})
return true
})
background: true 标志可在弹出窗口/侧边栏关闭时保持会话有效。若不启用,令牌会在 60 秒后过期。
使用原生 JS 的弹出窗口(src/popup.ts):
import { createClerkClient } from '@clerk/chrome-extension/client'
const EXTENSION_URL = chrome.runtime.getURL('.')
const POPUP_URL = `${EXTENSION_URL}popup.html`
const clerk = createClerkClient({ publishableKey })
clerk.load({
afterSignOutUrl: POPUP_URL,
signInForceRedirectUrl: POPUP_URL,
signUpForceRedirectUrl: POPUP_URL,
allowedRedirectProtocols: ['chrome-extension:'],
}).then(() => {
clerk.addListener(render)
render()
})
完整指南:references/create-clerk-client.md
无界面扩展(无弹出窗口、无侧边栏)
适用于完全在后台运行并与 Web 应用同步的扩展。
使用 syncHost 和 background: true 的 createClerkClient,从 Web 应用的 Cookie 中读取身份验证状态。
import { createClerkClient } from '@clerk/chrome-extension/client'
const publishableKey = process.env.PLASMO_PUBLIC_CLERK_PUBLISHABLE_KEY
const syncHost = process.env.PLASMO_PUBLIC_CLERK_SYNC_HOST
async function getAuthenticatedUser() {
const clerk = await createClerkClient({
publishableKey,
syncHost,
background: true,
})
return clerk.user
}
必须在 package.json 中为同步主机域名配置 host_permissions。
完整指南:references/headless-extension.md
内容脚本
内容脚本在注入网页的隔离 JavaScript 环境中运行。无法直接使用 Clerk —— 来源限制会阻止此操作。
使用消息传递向后台服务工作线程请求身份验证状态:
// content.ts
async function getToken(): Promise<string | null> {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ type: 'GET_TOKEN' }, (response) => {
resolve(response?.token ?? null)
})
})
}
async function main() {
const token = await getToken()
if (!token) return
// use token for authenticated API calls
}
main()
完整指南:references/content-scripts.md
稳定的 CRX ID
如果没有固定密钥,Chrome 会在构建时根据随机密钥生成 CRX ID。每次重新构建都会导致 ID 变化,从而破坏允许的来源。
选项 A —— Plasmo Itero(推荐):
- 访问 Plasmo Itero 生成密钥对
- 点击“Generate KeyPairs”——安全保存私钥,并复制公钥和 CRX ID
选项 B —— OpenSSL:
openssl genrsa -out key.pem 2048
# Use Plasmo Itero to convert or extract the public key in correct format
.env.chrome:
CRX_PUBLIC_KEY="<PUBLIC KEY from Itero>"
package.json:
{
"manifest": {
"key": "$CRX_PUBLIC_KEY",
"permissions": ["cookies", "storage"],
"host_permissions": [
"http://localhost/*",
"$CLERK_FRONTEND_API/*"
]
}
}
将 chrome-extension://YOUR_STABLE_CRX_ID 添加到 Clerk Dashboard > Allowed Origins。
令牌缓存(在弹出窗口关闭后保留)
const tokenCache = {
async getToken(key: string) {
const result = await chrome.storage.local.get(key)
return result[key] ?? null
},
async saveToken(key: string, token: string) {
await chrome.storage.local.set({ [key]: token })
},
async clearToken(key: string) {
await chrome.storage.local.remove(key)
},
}
<ClerkProvider publishableKey={PUBLISHABLE_KEY} tokenCache={tokenCache}>
| 存储类型 | 范围 | 清除时机 |
|---|---|---|
chrome.storage.local | 设备 | 卸载或手动清除 |
chrome.storage.session | 会话 | 浏览器关闭 |
chrome.storage.sync | 所有设备 | 卸载(大小限制为 8KB) |
localStorage | 仅限弹出窗口 | 弹出窗口关闭时——请勿用于身份验证 |
常见问题
| 症状 | 原因 | 修复方法 |
|---|---|---|
| 登录时重定向循环 | ClerkProvider 属性中缺少 CRX URL | 设置 afterSignOutUrl、signInFallbackRedirectUrl |
| OAuth 按钮不起作用 | 弹出窗口不支持 OAuth | 使用 syncHost 委托给 Web 应用 |
| 在 Web 应用登录后,身份验证状态过时 | 未配置 syncHost | 添加 syncHost 属性和 host_permissions |
| 在 Web 登录后,侧边栏显示为已退出登录 | 已知限制 | 用户必须关闭并重新打开侧边栏 |
| 60 秒后后台无法获取令牌 | 会话已过期,且没有后台刷新 | 使用 createClerkClient({ background: true }) |
| 内容脚本无法访问 Clerk | 隔离环境和来源限制 | 使用消息传递将请求发送到后台服务工作线程 |
| 重新构建后身份验证失效 | CRX ID 已变化 | 通过 .env.chrome 配置稳定密钥 |
PLASMO_PUBLIC_ 变量未定义 | 环境文件错误 | 使用 .env.development,而不是 .env |
| 机器人防护错误 | 扩展不支持 Cloudflare | 在 Clerk Dashboard 中禁用机器人防护 |
| 令牌缓存未持久化 | 在弹出窗口中使用了 localStorage | 使用 chrome.storage.local 或传递 tokenCache 属性 |
方案要求
| 功能 | 方案 |
|---|---|
| 基本弹出窗口身份验证(电子邮件/密码、OTP) | 免费 |
| 通行密钥 | 免费 |
| syncHost | 需要 Pro(自定义域名) |
| 通过 syncHost 使用 OAuth | Pro + 在 Web 应用中配置 OAuth |
| 通过 syncHost 使用 SAML | Enterprise |
| 机器人防护 | 不适用——必须为扩展禁用 |
另请参阅
clerk-setup- 初始 Clerk 安装clerk-custom-ui- 自定义流程和外观