Skip to content
Auth

高级指南

Details about SSR Auth flows and implementation for advanced users.

当用户通过 Supabase Auth 进行身份验证时,服务器会发出两条信息:

🌐 When a user authenticates with Supabase Auth, two pieces of information are issued by the server:

  1. 访问令牌 以 JWT 的形式。
  2. 刷新令牌,它是一个随机生成的字符串。

如果你没有使用服务器端渲染(SSR),默认的行为是将这些信息存储在本地存储中。本地存储服务器无法访问,所以对于 SSR,令牌需要存储在安全的 cookie 中。然后这个 cookie 可以在客户端和服务器端的应用代码之间来回传递。

🌐 The default behavior if you're not using SSR is to store this information in local storage. Local storage isn't accessible by the server, so for SSR, the tokens instead need to be stored in a secure cookie. The cookie can then be passed back and forth between your app code in the client and your app code in the server.

如果你没有使用 SSR,你可能也在使用 隐式流程 来获取访问令牌和刷新令牌。在这个流程中服务器无法访问令牌,所以对于 SSR,你应该改用 PKCE 流程。如果你的客户端库提供这个选项,你可以在初始化 Supabase 客户端时更改流程类型。

🌐 If you're not using SSR, you might also be using the implicit flow to get the access and refresh tokens. The server can't access the tokens in this flow, so for SSR, you should change to the PKCE flow. You can change the flow type when initiating your Supabase client if your client library provides this option.

它是怎么运作的 #

🌐 How it works

在 PKCE 流程中,会重定向到你的应用,URL 中包含了一个授权码。当你使用 exchangeCodeForSession 交换这个码时,会收到包含访问令牌和刷新令牌的会话信息。

🌐 In the PKCE flow, a redirect is made to your app, with an Auth Code contained in the URL. When you exchange this code using exchangeCodeForSession, you receive the session information, which contains the access and refresh tokens.

为了维持会话,这些令牌必须安全地存储在客户端和服务器间共享的存储介质中,通常是 cookie。每当会话刷新时,共享存储介质中的认证令牌和刷新令牌都必须更新。Supabase 客户端库在初始化客户端时提供了一个可自定义的 storage 选项,让你可以更改令牌的存储位置。

🌐 To maintain the session, these tokens must be stored in a storage medium securely shared between client and server, which is traditionally cookies. Whenever the session is refreshed, the auth and refresh tokens in the shared storage medium must be updated. Supabase client libraries provide a customizable storage option when a client is initiated, allowing you to change where tokens are stored.

常见问题 #

🌐 Frequently asked questions

Next.js 路由预取时服务器端没有会话? #

🌐 No session on the server side with Next.js route prefetching?

当你在 Next.js 中使用 <Link href="/..."> 组件或 Router.push() API 进行路由预取时,可能会在浏览器处理访问令牌和刷新令牌之前发送服务器端请求。这意味着这些请求可能没有任何设置的 cookie,你的服务器代码会渲染未认证的内容。

🌐 When you use route prefetching in Next.js using <Link href="/..."> components or the Router.push() APIs can send server-side requests before the browser processes the access and refresh tokens. This means that those requests may not have any cookies set and your server code will render unauthenticated content.

为了改善用户体验,我们建议在用户登录后将他们重定向到一个特定页面,该页面不包含来自 Next.js 的任何路由预取。一旦在浏览器中运行的 Supabase 客户端库从 URL 片段中获取了访问令牌和刷新令牌,你就可以将用户发送到使用预取的任何页面。

🌐 To improve experience for your users, we recommend redirecting users to one specific page after sign-in that does not include any route prefetching from Next.js. Once the Supabase client library running in the browser has obtained the access and refresh tokens from the URL fragment, you can send users to any pages that use prefetching.

我怎么做 HttpOnly#

🌐 How do I make the cookies HttpOnly?

这没必要。访问令牌和刷新令牌都是为了在你的应用的不同组件之间传递而设计的。反正你应用中的浏览器端也需要访问刷新令牌,才能正确维护浏览器会话。

🌐 This is not necessary. Both the access token and refresh token are designed to be passed around to different components in your application. The browser-based side of your application needs access to the refresh token to properly maintain a browser session anyway.

我的服务器出现了无效刷新令牌错误。这是怎么回事? #

🌐 My server is getting invalid refresh token errors. What's going on?

很可能浏览器发送到你服务器的刷新令牌已经过期。确保 onAuthStateChange 监听器回调没有漏洞,并且在你的应用生命周期的比较早期就注册好了

🌐 It is likely that the refresh token sent from the browser to your server is stale. Make sure the onAuthStateChange listener callback is free of bugs and is registered relatively early in your application's lifetime

