为 Next.js 配置服务端认证
Next.js 提供了两种模式:App Router 和 Pages Router。您可以使用其中任何一种策略来设置服务器端认证。甚至可以在同一个应用中同时使用两者。
安装 Supabase 包
安装 @supabase/supabase-js 包和辅助包 @supabase/ssr。
1npm install @supabase/supabase-js @supabase/ssr设置环境变量
在项目根目录下创建 .env.local 文件。
填写您的 NEXT_PUBLIC_SUPABASE_URL 和 NEXT_PUBLIC_SUPABASE_ANON_KEY:
Project URL
Anon key
12NEXT_PUBLIC_SUPABASE_URL=<your_supabase_project_url>NEXT_PUBLIC_SUPABASE_ANON_KEY=<your_supabase_anon_key>编写创建 Supabase 客户端的工具函数
要从 Next.js 应用访问 Supabase,您需要两种类型的 Supabase 客户端:
- 客户端组件客户端 - 用于在浏览器中运行的 Client Components 中访问 Supabase。
- 服务端组件客户端 - 用于在仅服务器端运行的 Server Components、Server Actions 和 Route Handlers 中访问 Supabase。
创建一个 utils/supabase 文件夹,为每种客户端类型创建一个文件。然后复制每种客户端类型的工具函数。
12345678import { createBrowserClient } from '@supabase/ssr'export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! )}连接中间件
在项目根目录下创建 middleware.ts 文件,如果使用 ./src 文件夹,则放在其中。
由于 Server Components 不能写入 cookies,您需要中间件来刷新过期的 Auth 令牌并存储它们。
中间件负责:
- 刷新 Auth 令牌(通过调用
supabase.auth.getUser)。 - 将刷新的 Auth 令牌传递给 Server Components,这样它们就不会尝试自己刷新相同的令牌。这是通过
request.cookies.set实现的。 - 将刷新的 Auth 令牌传递给浏览器,以便替换旧的令牌。这是通过
response.cookies.set实现的。
复制适用于您应用的中间件代码。
添加一个 matcher,这样中间件就不会在不访问 Supabase 的路由上运行。
保护页面时要小心。服务器从 cookies 中获取用户会话,任何人都可以伪造 cookies。
始终使用 supabase.auth.getUser() 来保护页面和用户数据。
在服务器代码(如中间件)中,永远不要 信任 supabase.auth.getSession()。它不能保证重新验证 Auth 令牌。
可以信任 getUser(),因为它每次都会向 Supabase Auth 服务器发送请求以重新验证 Auth 令牌。
12345678910111213141516171819import { type NextRequest } from 'next/server'import { updateSession } from '@/utils/supabase/middleware'export async function middleware(request: NextRequest) { return await updateSession(request)}export const config = { matcher: [ /* * 匹配所有请求路径,除了以下开头的路径: * - _next/static (静态文件) * - _next/image (图片优化文件) * - favicon.ico (favicon 文件) * 可以自由修改此模式以包含更多路径。 */ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', ],}创建登录页面
为您的应用创建一个登录页面。使用 Server Action 调用 Supabase 的 signup 函数。
由于 Supabase 是从 Action 调用的,使用 @/utils/supabase/server.ts 中定义的客户端。
注意,在调用 Supabase 之前调用了 cookies,这将 fetch 调用从 Next.js 的缓存中排除。这对于认证的数据获取很重要,以确保用户只能访问自己的数据。
查看 Next.js 文档了解更多关于 选择退出数据缓存 的信息。
1234567891011121314import { login, signup } from './actions'export default function LoginPage() { return ( <form> <label htmlFor="email">邮箱:</label> <input id="email" name="email" type="email" required /> <label htmlFor="password">密码:</label> <input id="password" name="password" type="password" required /> <button formAction={login}>登录</button> <button formAction={signup}>注册</button> </form> )}更改认证确认路径
如果您启用了邮件确认(默认情况下),新用户注册后会收到一封确认邮件。
更改邮件模板以支持服务器端认证流程。
转到仪表板中的 Auth 模板 页面。在 Confirm signup 模板中,将 {{ .ConfirmationURL }} 更改为 {{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email。
为认证确认创建路由处理器
为 auth/confirm 创建一个 Route Handler。当用户点击确认邮件链接时,将他们的安全代码交换为 Auth 令牌。
由于这是一个 Router Handler,使用 @/utils/supabase/server.ts 中的 Supabase 客户端。
12345678910111213141516171819202122232425262728import { type EmailOtpType } from '@supabase/supabase-js'import { type NextRequest } from 'next/server'import { createClient } from '@/utils/supabase/server'import { redirect } from 'next/navigation'export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const token_hash = searchParams.get('token_hash') const type = searchParams.get('type') as EmailOtpType | null const next = searchParams.get('next') ?? '/' if (token_hash && type) { const supabase = await createClient() const { error } = await supabase.auth.verifyOtp({ type, token_hash, }) if (!error) { // 将用户重定向到指定的重定向 URL 或应用的根目录 redirect(next) } } // 将用户重定向到错误页面并显示一些说明 redirect('/error')}从 Server Component 访问用户信息
Server Components 可以读取 cookies,因此您可以获取认证状态和用户信息。
由于您是从 Server Component 调用 Supabase,使用 @/utils/supabase/server.ts 中创建的客户端。
创建一个 private 页面,只有登录的用户才能访问。该页面显示他们的邮箱。
保护页面时要小心。服务器从 cookies 中获取用户会话,任何人都可以伪造 cookies。
始终使用 supabase.auth.getUser() 来保护页面和用户数据。
在 Server Components 中,永远不要 信任 supabase.auth.getSession()。它不能保证重新验证 Auth 令牌。
可以信任 getUser(),因为它每次都会向 Supabase Auth 服务器发送请求以重新验证 Auth 令牌。
1234567891011121314import { redirect } from 'next/navigation'import { createClient } from '@/utils/supabase/server'export default async function PrivatePage() { const supabase = await createClient() const { data, error } = await supabase.auth.getUser() if (error || !data?.user) { redirect('/login') } return <p>你好 {data.user.email}</p>}恭喜完成
您已经成功完成了所有步骤!让我们回顾一下您已经实现的内容:
- 从服务器操作(Server Action)调用Supabase
- 从服务器组件(Server Component)调用Supabase
- 设置Supabase客户端工具函数,以便从客户端组件调用Supabase(例如用于建立实时订阅)
- 配置中间件自动刷新Supabase认证会话
现在您可以在客户端或服务器代码中使用所有Supabase功能了!