Dans cet article, nous allons voir comment mettre en place un système d’authentification JWT sécurisé en divisant le token en deux cookies distincts. En utilisant Symfony 7, LexikJWTAuthenticationBundle et API Platform, cette méthode permet de renforcer la sécurité de votre application en limitant les risques d’interception de token.
🎯 Objectif : Protéger votre application efficacement
Problème : Un seul token peut être risqué
Utiliser un token JWT à longue durée de vie présente des risques : un attaquant qui l’intercepte pourrait se connecter à votre application pour une période indéfinie, et ce, sans avoir besoin du mot de passe de l’utilisateur. Pour éviter cela, diviser le token JWT en deux cookies permet de réduire les risques et de protéger les informations sensibles.
Solution : Diviser le token JWT en deux cookies
Pour maximiser la sécurité, la technique du split token divise le JWT en deux parties et utilise des cookies sécurisés pour stocker ces morceaux de token. Cela évite les inconvénients d’un refresh token, souvent plus exposé dans ce type d’architecture.
🤔 Pourquoi ne pas utiliser un refresh token ?
Qu’est-ce qu’un refresh token ?
Le refresh token est un moyen de renouveler un JWT token sans demander à l’utilisateur de ressaisir son mot de passe. Il permet ainsi de prolonger la session utilisateur.
Les limites de sécurité du refresh token
Le refresh token présente un risque : s’il est intercepté, un attaquant peut l’utiliser pour générer des JWT tokens sans limite et maintenir la session active sans interruption. La technique du split token permet d’éviter ce risque en se passant de refresh token.
💡 Solution : Diviser le token JWT en deux cookies
Fonctionnement : Le split token
La technique consiste à diviser le JWT en deux parties : un cookie contenant le header et le payload, et un autre cookie pour la signature. Le cookie avec la signature est en mode HTTPOnly, ce qui empêche son accès via JavaScript et limite les risques d’interception. Le cookie avec le header et le payload est, lui, accessible côté client pour certaines opérations.
Crédit : Peter Locke, dans son article Getting Token Authentication Right in a Stateless Single Page Application
Pour une analyse plus détaillée, cet article offre un complément utile, notamment dans la section "Keeping A User Authenticated with the Right UX: The Two Cookie JWT Approach".
🛠️ Mise en place dans Symfony 7
Voici comment configurer le split token dans un projet Symfony 7 avec API Platform pour un niveau de sécurité élevé.
🔧 Étape 1 : Configuration de LexikJWTAuthenticationBundle
- Désactivez le bearer token et activez le split cookie.
- Configurez les cookies
jwt_hp
(header/payload) etjwt_s
(signature) danslexik_jwt_authentication.yaml
:
lexik_jwt_authentication:
token_extractors:
authorization_header:
enabled: false
split_cookie:
enabled: true
cookies:
- jwt_hp
- jwt_s
set_cookies:
jwt_hp:
lifetime: null
samesite: strict
path: /
domain: null
httpOnly: false
secure: true
split:
- header
- payload
jwt_s:
lifetime: 0
samesite: strict
path: /
domain: null
httpOnly: true
secure: true
split:
- signature
🛡️ Étape 2 : Configuration des firewalls
Dans la configuration des firewalls, on définit un point d’entrée JWT et on configure json_login
pour que le success_handler et le failure_handler soient gérés par LexikJWTAuthenticationBundle :
security:
firewalls:
main:
stateless: false
provider: app_admin_provider
entry_point: jwt
json_login:
check_path: /api/auth
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
jwt: ~
logout:
path: /api/auth/logout
🔄 Étape 3 : Créer les routes d'authentification
Dans cette configuration, la route /api/auth
est utilisée pour l’authentification et /api/auth/logout
pour la déconnexion. Voici le code du contrôleur :
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
class CheckAuth extends AbstractController
{
#[Route('/api/auth/check', name: 'api_check_auth', methods: ['GET'])]
public function checkAuthAction(): JsonResponse
{
if(is_null($this->getUser())) {
return new JsonResponse(['error' => 'Unauthorized'], 401);
}else {
return new JsonResponse(['success' => 'Authorized'], 200);
}
}
#[Route('/api/auth/logout', name: 'api_logout')]
public function logout(): JsonResponse
{
throw new \LogicException('Cette méthode est interceptée par la configuration de sécurité.');
}
}
🚪 Étape 4 : Listener pour la déconnexion
Pour garantir que les cookies soient supprimés lors de la déconnexion, on ajoute un listener LogoutListener
car par défaut LexikJWTAuthenticationBundle ne supprime pas les cookies dans la configuration split token :
<?php
namespace App\Listener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Http\Event\LogoutEvent;
#[AsEventListener(event: LogoutEvent::class, method: 'onLogout')]
class LogoutListener
{
public function onLogout(LogoutEvent $logoutEvent): void
{
setcookie('jwt_hp', '', -1, '/');
setcookie('jwt_s', '', -1, '/');
$logoutEvent->setResponse(new JsonResponse([]));
}
}
📑 Étape 5 : Configurer API Platform pour l'authentification
Pour que API Platform reconnaisse l'authentification JWT, on ajoute la configuration pour que JWT soit envoyé dans les en-têtes :
api_platform:
swagger:
api_keys:
JWT:
name: Authorization
type: header
Enfin, on complète la configuration dans LexikJWTAuthenticationBundle pour que le Swagger d'API Platform prenne en compte la route d'authentification :
lexik_jwt_authentication:
api_platform:
check_path: /api/auth
username_path: email
password_path: password
Pour encore plus de détails, vous pouvez consulter la documentation officielle d’API Platform sur la configuration JWT. Notez cependant que la documentation n’aborde pas la configuration spécifique au split token.
🎉 Conclusion
Vous avez maintenant une authentification JWT sécurisée en utilisant deux cookies pour diviser le token. Cette méthode renforce la sécurité en réduisant les risques d’interception tout en s’intégrant harmonieusement avec Symfony 7 et API Platform. Il ne vous reste plus qu’à connecter votre front-end pour profiter de ce nouveau système d'authentification sécurisé.