docs: purge safeGetSession + getUser from auth example code (#47042)

Sweeps the example code that creating-a-client.mdx and other auth docs
pull via $CodeSample, so the rendered pages match the "use getClaims()"
guidance. Also adds Database type stubs and parameterizes
SupabaseClient<Database> across SvelteKit and Hono examples.

Fixes #40985 


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **Documentation**
* Updated OAuth server getting-started guide to use a claims-based
consent/auth gate and preserve the authorization identifier on redirect.
* Added the `auth_methods` partial across framework sections in the
server-side “creating a client” guide.

* **Refactor**
* Updated authentication examples for Hono, Next.js, and SvelteKit to
rely on JWT claims for logged-in checks and protected routes.
* Streamlined example auth state and UI rendering to use claims-derived
information.

* **Type Updates**
* Improved TypeScript typing for Supabase clients and app auth data
across examples, including generated database type stubs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Chris Chinchilla <chris@chrischinchilla.com>
This commit is contained in:
Katerina SkroumpelouandChris Chinchilla authored and GitHub committed 2026-06-17 17:53:27 +02:00
1 parent 2bae11b549
commit 6eb40f17a7
23 files changed
+97 -122

No files matched your search

@@ -0,0 +1,4 @@
// Replace this file by running:
// npx supabase gen types typescript --local > src/database.types.ts
// or pass --project-id <your-project-ref> for a remote project.
export type Database = Record<string, never>
+3 -3
View File
@@ -6,11 +6,11 @@ app.use('*', supabaseMiddleware())
const routes = app.get('/api/user', async (c) => {
const supabase = getSupabase(c)
const { data, error } = await supabase.auth.getUser()
const { data, error } = await supabase.auth.getClaims()
if (error) console.log('error', error)
if (!data?.user) {
if (!data?.claims) {
return c.json({
message: 'You are not logged in.',
})
@@ -18,7 +18,7 @@ const routes = app.get('/api/user', async (c) => {
return c.json({
message: 'You are logged in!',
userId: data.user,
userId: data.claims.sub,
})
})
@@ -4,9 +4,11 @@ import type { Context, MiddlewareHandler } from 'hono'
import { env } from 'hono/adapter'
import { setCookie } from 'hono/cookie'
import type { Database } from '../database.types'
declare module 'hono' {
interface ContextVariableMap {
supabase: SupabaseClient
supabase: SupabaseClient<Database>
}
}
+4
View File
@@ -0,0 +1,4 @@
// Replace this file by running:
// npx supabase gen types typescript --local > src/database.types.ts
// or pass --project-id <your-project-ref> for a remote project.
export type Database = Record<string, never>
@@ -4,9 +4,11 @@ import type { Context, MiddlewareHandler } from 'hono'
import { env } from 'hono/adapter'
import { setCookie } from 'hono/cookie'
import type { Database } from '../database.types'
declare module 'hono' {
interface ContextVariableMap {
supabase: SupabaseClient
supabase: SupabaseClient<Database>
}
}
@@ -8,11 +8,10 @@ import { redirect } from 'next/navigation'
export default async function ProtectedPage() {
const supabase = await createClient()
const {
data: { user },
} = await supabase.auth.getUser()
const { data } = await supabase.auth.getClaims()
const claims = data?.claims
if (!user) {
if (!claims) {
return redirect('/login')
}
@@ -5,9 +5,8 @@ import { redirect } from 'next/navigation'
export default async function AuthButton() {
const supabase = await createClient()
const {
data: { user },
} = await supabase.auth.getUser()
const { data } = await supabase.auth.getClaims()
const claims = data?.claims
const signOut = async () => {
'use server'
@@ -17,9 +16,9 @@ export default async function AuthButton() {
return redirect('/login')
}
return user ? (
return claims ? (
<div className="flex items-center gap-4">
Hey, {user.email}!
Hey, {claims.email}!
<form action={signOut}>
<button className="py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover">
Logout
+4 -6
View File
@@ -1,17 +1,15 @@
import type { Session, SupabaseClient, User } from '@supabase/supabase-js'
import type { Database } from './database.types.ts' // import generated types
import type { JwtPayload, SupabaseClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
declare global {
namespace App {
// interface Error {}
interface Locals {
supabase: SupabaseClient<Database>
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>
session: Session | null
user: User | null
claims: JwtPayload | null
}
interface PageData {
session: Session | null
claims: JwtPayload | null
}
// interface PageState {}
// interface Platform {}
@@ -0,0 +1,4 @@
// Replace this file by running:
// npx supabase gen types typescript --local > src/database.types.ts
// or pass --project-id <your-project-ref> for a remote project.
export type Database = Record<string, never>
@@ -29,31 +29,6 @@ const supabase: Handle = async ({ event, resolve }) => {
},
})
/**
* Unlike `supabase.auth.getSession()`, which returns the session _without_
* validating the JWT, this function also calls `getUser()` to validate the
* JWT before returning the session.
*/
event.locals.safeGetSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
if (!session) {
return { session: null, user: null }
}
const {
data: { user },
error,
} = await event.locals.supabase.auth.getUser()
if (error) {
// JWT validation has failed
return { session: null, user: null }
}
return { session, user }
}
return resolve(event, {
filterSerializedResponseHeaders(name) {
/**
@@ -66,15 +41,20 @@ const supabase: Handle = async ({ event, resolve }) => {
}
const authGuard: Handle = async ({ event, resolve }) => {
const { session, user } = await event.locals.safeGetSession()
event.locals.session = session
event.locals.user = user
/**
* `getClaims` validates the JWT signature locally (against the project's
* asymmetric signing keys) without an extra round-trip to the Auth server.
* Use this for route protection. Use `getUser()` only when you need the
* canonical server-validated user record (e.g., after password changes).
*/
const { data: claimsData } = await event.locals.supabase.auth.getClaims()
event.locals.claims = claimsData?.claims ?? null
if (!event.locals.session && event.url.pathname.startsWith('/private')) {
if (!event.locals.claims && event.url.pathname.startsWith('/private')) {
redirect(303, '/auth')
}
if (event.locals.session && event.url.pathname === '/auth') {
if (event.locals.claims && event.url.pathname === '/auth') {
redirect(303, '/private')
}
@@ -1,10 +1,8 @@
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
const { session, user } = await safeGetSession()
export const load: LayoutServerLoad = async ({ locals: { claims }, cookies }) => {
return {
session,
user,
claims,
cookies: cookies.getAll(),
}
}
@@ -3,11 +3,11 @@
import { onMount } from 'svelte'
let { data, children } = $props()
let { session, supabase } = $derived(data)
let { claims, supabase } = $derived(data)
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((_, newSession) => {
if (newSession?.expires_at !== session?.expires_at) {
if (newSession?.expires_at !== claims?.exp) {
invalidate('supabase:auth')
}
})
@@ -16,4 +16,4 @@
})
</script>
{@render children()}
{@render children()}
@@ -27,17 +27,14 @@ export const load: LayoutLoad = async ({ data, depends, fetch }) => {
})
/**
* It's fine to use `getSession` here, because on the client, `getSession` is
* safe, and on the server, it reads `session` from the `LayoutData`, which
* safely checked the session using `safeGetSession`.
* `getClaims` validates the JWT signature locally (for asymmetric keys) once
* the relevant signing keys are available or cached, and returns the decoded
* claims. While an initial or periodic network request may be required to
* fetch or refresh keys, this is both faster and safer than `getSession`,
* which does not validate the JWT.
*/
const {
data: { session },
} = await supabase.auth.getSession()
const { data: claimsData, error } = await supabase.auth.getClaims()
const claims = error ? null : claimsData?.claims
const {
data: { user },
} = await supabase.auth.getUser()
return { session, supabase, user }
return { supabase, claims }
}
@@ -5,7 +5,7 @@
import type { PageData } from './$types'
let { data } = $props()
let { notes, supabase, user } = $derived(data)
let { notes, supabase, claims } = $derived(data)
const handleSubmit: EventHandler<SubmitEvent, HTMLFormElement> = async (evt) => {
evt.preventDefault()
@@ -24,7 +24,7 @@
}
</script>
<h1>Private page for user: {user?.email}</h1>
<h1>Private page for user: {claims?.email}</h1>
<h2>Notes</h2>
<ul>
{#each notes as note}
@@ -36,4 +36,4 @@
Add a note
<input name="note" type="text" />
</label>
</form>
</form>
+2 -8
View File
@@ -1,17 +1,11 @@
import type { Session, SupabaseClient, User } from '@supabase/supabase-js'
import type { Database } from './database.types.ts' // import generated types
import type { SupabaseClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
declare global {
namespace App {
// interface Error {}
interface Locals {
supabase: SupabaseClient<Database>
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>
session: Session | null
user: User | null
}
interface PageData {
session: Session | null
}
// interface PageState {}
// interface Platform {}
@@ -0,0 +1,4 @@
// Replace this file by running:
// npx supabase gen types typescript --local > src/database.types.ts
// or pass --project-id <your-project-ref> for a remote project.
export type Database = Record<string, never>
+1 -26
View File
@@ -12,7 +12,7 @@ export const handle: Handle = async ({ event, resolve }) => {
/**
* Note: You have to add the `path` variable to the
* set and remove method due to sveltekit's cookie API
* requiring this to be set, setting the path to an empty string
* requiring this to be set, setting the path to `/`
* will replicate previous/standard behavior (https://kit.svelte.dev/docs/types#public-types-cookies)
*/
cookiesToSet.forEach(({ name, value, options }) =>
@@ -25,31 +25,6 @@ export const handle: Handle = async ({ event, resolve }) => {
},
})
/**
* Unlike `supabase.auth.getSession()`, which returns the session _without_
* validating the JWT, this function also calls `getUser()` to validate the
* JWT before returning the session.
*/
event.locals.safeGetSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
if (!session) {
return { session: null, user: null }
}
const {
data: { user },
error,
} = await event.locals.supabase.auth.getUser()
if (error) {
// JWT validation has failed
return { session: null, user: null }
}
return { session, user }
}
return resolve(event, {
filterSerializedResponseHeaders(name) {
return name === 'content-range' || name === 'x-supabase-api-version'
@@ -1,11 +1,7 @@
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
const { session, user } = await safeGetSession()
export const load: LayoutServerLoad = async ({ cookies }) => {
return {
session,
user,
cookies: cookies.getAll(),
}
}
@@ -23,13 +23,14 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
})
/**
* It's fine to use `getSession` here, because on the client, `getSession` is
* safe, and on the server, it reads `session` from the `LayoutData`, which
* safely checked the session using `safeGetSession`.
* `getClaims` validates the JWT signature locally (for asymmetric keys) once
* the relevant signing keys are available or cached, and returns the decoded
* claims. While an initial or periodic network request may be required to
* fetch or refresh keys, this is both faster and safer than `getSession`,
* which does not validate the JWT.
*/
const {
data: { session },
} = await supabase.auth.getSession()
const { data: claimsData, error } = await supabase.auth.getClaims()
const claims = error ? null : claimsData?.claims
return { supabase, session }
return { supabase, claims }
}
@@ -1,11 +1,14 @@
import type { SupabaseClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
interface Locals {
supabase: SupabaseClient
supabase: SupabaseClient<Database>
}
// interface PageState {}
// interface Platform {}
@@ -0,0 +1,4 @@
// Replace this file by running:
// npx supabase gen types typescript --local > src/database.types.ts
// or pass --project-id <your-project-ref> for a remote project.
export type Database = Record<string, never>