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.

Vue d'ensemble

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.

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

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",
};

res.cookie("auth", token, cookieOptions);

Le cookie est ainsi inaccessible au JavaScript client, prévenant les attaques XSS.

Déconnexion (destroyAccessToken)

Le serveur supprime simplement le cookie :

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

Vérification du token (verifyAccessToken)

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

  1. Lit le cookie 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.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

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

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.

Sécurité et bonnes pratiques

  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")
  5. Toujours vérifier les tokens côté serveur avant d'accéder à des ressources protégées
⚠️ **GitHub.com Fallback** ⚠️