L’essentiel en 30 secondes
- Supabase Auth gère e-mail et mot de passe, magic link, OTP, téléphone et 19 fournisseurs sociaux ; le JWT porte l'identité (sub) et le rôle Postgres, et expire par défaut au bout d'une heure.
- Les clés anon et service_role sont en cours de dépréciation (fin 2026) : clé publishable côté client, clé secrète côté serveur ; préférez les clés de signature asymétriques au secret HS256.
- Côté serveur, protégez les pages avec getClaims() via @supabase/ssr ; getSession() ne revalide pas le cookie. Les claims personnalisés passent par le Custom Access Token Hook.
- Avant la production : SMTP personnalisé (l'intégré est limité à 2 e-mails par heure), CAPTCHA, RLS ; en React, un client unique, un contexte onAuthStateChange et TanStack Query.
Supabase Auth est le serveur d'authentification de Supabase (issu de GoTrue, publié sous le dépôt supabase/auth). Concrètement, c'est lui qui décide qui est connecté, et c'est ce « qui » qui détermine ensuite ce que chaque utilisateur peut lire ou modifier dans votre base. Il stocke les utilisateurs dans le schéma auth, émet des jetons JWT et fait basculer chaque requête sur le rôle Postgres authenticated, ce qui permet d'écrire vos règles d'accès en Row Level Security1. E-mail et mot de passe, magic link, code OTP, téléphone et connexion sociale passent tous par la même API. Pour voir comment Auth s'articule avec les autres services, consultez le guide complet de Supabase.
Ce guide suit le parcours réel : choisir ses clés, ouvrir les inscriptions, comprendre le JWT et les sessions, vérifier l'identité côté serveur, puis brancher une application React. Trois changements rendent obsolètes la plupart des tutoriels écrits avant 2025 :
- Les clés : les clés
anonetservice_rolesont en cours de dépréciation (échéance annoncée : fin 2026). On utilise désormais une clé publishable (sb_publishable_…) côté navigateur et une clé secrète (sb_secret_…) côté serveur1. - Le paquet serveur :
@supabase/auth-helpers-nextjsest remplacé par@supabase/ssr2. - La vérification côté serveur : on protège les pages avec
supabase.auth.getClaims(), qui vérifie la signature du jeton, et jamais avecgetSession(), qui lit le cookie sans le revalider2.
Quels paquets et quelle clé utiliser ?
npm install @supabase/supabase-js @supabase/ssrDans le navigateur, le client se crée avec l'URL du projet et la clé publishable. Cette clé peut être publique : sans utilisateur connecté, elle ne donne accès qu'à ce que le rôle anon est autorisé à voir1.
// lib/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)
}Les anciennes clés anon et service_role étaient elles-mêmes des JWT valables dix ans9. Les nouvelles clés sont de courtes chaînes, pas des JWT : Supabase les transforme à la volée en JWT de courte durée17. Si un tutoriel vous fait copier une longue clé commençant par eyJ, il a été écrit pour l'ancien système.
| Clé | Rôle Postgres | Où l'utiliser |
|---|---|---|
| Publishable (sb_publishable_…), ex-anon | anon, ou authenticated si un utilisateur est connecté | Navigateur, application mobile : elle n'ouvre que ce que RLS autorise |
| Secret (sb_secret_…), ex-service_role | service_role (contourne RLS) | Serveur, tâches planifiées uniquement ; refusée par Supabase si elle arrive depuis un navigateur |
La clé secrète contourne toutes les règles RLS. Elle ne va que dans du code serveur (route API, Edge Function, tâche planifiée) et n'est jamais préfixée NEXT_PUBLIC_ : Supabase refuse d'ailleurs une clé secrète envoyée depuis un navigateur (réponse 401)1. Les deux systèmes de clés fonctionnent en parallèle : créer les nouvelles clés ne révoque pas les anciennes, il faut les désactiver explicitement dans Settings > API Keys1.
Comment inscrire et connecter un utilisateur par e-mail ?
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'un-mot-de-passe-long',
options: {
// stocké dans auth.users.raw_user_meta_data
data: { first_name: 'Jean', last_name: 'Dupont' },
// doit figurer dans la liste des Redirect URLs du projet
emailRedirectTo: 'https://votre-app.be/auth/confirm'
}
})
const { data: login, error: loginError } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'un-mot-de-passe-long'
})Sur un projet hébergé, la confirmation de l'adresse e-mail est activée par défaut ; en local et en auto-hébergement, elle est désactivée par défaut3. Les métadonnées passées dans options.data sont modifiables par l'utilisateur lui-même : ne vous en servez jamais pour décider d'un droit d'accès.
Piège de mise en production : le serveur SMTP fourni par Supabase est limité (actuellement 2 messages par heure, uniquement vers des adresses pré-autorisées, c'est-à-dire les membres de l'équipe du projet) et n'est pas prévu pour la production. Branchez votre propre fournisseur SMTP avant d'ouvrir les inscriptions ; la limite démarre alors à 30 messages par heure et se règle dans les paramètres de rate limits4.
Comment proposer un magic link ou un code à usage unique ?
// E-mail : envoie un magic link par défaut
const { error } = await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: { emailRedirectTo: 'https://votre-app.be/auth/confirm' }
})
// Téléphone : envoie un code par SMS (fournisseur SMS à configurer)
const { error: phoneError } = await supabase.auth.signInWithOtp({
phone: '+32470123456'
})Malgré son nom, signInWithOtp envoie un magic link pour l'e-mail. Pour recevoir un code à six chiffres à la place, il faut modifier le modèle d'e-mail « Magic Link ». Par défaut, un utilisateur ne peut demander un lien ou un code qu'une fois toutes les 60 secondes, et il expire après une heure5. Pour le téléphone, vous devez configurer un fournisseur SMS (Twilio, MessageBird, Vonage ou TextLocal) ; WhatsApp n'est possible qu'avec Twilio (documentation Phone Login).
Comment brancher Google, GitHub ou un autre fournisseur OAuth ?
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://votre-app.be/auth/callback',
// seulement si vous devez appeler les API Google au nom de l'utilisateur
queryParams: { access_type: 'offline', prompt: 'consent' }
}
})La documentation liste 19 fournisseurs sociaux (Apple, Azure, GitHub, Google, LinkedIn, Slack, etc.) et permet d'ajouter tout fournisseur compatible OAuth2 ou OIDC. Les jetons du fournisseur ne sont pas stockés par Supabase et Supabase ne les rafraîchit pas : si vous en avez besoin côté serveur, c'est à votre application de les conserver et de les renouveler6.
À quoi ressemble un JWT Supabase ?
Chaque utilisateur connecté reçoit un JWT (JSON Web Token) : un jeton signé, valable une heure par défaut, qui dit qui il est (sub) et avec quel rôle Postgres ses requêtes s'exécutent (role). PostgREST, Storage et Realtime vérifient la signature puis appliquent vos politiques RLS avec ces informations7. Un JWT est composé de trois parties encodées en Base64-URL et séparées par des points : <header>.<payload>.<signature>. L'en-tête indique l'algorithme (HS256, ES256 ou RS256) et l'identifiant de la clé (kid). Le payload d'un utilisateur connecté ressemble à ceci8 :
{
"iss": "https://<project-ref>.supabase.co/auth/v1",
"sub": "123e4567-e89b-12d3-a456-426614174000",
"aud": "authenticated",
"role": "authenticated",
"exp": 1640995200,
"iat": 1640991600,
"aal": "aal1",
"amr": [{ "method": "password", "timestamp": 1640991600 }],
"session_id": "…",
"email": "user@example.com",
"phone": "",
"is_anonymous": false,
"app_metadata": { "provider": "email", "providers": ["email"] },
"user_metadata": { "name": "Jean Dupont" }
}| Claim | Signification |
|---|---|
| sub | UUID de l'utilisateur (ce que renvoie auth.uid()) |
| role | Rôle Postgres utilisé pour la requête : anon, authenticated ou service_role |
| aal | Niveau d'authentification : aal1 (un facteur), aal2 (MFA) |
| amr | Méthodes d'authentification utilisées (password, oauth, otp, magiclink…) |
| is_anonymous | Vrai pour un utilisateur connecté en mode anonyme |
| app_metadata | Données contrôlées par le serveur (fournisseur, rôles applicatifs) : utilisables pour l'autorisation |
| user_metadata | Données modifiables par l'utilisateur lui-même : jamais pour une décision d'autorisation |
Les claims iss, aud, exp, iat, sub, role, aal, session_id, email, phone et is_anonymous sont obligatoires et ne peuvent pas être retirés811.
Combien de temps dure un token et comment est-il renouvelé ?
La durée par défaut est d'une heure ; Supabase déconseille de descendre sous cinq minutes (charge serveur, décalages d'horloge)10. La session contient aussi un refresh token, utilisable une seule fois (avec une tolérance de 10 secondes par défaut) pour obtenir une nouvelle paire access token / refresh token10. Les bibliothèques clientes renouvellent la session automatiquement avant expiration ; vous n'avez normalement pas à appeler refreshSession() vous-même.
Comment lire le JWT dans PostgreSQL ?
select auth.uid(); -- UUID de l'utilisateur (claim sub)
select auth.jwt() ->> 'role'; -- anon | authenticated
select auth.jwt() ->> 'email';
select auth.jwt() ->> 'aal'; -- aal1 | aal2
select auth.jwt() -> 'app_metadata' ->> 'org_id'; -- claim personnaliséDans une politique RLS, encadrez l'appel par un select : using ((select auth.uid()) = user_id). Postgres évalue alors la fonction une fois par requête au lieu d'une fois par ligne. Exemple d'exigence de MFA sur une table sensible :
create policy "MFA obligatoire pour les factures"
on public.invoices
as restrictive
for all
to authenticated
using ((select auth.jwt() ->> 'aal') = 'aal2');Le détail des politiques est dans notre guide des droits d'accès : RLS, rôles et permissions.
Comment ajouter des claims personnalisés ?
Avec le Custom Access Token Hook : une fonction Postgres (ou un endpoint HTTP) appelée par Supabase Auth juste avant d'émettre chaque token11. Elle reçoit user_id, claims et authentication_method, et renvoie l'événement modifié. Exemple : ajouter l'organisation et le forfait de l'utilisateur dans app_metadata. (Pour un modèle de rôles admin / modérateur complet, avec tables et policies, voir le guide des droits d'accès.)
create or replace function public.custom_access_token_hook(event jsonb)
returns jsonb
language plpgsql
stable
as $$
declare
claims jsonb;
v_tier text;
v_org uuid;
begin
select p.subscription_tier, p.organization_id
into v_tier, v_org
from public.profiles p
where p.id = (event ->> 'user_id')::uuid;
claims := event -> 'claims';
if jsonb_typeof(claims -> 'app_metadata') is null then
claims := jsonb_set(claims, '{app_metadata}', '{}');
end if;
claims := jsonb_set(claims, '{app_metadata,subscription}', to_jsonb(v_tier));
claims := jsonb_set(claims, '{app_metadata,org_id}', to_jsonb(v_org));
return jsonb_set(event, '{claims}', claims);
end;
$$;
-- Seul Supabase Auth doit pouvoir exécuter le hook et lire la table
grant usage on schema public to supabase_auth_admin;
grant execute on function public.custom_access_token_hook to supabase_auth_admin;
revoke execute on function public.custom_access_token_hook from authenticated, anon, public;
grant select on table public.profiles to supabase_auth_admin;Activez ensuite le hook dans le tableau de bord (Authentication > Hooks) ou, en local, dans supabase/config.toml11 :
[auth.hook.custom_access_token]
enabled = true
uri = "pg-functions://postgres/public/custom_access_token_hook"Trois précautions. Si profiles est protégée par RLS, ajoutez une politique qui autorise le rôle supabase_auth_admin à la lire. Les claims obligatoires doivent rester présents, sinon Auth renvoie une erreur. Et un claim n'est mis à jour qu'au prochain token émis : un changement de forfait peut mettre jusqu'à une heure à apparaître côté client.
Comment lire la session sans ouvrir une faille ?
Dans le navigateur, onAuthStateChange suffit pour réagir aux connexions et déconnexions. Côté serveur, la règle est stricte : le cookie de session peut être forgé, donc on appelle getClaims(). Sur les projets qui signent leurs jetons avec des clés asymétriques (le défaut des nouveaux projets), la vérification se fait localement avec les clés publiques mises en cache ; sinon, elle passe par le serveur Auth2.
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
// SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, ...
}
)
// au démontage du composant
subscription.unsubscribe()Comment vérifier un JWT reçu par une API ?
Ne vous fiez jamais à un token simplement décodé, ni à getSession() lorsque la session vient de cookies ou d'en-têtes : ces valeurs ne sont pas authentifiées (référence getClaims). Deux méthodes sûres :
supabase.auth.getClaims(token)vérifie la signature localement grâce aux clés publiques du projet (/auth/v1/.well-known/jwks.json, mises en cache) si le projet utilise des clés asymétriques ; sinon, elle interroge le serveur Auth.supabase.auth.getUser(token)interroge toujours le serveur Auth : plus lent, mais détecte une session révoquée avant l'expiration du token.
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PUBLISHABLE_KEY!
)
export async function GET(request: Request) {
const authHeader = request.headers.get('Authorization')
if (!authHeader?.startsWith('Bearer ')) {
return Response.json({ error: 'Non autorisé' }, { status: 401 })
}
const token = authHeader.slice('Bearer '.length)
const { data, error } = await supabase.auth.getClaims(token)
if (error || !data) {
return Response.json({ error: 'Token invalide' }, { status: 401 })
}
return Response.json({ userId: data.claims.sub, role: data.claims.role })
}Hors des bibliothèques Supabase, vérifiez la signature avec une bibliothèque éprouvée comme jose et le JWKS du projet ; n'implémentez pas l'algorithme vous-même7.
Faut-il migrer vers les clés de signature asymétriques ?
Oui : Supabase déconseille fortement le secret partagé HS256, qui complique la conformité (SOC 2, PCI-DSS, HIPAA), permet l'usurpation en cas de fuite et se fait difficilement pivoter7. Les clés asymétriques (ES256, RS256) permettent une vérification sans appel réseau (plus rapide, notamment en edge), une clé privée qu'on ne peut plus extraire et une rotation sans interruption9. Pour un projet encore signé avec le secret partagé, vérifiez chaque token auprès du serveur Auth plutôt qu'avec le secret, puis migrez. La migration se lance depuis Settings > JWT Keys (« Migrate JWT secret ») ; les tokens déjà émis restent acceptés jusqu'à leur expiration. Avant de révoquer l'ancien secret, vérifiez qu'aucun de vos services ne vérifie les tokens avec lui, et désactivez les clés anon / service_role, qui sont signées par ce secret.
Comment protéger des routes dans Next.js ?
Les Server Components ne peuvent pas écrire de cookies : un fichier proxy.ts (Next.js 16) ou middleware.ts (Next.js 15 et antérieur) rafraîchit le jeton à chaque requête et redirige les visiteurs non connectés2. Le code ci-dessous reprend l'exemple officiel with-supabase.
// lib/supabase/proxy.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({ request })
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
}
}
}
)
// Ne rien exécuter entre createServerClient et getClaims()
const { data } = await supabase.auth.getClaims()
const user = data?.claims
if (!user && request.nextUrl.pathname.startsWith('/dashboard')) {
const url = request.nextUrl.clone()
url.pathname = '/login'
return NextResponse.redirect(url)
}
// Renvoyer supabaseResponse tel quel, sinon la session se désynchronise
return supabaseResponse
}
// proxy.ts (racine du projet)
import { updateSession } from '@/lib/supabase/proxy'
import { type NextRequest } from 'next/server'
export async function proxy(request: NextRequest) {
return await updateSession(request)
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)']
}Deux détails expliquent la plupart des « déconnexions aléatoires » : renvoyer un autre objet réponse que supabaseResponse sans y recopier les cookies, et rafraîchir le jeton à deux endroits en parallèle (un jeton de rafraîchissement réutilisé hors de la fenêtre de tolérance révoque toute la session). La documentation demande aussi de recopier sur la réponse les en-têtes anti-cache transmis par setAll, pour qu'un CDN ne mette pas une session en cache2.
Que fait exactement la déconnexion ?
await supabase.auth.signOut() // scope 'global' par défaut
await supabase.auth.signOut({ scope: 'local' }) // cet appareil seulementPar défaut, signOut() utilise la portée global et termine toutes les sessions actives de l'utilisateur ; les portées local et others limitent l'effet à la session courante ou aux autres sessions (documentation Signing out).
Comment intégrer Supabase Auth dans une application React ?
Cette partie vise une application React rendue côté navigateur (Vite, par exemple). Avec du rendu serveur (Next.js), la session circule par cookies entre serveur et navigateur : reportez-vous aux deux sections précédentes. Dans le cas du navigateur seul, l'intégration tient en trois pièces : un client unique créé avec l'URL du projet et la clé publishable, un contexte qui suit la session via onAuthStateChange, et une couche de données, idéalement TanStack Query, pour le cache et l'invalidation. La sécurité ne se joue pas dans React : elle repose sur la RLS côté base, puisque tout ce qui tourne dans le navigateur est visible1.
Installer Supabase dans un projet React
npm create vite@latest mon-app -- --template react-ts
cd mon-app
npm install @supabase/supabase-jsLes variables d'environnement, dans .env.local, reprennent les noms de la documentation officielle12 :
VITE_SUPABASE_URL=https://<project_ref>.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=sb_publishable_...Seule la clé publishable a sa place côté navigateur : la clé secret contourne toute RLS.
Créer un client typé
Générez les types TypeScript à partir du schéma réel, puis passez-les au client : les noms de tables, de colonnes et les résultats de requêtes deviennent vérifiés à la compilation (documentation Generating Types).
npx supabase gen types typescript --project-id <project_ref> > src/lib/database.types.ts// src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from './database.types'
export const supabase = createClient<Database>(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
)Dans le navigateur, la session est conservée et rafraîchie automatiquement par défaut : inutile de renseigner persistSession et autoRefreshToken. Créez ce client une seule fois et importez-le partout.
Partager la session avec un contexte React
onAuthStateChange émet un événement INITIAL_SESSION dès que la session stockée est chargée, puis SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, etc. Un seul abonnement suffit donc à initialiser et suivre l'état, sans appel préalable à getSession() (code source de GoTrueClient).
// src/contexts/AuthContext.tsx
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
import type { Session, User } from '@supabase/supabase-js'
import { supabase } from '../lib/supabase'
type AuthContextType = {
user: User | null
session: Session | null
loading: boolean
signOut: () => Promise<void>
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: ReactNode }) {
const [session, setSession] = useState<Session | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session) // INITIAL_SESSION, SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED...
setLoading(false)
})
return () => subscription.unsubscribe()
}, [])
const value: AuthContextType = {
session,
user: session?.user ?? null,
loading,
signOut: async () => { await supabase.auth.signOut() },
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
export function useAuth() {
const ctx = useContext(AuthContext)
if (!ctx) throw new Error('useAuth doit être utilisé dans <AuthProvider>')
return ctx
}Gardez le rappel léger : un callback lent retarde les événements suivants. Pour décider d'un accès côté serveur, ne vous fiez pas à l'objet utilisateur de la session stockée : vérifiez le JWT avec getClaims() ou interrogez getUser().
Écrire ses hooks de requête, puis passer à TanStack Query
Un hook maison suffit pour un écran simple. Attention aux dépendances : la fonction de requête doit être stable ou la requête se relancera à chaque rendu.
import { useCallback, useEffect, useState } from 'react'
import type { PostgrestError } from '@supabase/supabase-js'
export function useSupabaseQuery<T>(
queryFn: () => PromiseLike<{ data: T | null; error: PostgrestError | null }>,
deps: unknown[] = []
) {
const [data, setData] = useState<T | null>(null)
const [error, setError] = useState<PostgrestError | null>(null)
const [loading, setLoading] = useState(true)
const run = useCallback(queryFn, deps) // eslint-disable-line react-hooks/exhaustive-deps
const refetch = useCallback(async () => {
setLoading(true)
const { data, error } = await run()
setData(data)
setError(error)
setLoading(false)
}, [run])
useEffect(() => { refetch() }, [refetch])
return { data, error, loading, refetch }
}Dès que plusieurs écrans partagent les mêmes données, passez à TanStack Query : cache, déduplication, rafraîchissement et invalidation sont déjà résolus. Syntaxe de la version 5, version majeure actuelle : les options se passent en objet et invalidateQueries prend une queryKey (documentation TanStack).
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { supabase } from '../lib/supabase'
export function usePosts() {
return useQuery({
queryKey: ['posts'],
queryFn: async () => {
const { data, error } = await supabase
.from('posts')
.select('id, title, created_at, profiles(username)')
.order('created_at', { ascending: false })
if (error) throw error
return data
},
staleTime: 5 * 60 * 1000,
})
}
export function useCreatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (post: { title: string; content: string }) => {
const { data, error } = await supabase.from('posts').insert(post).select().single()
if (error) throw error
return data
},
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
})
}Lancer une exception quand error est renseigné est indispensable : supabase-js ne rejette pas la promesse en cas d'erreur, il la renvoie dans l'objet résultat, et TanStack Query ne verrait sinon qu'un succès.
Afficher des données en temps réel
Le hook ci-dessous tient une liste à jour à partir de Postgres Changes. La table doit figurer dans la publication supabase_realtime, et le canal doit être fermé au démontage (documentation Postgres Changes).
import { useEffect, useState } from 'react'
import { supabase } from '../lib/supabase'
export function useRealtimeList<T extends { id: string }>(table: string, initial: T[] = []) {
const [rows, setRows] = useState<T[]>(initial)
useEffect(() => {
const channel = supabase
.channel(`${table}-changes`)
.on('postgres_changes', { event: '*', schema: 'public', table }, (payload) => {
setRows((current) => {
switch (payload.eventType) {
case 'INSERT': return [payload.new as T, ...current]
case 'UPDATE': return current.map((r) => (r.id === (payload.new as T).id ? (payload.new as T) : r))
case 'DELETE': return current.filter((r) => r.id !== (payload.old as Partial<T>).id)
default: return current
}
})
})
.subscribe()
return () => { supabase.removeChannel(channel) }
}, [table])
return rows
}Pour les DELETE, l'ancien enregistrement ne contient que la clé primaire, sauf si la table est en replica identity full ; c'est suffisant ici puisque le hook ne lit que id. Au-delà de quelques milliers d'abonnés, Supabase recommande Broadcast plutôt que Postgres Changes : voir notre article sur le temps réel avec Supabase. La logique qui nécessite la clé secrète n'a pas sa place dans React : placez-la dans une Edge Function Supabase ou votre propre back-end, appelé avec le JWT de l'utilisateur.
Que vérifier avant la mise en production ?
- SMTP personnalisé configuré et limites d'envoi ajustées4.
- CAPTCHA sur l'inscription, la connexion et la réinitialisation : Supabase prend en charge hCaptcha et Cloudflare Turnstile (documentation CAPTCHA).
- Clé secrète uniquement côté serveur ; clés
anon/service_roleremplacées avant leur retrait1. - Pages et données protégées côté serveur avec
getClaims()2. - Table
public.profilesalimentée par un trigger à l'inscription et protégée par RLS (voir la création et la structure de la base et le guide des droits d'accès). - Protection contre les mots de passe divulgués, session unique par utilisateur et délais d'expiration de session : les délais et la session unique ne sont disponibles qu'à partir du plan Pro10, et la grille tarifaire réserve de même la protection contre les mots de passe divulgués aux plans payants (détail des plans).
Si vous voulez qu'on regarde votre intégration, c'est le métier de notre équipe d'intégration de Supabase dans vos outils.
Sources
- API keys (documentation Supabase)
- Creating a Supabase client for SSR (documentation Supabase)
- Password-based Auth (documentation Supabase)
- Send emails with custom SMTP (documentation Supabase)
- Passwordless email sign-in (documentation Supabase)
- Social login (documentation Supabase)
- JSON Web Token (JWT) (documentation Supabase)
- JWT Claims Reference (documentation Supabase)
- JWT Signing Keys (documentation Supabase)
- User sessions (documentation Supabase)
- Custom Access Token Hook et Auth Hooks (documentation Supabase)
- Use Supabase with React (documentation Supabase)






Commentaires
Chaque commentaire est relu avant publication, en général sous 24 h ouvrées. Les liens promotionnels ne sont pas publiés.
Aucun commentaire pour l’instant. Une question sur l’article ? Posez-la ci-dessous.