Authentification - rocambille/start-express-react GitHub Wiki
L'authentification est une composante essentielle des applications modernes. StartER propose une implémentation complète et sécurisée, basée sur Express (backend) et React (frontend), avec un JWT stocké dans un cookie sécurisé. Ce système illustre les bonnes pratiques d'une authentification stateless.
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/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.
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",
};
res.cookie("auth", token, cookieOptions);Le cookie est ainsi inaccessible au JavaScript client, prévenant les attaques XSS.
Le serveur supprime simplement le cookie :
res.clearCookie("auth", cookieOptions);
res.sendStatus(204);Certaines routes nécessitent un utilisateur connecté.
Le middleware verifyAccessToken :
- Lit le cookie
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.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
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 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
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.
Cette persistance de session rend l'expérience utilisateur fluide tout en maintenant un haut niveau de sécurité. Le navigateur conserve le cookie, le serveur en vérifie la validité, et React restaure automatiquement l'état d'authentification au démarrage de l'application.
- 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") - Toujours vérifier les tokens côté serveur avant d'accéder à des ressources protégées