Authentification
Comment fonctionne l'authentification dans ScaleRocket — email/mot de passe, OAuth, gestion des sessions et routes protégées.
Authentification
ScaleRocket gère toute l'authentification via l'application dashboard (apps/app) — connexion, inscription, réinitialisation du mot de passe et flux OAuth. Les routes protégées sont appliquées via TanStack Router.
Comment l'authentification fonctionne
- L'utilisateur s'inscrit ou se connecte via l'application dashboard
- Supabase Auth crée une session et retourne un JWT
- Le client Supabase stocke la session dans
localStorage - Chaque requête API inclut le JWT pour l'autorisation
- Les Edge Functions vérifient le JWT pour identifier l'utilisateur
- Le Row Level Security dans PostgreSQL utilise le JWT pour filtrer les données
Comment l'authentification fonctionne
- L'utilisateur s'inscrit ou se connecte via l'application dashboard
- Convex Auth crée une session et la stocke côté serveur
- Le client Convex gère automatiquement le token d'authentification
- Chaque query/mutation Convex est authentifiée automatiquement
- Les fonctions serveur utilisent
getAuthUserId(ctx)pour identifier l'utilisateur - Le système de permissions de Convex contrôle l'accès aux données par fonction
Inscription par email/mot de passe
Le flux d'inscription par défaut :
- L'utilisateur remplit email + mot de passe sur
/signup - L'application appelle
supabase.auth.signUp() - Supabase envoie un email de confirmation
- L'utilisateur clique sur le lien de confirmation
- L'utilisateur est redirigé vers l'application en tant qu'authentifié
// apps/app/src/lib/auth.ts
import { supabase } from "./supabase";
export async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`,
},
});
if (error) throw error;
return data;
}
export async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) throw error;
return data;
}- L'utilisateur remplit email + mot de passe sur
/signup - L'application appelle
signIn("password", formData)avecflow: "signUp" - Convex Auth envoie un email de vérification
- L'utilisateur clique sur le lien de vérification
- L'utilisateur est redirigé vers l'application en tant qu'authentifié
// apps/app/src/lib/auth.ts
import { useAuthActions } from "@convex-dev/auth/react";
// Dans un composant React
const { signIn } = useAuthActions();
// Inscription
async function handleSignUp(email: string, password: string) {
const formData = new FormData();
formData.set("email", email);
formData.set("password", password);
formData.set("flow", "signUp");
await signIn("password", formData);
}
// Connexion
async function handleSignIn(email: string, password: string) {
const formData = new FormData();
formData.set("email", email);
formData.set("password", password);
formData.set("flow", "signIn");
await signIn("password", formData);
}Configuration OAuth (Google, GitHub)
ScaleRocket supporte les fournisseurs OAuth nativement.
Après avoir configuré les fournisseurs dans Supabase (voir Configuration de Supabase), le code côté client est simple :
// apps/app/src/lib/auth.ts
export async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
if (error) throw error;
return data;
}
export async function signInWithGitHub() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "github",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
if (error) throw error;
return data;
}Après avoir configuré les fournisseurs dans Convex Auth, le code côté client est simple :
// apps/app/src/lib/auth.ts
import { useAuthActions } from "@convex-dev/auth/react";
// Dans un composant React
const { signIn } = useAuthActions();
// Google OAuth
async function signInWithGoogle() {
await signIn("google");
}
// GitHub OAuth
async function signInWithGitHub() {
await signIn("github");
}Page de callback d'authentification
Après la redirection OAuth, la page de callback échange le code contre une session :
// apps/app/src/routes/auth/callback.tsx
import { useEffect } from "react";
import { useNavigate } from "@tanstack/react-router";
import { supabase } from "../../lib/supabase";
export default function AuthCallback() {
const navigate = useNavigate();
useEffect(() => {
supabase.auth.onAuthStateChange((event) => {
if (event === "SIGNED_IN") {
navigate({ to: "/dashboard" });
}
});
}, [navigate]);
return <div>Completing sign in...</div>;
}Avec Convex Auth, les callbacks OAuth sont gérés automatiquement. Aucune page de callback n'est nécessaire — l'utilisateur est redirigé vers l'application et la session est établie par le client Convex.
Réinitialisation du mot de passe
ScaleRocket inclut un flux complet de réinitialisation du mot de passe :
// Request password reset
export async function resetPassword(email: string) {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`,
});
if (error) throw error;
}
// Update password (on the reset page)
export async function updatePassword(newPassword: string) {
const { error } = await supabase.auth.updateUser({
password: newPassword,
});
if (error) throw error;
}Le flux :
- L'utilisateur entre son email sur
/forgot-password - Supabase envoie un email de réinitialisation avec un lien magique
- L'utilisateur clique sur le lien et arrive sur
/auth/reset-password - L'utilisateur entre un nouveau mot de passe
- L'application appelle
updateUser()pour définir le nouveau mot de passe
ScaleRocket inclut un flux complet de réinitialisation du mot de passe :
import { useAuthActions } from "@convex-dev/auth/react";
const { signIn } = useAuthActions();
// Demander la réinitialisation du mot de passe
async function resetPassword(email: string) {
const formData = new FormData();
formData.set("email", email);
formData.set("flow", "reset");
await signIn("password", formData);
}
// Le code/lien de réinitialisation est envoyé par email.
// Quand l'utilisateur soumet le nouveau mot de passe avec le token :
async function updatePassword(code: string, newPassword: string) {
const formData = new FormData();
formData.set("code", code);
formData.set("newPassword", newPassword);
formData.set("flow", "reset-verification");
await signIn("password", formData);
}Le flux :
- L'utilisateur entre son email sur
/forgot-password - Convex Auth envoie un email de réinitialisation avec un code/lien
- L'utilisateur clique sur le lien ou entre le code
- L'utilisateur entre un nouveau mot de passe
- L'application appelle
signIn("password", formData)avec le flux de vérification de réinitialisation
Gestion des sessions
Supabase gère le rafraîchissement des sessions automatiquement. Vous pouvez écouter les changements d'état d'authentification :
// apps/app/src/lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
);// Listen for auth changes anywhere in the app
supabase.auth.onAuthStateChange((event, session) => {
if (event === "SIGNED_OUT") {
// Redirect to login
}
if (event === "TOKEN_REFRESHED") {
// Session was refreshed automatically
}
});Obtenir l'utilisateur actuel :
const { data: { user } } = await supabase.auth.getUser();Se déconnecter :
await supabase.auth.signOut();Convex gère les sessions automatiquement. Utilisez le hook useConvexAuth() pour vérifier l'état d'authentification :
import { useConvexAuth } from "convex/react";
function MyComponent() {
const { isAuthenticated, isLoading } = useConvexAuth();
if (isLoading) return <div>Chargement...</div>;
if (!isAuthenticated) return <div>Non connecté</div>;
return <div>Bienvenue !</div>;
}Obtenir l'utilisateur actuel côté serveur :
import { getAuthUserId } from "@convex-dev/auth/server";
// Dans une query ou mutation Convex
const userId = await getAuthUserId(ctx);Se déconnecter :
import { useAuthActions } from "@convex-dev/auth/react";
const { signOut } = useAuthActions();
await signOut();Routes protégées (TanStack Router)
ScaleRocket utilise TanStack Router avec un guard beforeLoad pour protéger les routes :
// apps/app/src/routes/__root.tsx
import { createRootRoute } from "@tanstack/react-router";
import { supabase } from "../lib/supabase";
export const Route = createRootRoute({
beforeLoad: async ({ location }) => {
const { data: { session } } = await supabase.auth.getSession();
const publicRoutes = ["/login", "/signup", "/forgot-password", "/auth/callback"];
const isPublicRoute = publicRoutes.some((route) =>
location.pathname.startsWith(route)
);
if (!session && !isPublicRoute) {
throw redirect({ to: "/login" });
}
if (session && (location.pathname === "/login" || location.pathname === "/signup")) {
throw redirect({ to: "/dashboard" });
}
},
});// apps/app/src/components/AuthGuard.tsx
import { useConvexAuth } from "convex/react";
import { Navigate } from "@tanstack/react-router";
export function AuthGuard({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useConvexAuth();
if (isLoading) return <div>Chargement...</div>;
if (!isAuthenticated) return <Navigate to="/login" />;
return <>{children}</>;
}Enveloppez vos layouts de routes protégées avec AuthGuard pour appliquer l'authentification.
Cela garantit :
- Les utilisateurs non authentifiés sont redirigés vers
/login - Les utilisateurs authentifiés sont redirigés loin des pages de connexion/inscription
- Les routes publiques (connexion, inscription, callback) sont toujours accessibles
Profil utilisateur
Après l'inscription, ScaleRocket crée un profil pour l'utilisateur :
Une ligne est créée dans la table profiles via un trigger de base de données :
-- supabase/migrations/create_profile_trigger.sql
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS trigger AS $$
BEGIN
INSERT INTO public.profiles (id, email, full_name, avatar_url)
VALUES (
NEW.id,
NEW.email,
NEW.raw_user_meta_data->>'full_name',
NEW.raw_user_meta_data->>'avatar_url'
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();Un profil est créé via une mutation getOrCreateProfile, appelée automatiquement après l'authentification :
// convex/users.ts
import { mutation, query } from "./_generated/server";
import { getAuthUserId } from "@convex-dev/auth/server";
export const getOrCreateProfile = mutation({
handler: async (ctx) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
const existing = await ctx.db
.query("profiles")
.withIndex("by_userId", (q) => q.eq("userId", userId))
.unique();
if (existing) return existing._id;
const user = await ctx.db.get(userId);
return await ctx.db.insert("profiles", {
userId,
email: user?.email ?? "",
fullName: user?.name ?? "",
avatarUrl: user?.image ?? "",
});
},
});Prochaines étapes
- Configurer les appels API — authentifier les requêtes vers les Edge Functions
- Configurer le système de crédits — suivre l'utilisation par utilisateur
- Configurer les fournisseurs OAuth — configuration Google et GitHub
Fini ? Marquez cette page comme terminée.