当你在服务器端收到这个错误时,试着把渲染延迟到浏览器端,这样客户端库就可以获取最新的刷新令牌,并给用户提供更好的体验。

🌐 When you receive this error on the server-side, try to defer rendering to the browser where the client library can access an up-to-date refresh token and present the user with a better experience.

我应该把 cookies 上的 Max-Age#

🌐 Should I set a shorter Max-Age parameter on the cookies?

Max-AgeExpires cookie 参数只控制浏览器是否将值发送到服务器。由于刷新令牌代表用户在该浏览器上的长期认证会话,因此在 cookie 上设置较短的 Max-AgeExpires 参数只会导致用户体验下降。

🌐 The Max-Age or Expires cookie parameters only control whether the browser sends the value to the server. Since a refresh token represents the long-lived authentication session of the user on that browser, setting a short Max-Age or Expires parameter on the cookies only results in a degraded user experience.

确保用户已经注销或会话已结束的唯一方法是使用 getUser() 获取用户的详细信息。getClaims() 方法只会检查本地 JWT 的有效性(签名和过期时间),但它不会向认证服务器验证会话是否仍然有效,也不会确认用户是否已经在服务器端注销。

🌐 The only way to ensure that a user has logged out or their session has ended is to get the user's details with getUser(). The getClaims() method only checks local JWT validation (signature and expiration), but it doesn't verify with the auth server whether the session is still valid or if the user has logged out server-side.

我应该用什么来设置 SameSite#

🌐 What should I use for the SameSite property?

确保你了解该属性在不同情况下的表现,因为有些属性可能会影响用户体验。

🌐 Make sure you understand the behavior of the property in different situations as some properties can degrade the user experience.

一个好的默认做法是使用 Lax,它会在用户访问你的网站时发送 Cookies。Cookies 通常需要 Secure 属性,这样它们只会通过 HTTPS 发送。不过,当在 localhost 上开发时,这可能会成为问题。

🌐 A good default is to use Lax which sends cookies when users are navigating to your site. Cookies typically require the Secure attribute, which only sends them over HTTPS. However, this can be a problem when developing on localhost.

我可以在使用 CDN 或缓存的情况下使用服务器端渲染吗? #

🌐 Can I use server-side rendering with a CDN or cache?

是的,但有两种特定情况可能导致用户接收到其他用户的会话。这两种情况都与包含 Set-Cookie 头的 HTTP 响应缓存有关。

🌐 Yes, but there are two specific scenarios that can cause users to receive another user's session. Both are related to caching of HTTP responses that contain Set-Cookie headers.

ISR(增量静态再生) #

🌐 ISR (incremental static regeneration)

如果你在触发 Supabase 会话刷新的页面上使用 ISR,缓存的响应将包括包含刷新后的 JWT 的 Set-Cookie 头。当那个缓存的响应被提供给下一个用户时,他们的浏览器会存储这个令牌,他们就会以错误的身份登录。

🌐 If you use ISR on pages that trigger a Supabase session refresh, the cached response will include the Set-Cookie header containing the refreshed JWT. When that cached response is served to a subsequent user, their browser stores the token and they are signed in as the wrong person.

不要在处理认证或可能发生会话刷新(session refresh)的路由上启用 ISR。在 Nuxt 中,避免在已认证的路由上设置 isr。在 Next.js 中,在需要认证的页面上使用 export const dynamic = 'force-dynamic'

🌐 Do not enable ISR on any route where authentication is handled or where a session refresh can occur. In Nuxt, avoid setting isr on authenticated routes. In Next.js, use export const dynamic = 'force-dynamic' on pages that require authentication.

CDN 和反向代理缓存 #

🌐 CDN and reverse proxy caching

@supabase/ssr 在服务器端刷新会话令牌时,它会通过 Set-Cookie 头将更新后的 JWT 写入 HTTP 响应。如果你的 CDN(例如 Vercel Edge、Cloudflare)缓存了该响应并将其提供给其他用户,那么该用户的浏览器就会存储这个缓存的令牌,从而以错误的身份登录。

🌐 When @supabase/ssr refreshes a session token server-side, it writes the updated JWT to the HTTP response via a Set-Cookie header. If your CDN (e.g. Vercel Edge, Cloudflare) caches that response and serves it to a different user, that user's browser will store the cached token and be signed in as the wrong person.

@supabase/ssr v0.10.0 开始,该库会在每次令牌刷新时自动将必要的缓存头(Cache-ControlExpiresPragma)作为第二个参数传递给你的 setAll 回调。如果你的 setAll 实现将这些头应用到响应中(就像 为 SSR 创建 Supabase 客户端 的示例中所示),大多数 CDN 就不需要额外的手动配置了。

