diff --git a/apps/docs/content/guides/auth/server-side/advanced-guide.mdx b/apps/docs/content/guides/auth/server-side/advanced-guide.mdx
index 5659375a70f..f2cf7006708 100644
--- a/apps/docs/content/guides/auth/server-side/advanced-guide.mdx
+++ b/apps/docs/content/guides/auth/server-side/advanced-guide.mdx
@@ -52,7 +52,7 @@ A common cause is calling `supabase.auth.signOut()` without a `scope`. It defaul
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.
-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.
+The only way to detect that a session ended server-side, for example because the user signed out on another device, is to fetch the user with `getUser()`. `getClaims()` verifies the token's signature and expiry, which is what authorizes a request, but an unexpired token stays valid even when the session behind it was revoked. Call `getUser()` where that gap matters.
### What should I use for the `SameSite` property?
@@ -78,11 +78,11 @@ As of `@supabase/ssr` v0.10.0, the library automatically passes the necessary ca
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 middleware
+#### Next.js proxy
```ts
const response = NextResponse.next()
-// ... supabase client setup and getUser() call
+// ... supabase client setup and getClaims() call
response.headers.set('Cache-Control', 'private, no-store')
return response
```
@@ -90,7 +90,7 @@ return response
#### Nuxt server middleware
```ts
-// ... supabase client setup and getUser() call
+// ... supabase client setup and getClaims() call
setHeader(event, 'Cache-Control', 'private, no-store')
```
@@ -102,7 +102,7 @@ To protect against session leakage on CloudFront, use one or more of the followi
- **Set Minimum TTL to 0** in your CloudFront cache policy. This allows `Cache-Control: no-store` to take effect as intended.
- **Use `Cache-Control: no-cache="Set-Cookie"`** to instruct CloudFront not to cache the `Set-Cookie` header specifically, while still allowing other parts of the response to be cached.
-- **Disable caching entirely** for authenticated routes (e.g. your middleware path) by associating a cache policy with TTL set to 0, or by using the managed `CachingDisabled` policy for those behaviors.
+- **Disable caching entirely** for authenticated routes such as your proxy path, by associating a cache policy with TTL set to 0, or by using the managed `CachingDisabled` policy for those behaviors.
diff --git a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
index d1a80771c58..95a20d859d0 100644
--- a/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
+++ b/apps/docs/content/guides/auth/server-side/creating-a-client.mdx
@@ -130,12 +130,6 @@ SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key
Install [dotenv](https://www.npmjs.com/package/dotenv):
-```bash
-npm i dotenv
-```
-
-And initialize it:
-
@@ -164,6 +158,12 @@ pnpm add dotenv
+Then load the file before you read any variable from it. Put this on the first line of your entry point, above every other import:
+
+```js app.js
+require('dotenv').config()
+```
+
@@ -269,9 +269,9 @@ The code adds a [matcher](https://nextjs.org/docs/app/api-reference/file-convent
Anyone can forge the session cookie, so trusting it without verification lets an attacker render another user's page. Always use `supabase.auth.getClaims()` to protect pages and user data.
-_Never_ trust `supabase.auth.getSession()` inside server code such as Proxy. It isn't guaranteed to revalidate the Auth token.
+_Never_ trust `supabase.auth.getSession()` inside server code such as Proxy. It reads the session out of the cookie without revalidating it.
-It's safe to trust `getClaims()` because it validates the JWT signature against the project's published public keys every time.
+`getClaims()` verifies the token's signature on every call. On projects with asymmetric signing keys, the default for new projects, it verifies locally against a cached copy of the project's public keys. On projects still using a symmetric secret, it calls the Auth server instead. Either way the claims come from a token the server has verified rather than from whatever the cookie says.
@@ -422,10 +422,12 @@ const supabase = createServerClient(
```ts route.ts
-import { createServerClient, parseCookieHeader } from "@supabase/ssr";
-import type { APIContext } from "astro";
+import { createServerClient, parseCookieHeader } from '@supabase/ssr'
+import type { APIContext } from 'astro'
export async function GET(context: APIContext) {
+ const responseHeaders = new Headers()
+
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
@@ -434,15 +436,17 @@ export async function GET(context: APIContext) {
getAll() {
return parseCookieHeader(context.request.headers.get('Cookie') ?? '')
},
- setAll(cookiesToSet, _headers) {
- cookiesToSet.forEach(({ name, value }) =>
- context.cookies.set(name, value))
+ setAll(cookiesToSet, headers) {
+ cookiesToSet.forEach(({ name, value }) => context.cookies.set(name, value))
+ Object.entries(headers).forEach(([key, value]) => responseHeaders.set(key, value))
},
},
}
- );
+ )
- return ...
+ // Build your response here, and pass `responseHeaders` to it. Without them a
+ // shared cache can store this response along with its Set-Cookie header.
+ return new Response(null, { headers: responseHeaders })
}
```
@@ -455,6 +459,8 @@ import { createServerClient, parseCookieHeader } from '@supabase/ssr'
import { defineMiddleware } from 'astro:middleware'
export const onRequest = defineMiddleware(async (context, next) => {
+ const responseHeaders = new Headers()
+
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
@@ -463,14 +469,17 @@ export const onRequest = defineMiddleware(async (context, next) => {
getAll() {
return parseCookieHeader(context.request.headers.get('Cookie') ?? '')
},
- setAll(cookiesToSet, _headers) {
+ setAll(cookiesToSet, headers) {
cookiesToSet.forEach(({ name, value }) => context.cookies.set(name, value))
+ Object.entries(headers).forEach(([key, value]) => responseHeaders.set(key, value))
},
},
}
)
- return next()
+ const response = await next()
+ responseHeaders.forEach((value, key) => response.headers.set(key, value))
+ return response
})
```
@@ -609,7 +618,7 @@ You can now use any Supabase feature from your client or server code.
```ts server/api/hello.ts
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
-import { appendHeader, defineEventHandler, getHeader } from 'h3'
+import { appendHeader, defineEventHandler, getHeader, setHeader } from 'h3'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
@@ -622,10 +631,11 @@ export default defineEventHandler(async (event) => {
getAll() {
return parseCookieHeader(getHeader(event, 'Cookie') ?? '')
},
- setAll(cookiesToSet) {
+ setAll(cookiesToSet, cacheHeaders) {
cookiesToSet.forEach(({ name, value, options }) => {
appendHeader(event, 'Set-Cookie', serializeCookieHeader(name, value, options))
})
+ Object.entries(cacheHeaders).forEach(([key, value]) => setHeader(event, key, value))
},
},
}
@@ -785,7 +795,7 @@ You can now use any Supabase feature from your client or server code.
>
-```ts lib/supabase.js
+```js lib/supabase.js
const { createServerClient, parseCookieHeader, serializeCookieHeader } = require('@supabase/ssr')
exports.createClient = (context) => {
@@ -808,9 +818,10 @@ exports.createClient = (context) => {
-```ts app.js
+```js app.js
+require("dotenv").config()
+
const express = require("express")
-const dotenv = require("dotenv")
const { createClient } = require("./lib/supabase")
diff --git a/examples/auth/nextjs/lib/supabase/server.ts b/examples/auth/nextjs/lib/supabase/server.ts
index 867ecbc085d..59e0b6165b8 100644
--- a/examples/auth/nextjs/lib/supabase/server.ts
+++ b/examples/auth/nextjs/lib/supabase/server.ts
@@ -19,7 +19,7 @@ export async function createClient() {
)
} catch {
// The `setAll` method was called from a Server Component.
- // This can be ignored if you have middleware refreshing
+ // This can be ignored if you have a proxy refreshing
// user sessions.
}
},