Skip to content

Authentification

rocambille edited this page Mar 10, 2026 · 10 revisions

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 fonctionnement de l'authentification

Le processus d'authentification dans StartER repose sur un échange simple :

  1. L'utilisateur envoie ses identifiants (email, mot de passe)
  2. Le serveur Express les vérifie
  3. Un token JWT est généré et stocké dans un cookie HTTP-only
  4. Le frontend React est informé de la connexion
  5. À la déconnexion, le cookie est supprimé

Cette approche assure à la fois sécurité, simplicité et cohérence entre le front et le back.

Implémentation dans StartER

Côté serveur : Express

Les fonctionnalités d'authentification sont regroupées dans le module auth :

src/express/modules/auth/
├── authRoutes.ts
└── authActions.ts

Endpoints principaux

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.

Inscription (createUserAndAccessToken)

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 :

  1. Validation (userValidator.validate) : vérifie l'email, le mot de passe et la confirmation via Zod
  2. Hachage (authActions.hashPassword) : sécurise le mot de passe avec Argon2id
  3. 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é.

Création du token (createAccessToken)

Lorsqu'un utilisateur se connecte :

  1. L'email est recherché dans la base via userRepository.readByEmailWithPassword
  2. Le mot de passe est vérifié avec Argon2id
  3. En cas de succès, un JWT est signé avec jsonwebtoken
  4. 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.

Déconnexion (destroyAccessToken)

Le serveur supprime simplement le cookie :

res.clearCookie("__Host-auth", cookieOptions);
res.sendStatus(204);

Vérification du token (verifyAccessToken)

Certaines routes nécessitent un utilisateur connecté. Le middleware verifyAccessToken :

  1. Lit le cookie __Host-auth
  2. Vérifie la validité du JWT
  3. Ajoute le payload décodé à req.auth
  4. 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é client : React

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.

Connexion

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.

Déconnexion

fetch("/api/access-tokens", {
  method: "delete",
}).then((response) => {
  if (response.status === 204) {
    setUser(null);
  }
});

Interface utilisateur

Le composant AuthForm affiche dynamiquement :

  • LoginRegisterForm si l'utilisateur n'est pas connecté
  • LogoutForm si 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.

Persistance de la session

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).

Endpoint /api/me côté Express

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.

Restauration de la session côté React

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.

Récapitulatif

  1. Lors de la connexion ou de l'inscription, le serveur crée un cookie __Host-auth contenant un token JWT signé.
  2. Ce cookie est envoyé automatiquement par le navigateur à chaque requête.
  3. Au montage de l'application React, le AuthContext appelle /api/me pour restaurer l'utilisateur courant à partir du token.
  4. Si le token a expiré ou est invalide, la requête renvoie 401, et user reste null.

En résumé : le cookie persiste côté navigateur, Express le vérifie, React restaure l'état via /api/me.

Bonnes pratiques et Cas d'usage

  1. Utiliser des cookies HTTP-only : empêche tout accès JavaScript au JWT
  2. 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.
  3. Configurer secure: true et sameSite: "strict" : protège contre les attaques CSRF
  4. 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.
  5. 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.

Voir aussi

Clone this wiki locally