Ce qui est branché · 3 min
Authentification
Liens magiques et OAuth sur Supabase Auth, et les deux pannes qui coûtent une journée chacune quand on les découvre.
Les sessions vivent dans des cookies que le serveur peut lire, et c'est ce qui
permet à requireUser() de fonctionner dans un Server Component sans état de
chargement.
import { requireUser, currentUser } from "@/lib/auth";
// Dans une page qui n'a aucun sens déconnecté. Redirige s'il n'y a pas de session.
const user = await requireUser();
// Là où être déconnecté est un état normal, la navigation par exemple.
const maybe = await currentUser();currentUser est enveloppé dans le cache de React, donc un layout et la page
qu'il contient partagent un seul appel au serveur d'authentification au lieu
d'en faire deux.
Le piège du lien magique
Les modèles d'e-mail par défaut de Supabase utilisent {{ .ConfirmationURL }},
qui renvoie la session dans un fragment d'URL. Un fragment n'est jamais
envoyé au serveur. Votre route de callback voit une query string vide, ne crée
aucune session, et renvoie l'utilisateur vers la page de connexion sans erreur à
lui montrer. On dirait que le lien est cassé. Il ne l'est pas : la session est
partie vers le navigateur et personne ne l'a lue.
Réécrivez les trois modèles, confirmation d'inscription, lien magique et changement d'adresse, pour envoyer un token hash vers votre propre callback :
{{ .SiteURL }}/auth/callback?token_hash={{ .TokenHash }}&type=magiclink
Le piège du cookie
Dans un route handler, modifier le store de cookies de next/headers ne
suit pas sur un NextResponse.redirect. Vous vérifiez le token, vous posez
la session, vous redirigez, et la session a disparu.
Liez plutôt le client Supabase à l'objet de réponse :
let response = NextResponse.redirect(`${origin}${target}`);
const supabase = createServerClient(url, key, {
cookies: {
getAll: () => request.cookies.getAll(),
setAll: (list) => {
list.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options));
},
},
});
await supabase.auth.verifyOtp({ type, token_hash: tokenHash });
return response;C'est ce que fait src/app/auth/callback/route.ts, et le commentaire au-dessus
le dit, parce que c'est exactement le code que quelqu'un simplifiera dans six
mois.
Les profils
Une ligne dans auth.users ne vous appartient pas. Le schéma crée une ligne
public.profiles correspondante depuis un trigger :
create trigger on_auth_user_created
after insert on auth.users
for each row execute function public.handle_new_user();Un trigger, et pas un insert dans le handler d'inscription, parce qu'un
utilisateur peut arriver par OAuth, par une invitation ou par le tableau de
bord, et seule la base voit les trois. Faites-le côté application et vous aurez
un jour un utilisateur sans profil et une page qui plante sur
profile.full_name.
Ajouter un fournisseur
Activez-le dans le tableau de bord Supabase, ajoutez l'URL de redirection, et ajoutez un bouton. L'appel client tient en une ligne :
await supabaseBrowser().auth.signInWithOAuth({
provider: "github",
options: { redirectTo: `${location.origin}/auth/callback?next=/app` },
});Aucune nouvelle route. Le même callback s'en charge, puisqu'il lit déjà ?code=
aussi bien que ?token_hash=.
Une erreur ou un manque sur cette page ? Dites-le-nous.