mcpField
C
clerk-chrome-extension-patterns

clerk-chrome-extension-patterns

github.com/clerk开发8914.5k 次安装今天更新
代码仓库

关于 clerk-chrome-extension-patterns

面向使用 Clerk 的 Chrome 扩展开发者,汇总弹出窗口、侧边栏、服务工作线程和内容脚本中的认证实现方式,并说明与网页应用同步认证的限制。

主要功能

  1. 01弹出窗口与侧边栏认证模式
  2. 02通过 syncHost 同步网页应用会话
  3. 03使用 createClerkClient 获取令牌
  4. 04内容脚本通过消息传递请求认证状态
  5. 05配置稳定的扩展 ID

适用场景

  1. 01为 Chrome 扩展添加电子邮件认证
  2. 02将 OAuth 认证委托给网页应用
  3. 03从服务工作线程获取会话令牌
  4. 04让内容脚本向后台请求认证状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/clerk/skills --skill clerk-chrome-extension-patterns

安装后在 Claude Code 中输入/clerk-chrome-extension-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
content-scripts.md
create-clerk-client.md
headless-extension.md
sync-host.md
templates/chrome-ext-basic-auth/
package.json
tsconfig.json
templates/chrome-ext-basic-auth/src/
popup.tsx

详细介绍

Chrome 扩展模式

关键规则

  1. 弹出窗口或侧边栏不支持 OAuth(Google、GitHub 等)和 SAML —— 使用 syncHost 将身份验证委托给你的 Web 应用
  2. 邮件链接(魔法链接)在弹出窗口中不起作用 —— 用户点击外部区域时弹出窗口会关闭,导致登录状态重置
  3. 侧边栏不会自动刷新身份验证状态 —— 用户通过 Web 应用登录后,必须关闭并重新打开侧边栏
  4. 服务工作线程和内容脚本无法访问 Clerk React 钩子 —— 使用 createClerkClient() 或消息传递
  5. 扩展 URL 使用 chrome-extension://,而非 http:// —— 所有重定向 URL 都必须使用 chrome.runtime.getURL('.')
  6. 没有稳定的 CRX ID 时,每次重新构建都会导致身份验证失效 —— 部署前请在清单中配置 key
  7. 由于来源限制,内容脚本无法直接使用 Clerk —— Clerk 会严格限制允许的来源
  8. 必须在 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(推荐):

  1. 访问 Plasmo Itero 生成密钥对
  2. 点击“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 使用 OAuthPro + 在 Web 应用中配置 OAuth
通过 syncHost 使用 SAMLEnterprise
机器人防护不适用——必须为扩展禁用

另请参阅

  • clerk-setup - 初始 Clerk 安装
  • clerk-custom-ui - 自定义流程和外观

评论

支持 Markdown