🌐 As of @supabase/ssr v0.10.0, the library automatically passes the necessary cache headers (Cache-Control, Expires, Pragma) to your setAll callback as a second argument whenever a token refresh occurs. If your setAll implementation applies those headers to the response (as shown in the examples in Creating a Supabase client for SSR), no additional manual configuration is needed for most CDNs.

如果你使用的是旧版本或需要手动设置头信息,请在处理认证的任何路由的响应中添加 Cache-Control: private, no-store

🌐 If you are on an older version or need to set headers manually, add Cache-Control: private, no-store to responses from any route that handles authentication:

Next.js 中间件 #

🌐 Next.js middleware

1
const response = NextResponse.next()
2
// ... supabase client setup and getUser() call
3
response.headers.set('Cache-Control', 'private, no-store')
4
return response

Nuxt 服务器中间件 #

🌐 Nuxt server middleware

1
// ... supabase client setup and getUser() call
2
setHeader(event, 'Cache-Control', 'private, no-store')

CloudFront

CloudFront 的行为取决于它的缓存策略配置,而不仅仅由 Cache-Control 响应头控制。即使有 Cache-Control: private, no-store,如果其缓存策略的最小 TTL 大于 0,或者如果 cookies 和 Set-Cookie 头没有转发到源站,CloudFront 仍然可以缓存响应和 Set-Cookie 头。

🌐 CloudFront's behavior depends on its cache policy configuration and is not solely controlled by the Cache-Control response header. Even with Cache-Control: private, no-store, CloudFront can still cache the response and the Set-Cookie header if its cache policy has a Minimum TTL greater than 0, or if cookies and the Set-Cookie header are not forwarded to the origin.

为了防止 CloudFront 上的会话泄露,可以采用以下一项或多项步骤:

🌐 To protect against session leakage on CloudFront, use one or more of the following steps:

  • 在你的 CloudFront 缓存策略中 将最小 TTL 设置为 0。这样可以让 Cache-Control: no-store 按预期生效。
  • 使用 Cache-Control: no-cache="Set-Cookie" 来指示 CloudFront 不专门缓存 Set-Cookie 头,同时仍允许缓存响应的其他部分。
  • 完全禁用缓存 对于已认证的路由(例如你的中间件路径),可以通过将缓存策略的 TTL 设置为 0,或者对这些行为使用托管的 CachingDisabled 策略来实现。

如果你需要缓存 SSR 页面来提升性能,只对没有写入 Set-Cookie 头的路由应用缓存,并且对于提供用户特定内容的路由,缓存键中一定要包含刷新令牌的 cookie 值。

🌐 If you need to cache SSR pages for performance, apply caching only to routes that do not write Set-Cookie headers, and always include the refresh token cookie value in the cache key for any routes that serve user-specific content.

Vercel 流体计算(内存客户端共享) #

🌐 Vercel Fluid compute (in-memory client sharing)

Vercel 的流式计算模型可以保持服务器实例处于活跃状态,并在不同请求间重复使用它们。在某些情况下,这意味着在模块作用域中初始化的 Supabase 客户端 —— 或存储在共享变量中的客户端 —— 可能会被不同用户的请求重复使用,从而导致一个用户的会话泄露到另一个用户的请求中。

🌐 Vercel's Fluid compute model can keep server instances warm and reuse them across requests. In some cases this means a Supabase client initialized in module scope — or stored in a shared variable — may be reused across requests from different users, causing one user's session to leak into another user's request.

总是要在请求处理器里面初始化 Supabase 客户端,而不是在模块级别。不要把客户端或任何用户相关的状态存到会在请求之间持续存在的变量里。

🌐 Always initialize the Supabase client inside the request handler, not at module level. Do not store the client or any user-specific state in a variable that persists between requests.

哪些认证流程支持 PKCE? #

🌐 Which authentication flows have PKCE support?

目前,PKCE 在 Magic Link、OAuth、注册和密码恢复路由上都支持。这些对应于 Supabase 客户端库中的 signInWithOtpsignInWithOAuthsignUpresetPasswordForEmail 方法。在使用 PKCE 与手机和邮箱 OTP 时,相对于隐式流程没有行为变化——请求成功后,访问令牌会在响应体中返回。

🌐 At present, PKCE is supported on the Magic Link, OAuth, Sign Up, and Password Recovery routes. These correspond to the signInWithOtp, signInWithOAuth, signUp, and resetPasswordForEmail methods on the Supabase client library. When using PKCE with Phone and Email OTPs, there is no behavior change with respect to the implicit flow - an access token will be returned in the body when a request is successful.