-
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 stateless, basée sur Express et React, avec un JWT stocké dans un cookie sécurisé.
Le processus d'authentification dans StartER repose sur un échange simple :
- L'utilisateur envoie ses identifiants (email, mot de passe)
- Le serveur Express les vérifie
- Un token JWT est généré et stocké dans un cookie HTTP-only
- Le frontend React est informé de la connexion
- À la déconnexion, le cookie est supprimé
Cette approche assure à la fois sécurité, simplicité et cohérence entre le front et le back.
Les fonctionnalités d'authentification sont regroupées dans le module auth :
src/express/modules/auth/
├── authRoutes.ts
└── authActions.ts
| Méthode | Route | Action principale | Description |
|---|---|---|---|
| POST | /api/users |
createUserAndAccessToken |
Inscription (crée l'utilisateur et le cookie) |
| POST | /api/access-tokens |
createAccessToken |
Connexion (génère le JWT et le cookie) |
| DELETE | /api/access-tokens |
destroyAccessToken |
Déconnexion (supprime le cookie) |
| GET | /api/me |
verifyAccessToken |
Vérifie le JWT |
Ces routes sont montées dans src/express/modules/auth/authRoutes.ts et src/express/modules/user/userRoutes.ts.
L'inscription crée un nouvel utilisateur et le connecte automatiquement en une seule requête.
La route POST /api/users est définie dans src/express/modules/user/userRoutes.ts et passe par trois étapes :
-
Validation (
userValidator.validate) : vérifie l'email, le mot de passe et la confirmation via Zod -
Hachage (
authActions.hashPassword) : sécurise le mot de passe avec Argon2id -
Création (
authActions.createUserAndAccessToken) : insère l'utilisateur en base et génère un cookie__Host-auth
router.post(
"/api/users",
userValidator.validate,
authActions.hashPassword,
authActions.createUserAndAccessToken,
);En cas de succès, le serveur répond avec un statut 201 et le cookie d'authentification est automatiquement posé.
Lorsqu'un utilisateur se connecte :
- L'email est recherché dans la base via
userRepository.readByEmailWithPassword - Le mot de passe est vérifié avec Argon2id
- En cas de succès, un JWT est signé avec
jsonwebtoken - Ce token est stocké dans un cookie sécurisé :
const cookieOptions: CookieOptions = {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 60 * 60 * 1000, // 1 hour
};
res.cookie("__Host-auth", token, cookieOptions);Note
Le préfixe __Host- est une convention de sécurité qui impose au navigateur que le cookie soit envoyé uniquement via HTTPS, avec Path=/, et sans attribut Domain. Cela empêche un sous-domaine compromis de manipuler le cookie. Plus de détails dans la page Sécurité.
Le cookie est ainsi inaccessible au JavaScript client, prévenant les attaques XSS.
Le serveur supprime simplement le cookie :
res.clearCookie("__Host-auth", cookieOptions);
res.sendStatus(204);Certaines routes nécessitent un utilisateur connecté.
Le middleware verifyAccessToken :
- Lit le cookie
__Host-auth - Vérifie la validité du JWT
- Ajoute le payload décodé à
req.auth - Rejette la requête (
401) si le token est invalide
const token = req.cookies["__Host-auth"];
req.auth = jwt.verify(token, appSecret);
next();Cela permet dans les middlewares suivants d'accéder à req.auth.sub pour identifier l'utilisateur connecté.
Côté React, la logique d'authentification est centralisée dans un contexte :
src/react/components/auth/AuthContext.tsx.
Ce contexte fournit :
-
user: l'utilisateur courant -
check(): indique si l'utilisateur est connecté -
login(credentials): connexion -
logout(): déconnexion -
register(credentials): inscription
Note
Voir le code complet pour les détails.
fetch("/api/access-tokens", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(credentials),
})
.then((response) => {
if (response.status === 201) {
return response.json();
}
})
.then((user: User) => {
setUser(user);
});Le cookie d'authentification est automatiquement géré par le navigateur.
fetch("/api/access-tokens", {
method: "delete",
}).then((response) => {
if (response.status === 204) {
setUser(null);
}
});Le composant AuthForm affiche dynamiquement :
-
LoginRegisterFormsi l'utilisateur n'est pas connecté -
LogoutFormsi l'utilisateur est connecté
function AuthForm() {
const auth = useAuth();
return auth.check() ? <LogoutForm /> : <LoginRegisterForm />;
}Avec le contexte, ce composant est intégré dans le layout principal src/react/components/Layout.tsx :
<AuthProvider>
<header>
<NavBar />
<BurgerMenu>
<AuthForm />
</BurgerMenu>
</header>
<main>{children}</main>
</AuthProvider>Ainsi, le contexte d'authentification est disponible dans toute l'application.
L'authentification avec cookie HTTP-only permet déjà de maintenir la session côté serveur.
Cependant, sans mécanisme de restauration, le state React (user) est perdu après un rechargement de page.
Pour résoudre ce problème, StartER intègre un endpoint /api/me et un chargement automatique des informations utilisateur dans le contexte d'authentification (AuthContext).
L'endpoint /api/me permet de récupérer les informations de l'utilisateur actuellement authentifié.
Il repose sur la présence du cookie __Host-auth, vérifié et décodé par le middleware verifyAccessToken.
Dans src/express/modules/auth/authRoutes.ts :
router.get("/api/me", authActions.verifyAccessToken, authActions.readMe);Et dans src/express/modules/auth/authActions.ts :
const readMe: RequestHandler = async (req, res) => {
const me = await userRepository.read(Number(req.auth.sub));
res.json(me);
};Cette route renvoie les informations de l'utilisateur (id, email) correspondant à l'identifiant (sub) contenu dans le token JWT.
Ainsi, même après un redémarrage du navigateur ou un rafraîchissement de la page, le client peut récupérer les informations de session à partir du serveur.
Le contexte d'authentification (AuthContext) interroge automatiquement /api/me au montage du composant pour restaurer l'état user si le cookie d'authentification est toujours valide.
useEffect(() => {
fetch("/api/me")
.then((response) => {
if (response.status === 200) {
return response.json();
}
})
.then((user: User) => {
setUser(user);
});
}, []);Grâce à cet effet, la connexion persiste entre les rechargements de page sans nécessiter de nouvelle authentification.
- Lors de la connexion ou de l'inscription, le serveur crée un cookie
__Host-authcontenant un token JWT signé. - Ce cookie est envoyé automatiquement par le navigateur à chaque requête.
- Au montage de l'application React, le
AuthContextappelle/api/mepour restaurer l'utilisateur courant à partir du token. - Si le token a expiré ou est invalide, la requête renvoie
401, etuserrestenull.
En résumé : le cookie persiste côté navigateur, Express le vérifie, React restaure l'état via /api/me.
- Utiliser des cookies HTTP-only : empêche tout accès JavaScript au JWT
- Suivre les recommandations de l'OWASP pour le hachage des mots de passe : au moment de la rédaction, Argon2id avec une configuration minimale de 19 Mio de mémoire, un nombre d'itérations de 2 et 1 degré de parallélisme.
-
Configurer
secure: trueetsameSite: "strict": protège contre les attaques CSRF -
Limiter la durée de vie des tokens (
expiresIn: "1h") : un token volé ne peut être exploité que pendant sa durée de validité. Une durée courte réduit la fenêtre d'attaque. - Toujours vérifier les tokens côté serveur avant d'accéder à des ressources protégées : un JWT peut être falsifié côté client. Seule la vérification de la signature côté serveur garantit l'authenticité du token.
Bien démarrer
Explications
Guides
Référence