-
Notifications
You must be signed in to change notification settings - Fork 7
Authentification
Résumé : StartER propose une implémentation complète et sécurisée d'authentification sans mot de passe (Magic Link), basée sur des jetons opaques stockés en base de données et un JWT de session stocké dans un cookie sécurisé.
Le processus dans StartER repose sur un lien de connexion envoyé par email :
- L'utilisateur saisit son email.
- Le serveur génère un jeton opaque (random bytes), le hache, le stocke en base et envoie le lien par email via SMTP (ou l'affiche dans le terminal en développement).
- L'utilisateur clique sur le lien, arrivant sur une page de vérification.
- Le serveur vérifie la validité du jeton (non expiré, non consommé).
- Un token JWT de session est généré et stocké dans un cookie HTTP-only.
- Le frontend React est informé de la connexion via le
AuthContext.
Les fonctionnalités d'authentification sont regroupées dans le module auth :
src/express/modules/auth/
├── authActions.ts (Logique métier et middleware)
├── authRepository.ts (Accès aux jetons en base)
└── authRoutes.ts (Définition des endpoints)
| Méthode | Route | Action | Description |
|---|---|---|---|
| POST | /api/auth/magic-link |
sendMagicLink |
Envoie le lien de connexion par email |
| POST | /api/auth/verify |
verifyMagicLink |
Vérifie le jeton opaque et pose le cookie |
| DELETE | /api/auth/logout |
destroyAccessToken |
Déconnexion (supprime le cookie) |
| GET | /api/me |
readMe |
Renvoie l'utilisateur connecté (req.me) |
Lorsqu'un utilisateur demande un lien, le serveur cherche ou crée l'utilisateur, génère un jeton aléatoire sécurisé, puis stocke son hachage SHA-256 en base.
Afin de faciliter le développement local sans imposer la configuration immédiate d'un outil comme Docker ou Mailpit, StartER adopte une approche d'email hybride :
-
En développement : si la variable
SMTP_URLn'est pas définie dans votre.env, le lien magique de connexion ne sera pas envoyé par email, mais sera affiché dans la console de votre terminal. Vous pourrez cliquer dessus pour vous connecter. -
En production : la variable
SMTP_URLdevient obligatoire. Le serveur refusera de démarrer sans elles pour s'assurer que l'application est bien capable de relayer les emails aux véritables utilisateurs.
const magicLink = `${trustedBaseUrl}/verify?token=${rawToken}`;
if (transporter) {
// Le transporteur a pu être configuré avec la variable SMTP_URL du .env
await transporter.sendMail({
from: "starter@mail.com",
to: email,
subject: "Lien de connexion",
html: `<a href="${magicLink}">Cliquez ici pour vous connecter</a>`,
});
} else {
// Fallback de développement : affiche le lien dans la console
console.info(`Magic Link for ${email}:`);
console.info(magicLink);
}Lorsque l'utilisateur clique sur le lien :
- Le client envoie le jeton au serveur.
- Le serveur compare le hachage reçu avec celui en base.
- Si valide, il génère un JWT de session et le place dans un cookie sécurisé :
const cookieOptions: CookieOptions = {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 jours
};
res.cookie("__Host-auth", sessionToken, cookieOptions);Certaines routes nécessitent d'identifier l'utilisateur. StartER injecte l'entité User complète dans req.me.
const verifyAccessToken: RequestHandler = async (req, res, next) => {
const token = req.cookies["__Host-auth"];
const payload = auth.verify(token); // Vérifie la signature du JWT
// Récupère l'utilisateur depuis la base de données
const me = await userRepository.find(Number(payload.sub));
if (me == null) throw new Error("User not found");
req.me = me; // Injecte l'utilisateur dans la requête
next();
};Cela sécurise l'application : si un utilisateur est supprimé de la base, son JWT devient instantanément invalide même s'il n'a pas expiré.
La logique est centralisée dans src/react/components/auth/AuthContext.tsx.
-
MagicLinkForm: Saisie de l'email et appel à/api/auth/magic-link. -
VerifyPage: Route/verifyqui récupère le token dans l'URL et l'envoie à/api/auth/verify.
Au chargement de l'application, le routeur React (via la fonction loader de la route racine) appelle /api/me. Si le cookie est présent et valide, l'utilisateur est récupéré et automatiquement injecté dans le composant AuthProvider.
// src/react/routes.tsx
export const routes = [
{
loader: async ({ request }) => {
// Sur le serveur : transfert explicite des cookies
const response = await fetch("/api/me", {
headers: { cookie: request.headers.get("cookie") ?? "" },
});
const me = response.ok ? await response.json() : null;
return { me };
},
Component: () => {
const { me } = useLoaderData<{ me: User | null }>();
return <AuthProvider initialUser={me}>...</AuthProvider>;
}
}
];- Jetons à usage unique : une fois vérifié, le jeton de Magic Link est marqué comme "consommé" en base de données.
- Hachage des jetons : les jetons opaques ne sont jamais stockés en clair (SHA-256).
- Cookies HTTP-only : le JWT de session est invisible pour le JavaScript, protégeant contre les vols de session via XSS.
-
Trusted Identity (
req.me) : utiliser l'objetreq.medans vos controllers garantit que vous manipulez un utilisateur existant et à jour.
Bien démarrer
Explications
Guides
Référence
Sous le capot