☰
Clerk + Astro SSR 页面认证实战:Astro.locals.auth() 的完整使用指南
2026/10/9 5:30:29 网站建设 项目流程

【免费下载链接】autoskills

One command. Your entire AI skill stack. Installed.

项目地址:https://gitcode.com/gh_mirrors/au/autoskills
点击查看免费下载

本篇指南聚焦packages/autoskills/skills-registry/clerk-astro-patterns技能包中的 SSR Pages 参考文档,系统讲解如何在 Astro 服务端渲染(SSR)页面中接入 Clerk 认证。你将掌握基于Astro.locals.auth()的基础登录校验、组织(Org)级页面权限控制、拉取当前用户资料、获取 JWT 调用外部 API,以及 SSR 与静态预渲染之间认证行为的差异与避坑要点。

前置条件与运行环境

本文示例基于@clerk/astroSDK(Clerk Astro SDK v3+),要求 Astro 4.15+。完整的项目骨架可以直接参考技能包中的 astro-basic-auth 模板,其依赖为astro(^5.0.0)、@clerk/astro(^2.0.0)与@astrojs/node(^9.0.0)。

SSR 页面认证依赖两个前提:

  1. 项目开启服务端渲染:在astro.config.mjs中设置output: 'server'(或混合模式hybrid),并接入clerk()集成:
// astro.config.mjs import { defineConfig } from 'astro/config' import node from '@astrojs/node' import clerk from '@clerk/astro' export default defineConfig({ integrations: [clerk()], adapter: node({ mode: 'standalone' }), output: 'server', })
  1. 配置 Clerk 中间件:Astro.locals.auth()由clerkMiddleware在请求链路中填充。最简配置是透传式(pass-through)写法,把认证对象挂载到locals上,再由每个页面自行决定跳转逻辑:
// src/middleware.ts import { clerkMiddleware } from '@clerk/astro/server' export const onRequest = clerkMiddleware()

模板 src/middleware.ts 正是这种写法。更完整的中间件(含createRouteMatcher路由匹配与集中式重定向)可参考 middleware.md。

基础认证检查:保护 Dashboard 页面

最常见的 SSR 认证场景,是在页面 frontmatter 中读取当前用户 ID,未登录则重定向到登录页。以下代码来自原文档的核心示例:

--- // src/pages/dashboard.astro const { userId } = Astro.locals.auth() if (!userId) return Astro.redirect('/sign-in') const data = await fetchData(userId) --- <h1>Dashboard</h1> <pre>{JSON.stringify(data)}</pre>

要点拆解:

  • Astro.locals.auth()是一个函数调用,必须带括号(auth()),返回一个包含userId等字段的认证对象;
  • fetchData(userId)表明后续数据获取以userId为参数,天然具备"按用户隔离数据"的能力;
  • Astro.redirect('/sign-in')在 frontmatter 中直接返回,Astro 会将其转换为 302 重定向响应。

技能包的 evals.json 中第 2 条评估用例也印证了这一范式:在 frontmatter 中调用Astro.locals.auth()解构出userId,当userId为空时执行return Astro.redirect('/sign-in'),且页面必须是 SSR 输出(output: 'server'或未标记为 prerender)。

组织级页面:基于 Org 的多级权限控制

对于 B2B 场景,页面往往同时依赖"是否登录"与"是否处于某个组织上下文"。原文档给出了三层校验模式:

--- const { userId, orgId, orgRole } = Astro.locals.auth() if (!userId) return Astro.redirect('/sign-in') if (!orgId) return Astro.redirect('/select-org') if (orgRole !== 'org:admin') return Astro.redirect('/dashboard') const settings = await fetchOrgSettings(orgId) --- <h1>Org Settings</h1>

这套守卫逻辑逐层递进,每一层失败都有明确的去向:

校验层级判定条件失败去向
身份层!userId重定向/sign-in(未登录)
组织层!orgId重定向/select-org(未选择组织)
角色层orgRole !== 'org:admin'重定向/dashboard(权限不足)

角色字符串采用 Clerk 的标准格式org:admin,其中org:前缀标记为组织级角色。技能包 evals.json 第 6 条用例要求同时处理"未认证"与"未选择组织"两个状态,与本例完全一致。

获取当前用户数据:clerkClient 服务端用法

Astro.locals.auth()只提供会话元信息(如userId),要获取完整的用户档案(头像、姓名、邮箱等),需要通过服务端的clerkClient发起请求:

--- import { clerkClient } from '@clerk/astro/server' const { userId } = Astro.locals.auth() if (!userId) return Astro.redirect('/sign-in') const client = clerkClient(Astro) const user = await client.users.getUser(userId) --- <img src={user.imageUrl} alt={user.fullName ?? ''} />

三个值得注意的细节:

  1. clerkClient从@clerk/astro/server导入——这是服务端专用入口,与客户端 hooks 的导入路径严格区分;
  2. clerkClient(Astro)接收 Astro 上下文作为参数,以便携带请求级上下文(含密钥解析);
  3. user.imageUrl、user.fullName等字段来自 Clerk 用户对象;user.fullName ?? ''使用空值合并运算符兜底,避免头像 alt 属性为空字符串时的可访问性问题。

同样的clerkClient模式也适用于 API 路由:在 API 路由中传入的是context而非Astro,并配合context.locals.auth()读取认证信息。

getToken:为外部 API 签发 JWT

当 SSR 页面需要代表用户调用第三方服务(如 Supabase、自建后端)时,使用getToken获取针对指定模板签发的 JWT:

--- const auth = Astro.locals.auth() if (!auth.userId) return Astro.redirect('/sign-in') const token = await auth.getToken({ template: 'supabase' }) const data = await fetchFromSupabase(token) ---

执行流程:

  1. 先取整个auth对象,检查auth.userId是否存在;
  2. 调用await auth.getToken({ template: 'supabase' }),其中template对应你在 Clerk Dashboard 中配置的 JWT 模板名称——模板决定了 token 中携带的自定义 claims;
  3. 将返回的 token 作为Authorization头(通常为Bearer前缀)转发给外部 API。

getToken是异步函数,必须 await;模板不存在时,Clerk 会抛出错误,建议在调用处做好 try/catch 兜底。

Auth 对象字段全解

Astro.locals.auth()返回的认证对象包含以下字段(原文档表格完整继承):

FieldTypeDescription
userIdstring \| nullCurrent user ID
orgIdstring \| nullActive org ID
orgRolestring \| nullUser's role in active org
sessionIdstring \| nullCurrent session ID
has()functionCheck permissions
getToken()async functionGet JWT for external APIs

补充说明:

  • 可空性语义:所有 ID 字段都可能为null——未登录时userId为null;未选择组织时orgId为null;即使登录但组织内无角色时orgRole亦可能为null。因此页面守卫必须显式判空,不能依赖 truthy 巧合;
  • has()权限检查:适用于细粒度权限(如auth.has({ permission: 'org:items:delete' })),可返回布尔值直接驱动页面内容分支。同一能力在 API 路由 的DELETE处理器中也有示范(配合 403 状态码);
  • 中间件的auth()与页面不同:在src/middleware.ts中,认证对象由处理器参数回调取得(auth().userId),且同样必须调用auth()而非直接访问属性——详见 middleware.md 的 CRITICAL 说明。

关键注意事项(CRITICAL)

原文档明确列出的三条红线,是 SSR 认证稳定运行的生命线:

  1. Astro.locals.auth()必须带括号调用——它返回认证对象,locals.auth(不带括号)只会得到未调用的函数引用,解构会得到undefined;
  2. 受保护页面绝不能声明export const prerender = true——Clerk 中间件会跳过静态预渲染页面,Astro.locals.auth()在这些页面上拿不到任何认证数据;
  3. 如需将单个页面排除在静态渲染之外,显式声明export const prerender = false,让该页回到 SSR 路径。

这条规则与中间件文档中的说明互为印证:clerkMiddleware对export const prerender = true的页面不执行。技能包 SKILL.md 的"Common Pitfalls"表给出了完整的症状对照:

SymptomCauseFix
Astro.locals.authis undefinedMissing middlewareAddclerkMiddlewaretosrc/middleware.ts
Auth works in dev but not productionoutput: 'static'globallySetoutput: 'server'orhybridfor protected pages
Static page has no authPrerendered pages skip middlewareUseexport const prerender = falseor move to island
Island not reactive to sign-inMissingclient:loaddirectiveAddclient:loadto the island component

SSR 与静态渲染的取舍:一张图看懂请求链路

理解 Astro 的双渲染模式是正确使用 SSR 认证的前提。技能包给出了清晰的请求模型:

Request → clerkMiddleware() → SSR page → Astro.locals.auth() ↓ Island (.client) → useAuth() hook
  • SSR 页面:中间件填充Astro.locals.auth(),服务端完成认证与重定向,适合 dashboard、组织设置等需要受保护数据渲染的页面;
  • 静态预渲染页面(export const prerender = true):中间件直接跳过,服务端拿不到认证信息,只能依靠客户端岛屿组件中的useAuth()等 hooks 做前端条件渲染;
  • 岛屿组件(React/Vue/Svelte 等.client组件):使用@clerk/astro/react的useAuth、useUser、UserButton等 hooks,且必须附带client:load等client:*指令才会在浏览器端水合。

对于"大部分静态 + 少数受保护"的混合站点,官方推荐output: 'hybrid'配合export const prerender = false逐页控制。技能包 evals.json 第 5 条用例也明确要求:静态预渲染页面上不能依赖Astro.locals.auth(),应改用岛屿客户端认证或将该页转为 SSR。

完整可运行的最小模板

技能包自带的最小模板 astro-basic-auth 展示了 SSR 认证的最小闭环:

  • astro.config.mjs:integrations: [clerk()]+adapter: node({ mode: 'standalone' })+output: 'server';
  • src/middleware.ts:export const onRequest = clerkMiddleware()透传挂载认证对象;
  • src/pages/index.astro:通过SignedIn/SignedOut组件做登录状态的条件渲染(页面级组件来自@clerk/astro/components)。

在此基础上,将本文的Astro.locals.auth()守卫模式写入任意.astro页面 frontmatter,即可完成受保护页面的服务端认证。若页面还需展示客户端实时认证状态(如登录/登出按钮),可结合 island-components.md 的useAuth+client:load方案;若需要 React 组件深度集成,参考 astro-react.md 的@clerk/astro/react与$userStore用法。

最后提醒:Clerk 环境变量遵循 Astro 的PUBLIC_前缀约定(而非 Next.js 的NEXT_PUBLIC_),在.env中配置PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...与CLERK_SECRET_KEY=sk_...后即可在本地运行验证。

【免费下载链接】autoskills

One command. Your entire AI skill stack. Installed.

项目地址:https://gitcode.com/gh_mirrors/au/autoskills
点击查看免费下载
上一篇:部署AI Agent沙盒应用的10条安全清单:just-bash生产环境最佳实践
下一篇:PilotDeck插件开发完全指南:用plugin.json注册工具、Hook与自定义记忆存储

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询