Gestion des sessions, de l'authentification, des autorisations

Gestion des mots de passe

  • ils doivent être stockés de façon hashée dans la BD, jamais en clair
  • la production du hash est non-inversible, mais permet de vérifier la concordance
  • le hash doit être salé, sinon il peut être attaqué par des dictionnaires de valeurs hashées de mots de passe

Librairie bcryptjs

La librairie npm ‘bcrypt’ est un binding d’une librairie C++ qui pose parfois des problèmes d’installation. On lui préferera la librairie ‘bcryptjs’, imprémentée à 100% en JS et totalement compatible avec bcrypt.

Production du hash

Le hash d’un mot de passe est modifié à chaque appel (salage), mais cela n’affecte pas la vérification de la compatibilité d’un mot de passe candidat avec l’une quelconque de ses valeurs de hashage

Vérification d’un mot de passe candidat

Cookies

Un cookie est une paire (clé, valeur) qui est stockée dans un espace dédié du navigateur, associé au nom de domaine appelé.

Un serveur peut mettre dans une response HTTP un ordre de stockage d’un nouveau cookie dans le navigateur client :

Lors de chaque requête sur le même nom de domaine, le navigateur a l’obligation d’inclure systématiquement tous les cookies dans la requête HTTP, dans une ligne :

Exemple:

Tokens JWT

Voir https://jwt.io

Un token JWT est composé de trois parties, séparées par un point :

  • une en-tête qui indique notamment le type de signature
  • un payload json, non crypté. Sa validité est garantie par la signature
  • une signature

Un tel token est généralement créé par le serveur après authentification et transmis au client qui le conserve et le joint à toutes ses requêtes. Le serveur n’accepte de traiter une requête sécurisée que si elle contient un token valide.

Le payload du token contient typiquement l’id de l’utilisateur et une date d’expiration. Contrairement à d’autres types de tokens, le serveur n’a pas besoin de les stocker.

Créer un token

Lire le payload d’un token

Vérifier la signature d’un token

Lors d’une authentification, le serveur doit créer un token d’authentification spécifique à l’utilisateur, et le renvoyer au client, sous forme d’un cookie ou d’une donnée ordinaire.

  • si ce token est envoyé sous forme d’un cookie, le client l’incluera automatiquement dans chacune de ses requêtes ultérieures dans un header tel que Cookie: token=<token>, et à chacune de ces requêtes le serveur pourra vérifier que ce token est valide et pourra retrouver l’utilisateur associé dans sa base de données

  • sinon, ce token peut être stocké par le navigateur dans LocalStorage ou SessionStorage; le client devra alors explicitement rajouter le token à chacune de ses requêtes ultérieures, dans un header tel que Authorization: Bearer <token>

Pour Express, voir les middlewares cookie-parser et express-bearer-token.

Gestion des sessions

  • si on utilise le protocole HTTP, après un échange request -> response, le client et le serveur s’oublient l’un l’autre. On dit souvent que HTTP est un “protocole sans état”. Si on veut gérer des sessions, il faut que le client et le serveur s’échangent des cookies ou des tokens.
  • si on utilise le protocole websocket, la situation est plus simple car la connexion persistante entre le client et le serveur représente en quelque sorte la session.

Sessions avec HTTP et cookies

  • 1 : le client fait une première requête, qui ne contient aucun cookie ‘sessionId’. Le serveur ne trouvant pas de cookie dans la requête, génère un identifiant de session unique tel que ‘xyz’ et ajoute une nouvelle entrée dans sa table de sessions, identifiée par ‘xyz’.
  • 2 : le serveur envoie la réponse au client, avec une demande de stockage du cookie ‘sessionId’: ’xyz’. Le client a l’obligation de stocker le cookie et de l’envoyer dans toute requête ultérieure vers ce nom de domaine.
  • 3 : le client fait une deuxième requête, qui contient donc le cookie ‘sessionId’: ’xyz’. À réception, le serveur retrouve la session associée à ‘xyz’ et peut utiliser les informations qui y sont stockées pour produire sa réponse. Même en absence d’authentification, le serveur peut ainsi assurer une continuité dans la session, par exemple gérer un panier.

Le serveur doit explicitement gérer une table de sessions. Cette table devant résister aux redémarrages du backend, elle est généralement stockée dans une base de données, soit sans persistance comme Redis, soit avec persistance (Postgres, etc.) si on souhaite conserver les données de toutes les sessions.

Si l’application propose une authentification, l’identifiant utilisateur peut tout simplement être ajouté dans la session stockée dans la table des sessions.

Sessions avec HTTP et tokens JWT

  • 1 : l’application cliente, constatant qu’elle ne possède aucun token JWT valide, fait une requête XHR vers le serveur pour lui demander de créer un token JWT. Généralement cette requête est faite après l’authentification, afin de disposer de l’identifiant utilisateur.
  • 2 : en réponse à cette requête XHR, le serveur créé et renvoie au client un token JWT dont le payload contient l’identifiant utilisateur, une date d’expiration et les autorisations accordées. Ce token est signé avec une clé privée dont seul dispose le serveur. À la réception, l’application cliente stocke le token, au choix selon le besoin, dans localStorage, ou dans sessionStorage, ou dans un cookie sécurisé. Le serveur ne stocke pas le token.
  • 3 : l’application client fait ensuite des requêtes métier, en joignant à chaque fois le JWT sous forme de cookie ou d’un header “Bearer token”. À réception, le serveur vérifie la signature, la date d’expiration et les autorisations avant de satisfaire la requête.

Le serveur n’a pas besoin de gérer une table de sessions ; il se sert des informations accessibles dans le payload du JWT pour répondre à la requête.

Sessions avec Websocket

Lorsque le client créé une connexion websocket persistante avec le serveur, cette connexion représente la session. Si de plus elle est créée après une étape d’authentification, la connexion reste sécurisée tant qu’elle n’est pas fermée et il n’est pas nécessaire de joindre un cookie ou un token à chaque envoi de message.

Note à propos de socket.io

L’utilisation des websocket se fait généralement à l’aide de la librairie socket.io. Lors de la connexion d’un client, le serveur socket.io fournit un objet associé à la connexion persistante, qui possède les attributs suivants :

  • data, un objet où peuvent être stockés un identifiant d’utilisateur, un identifiant de session, etc.
  • rooms, qui permet d’implémenter facilement un mécanisme publish-subscribe et fournir des fonctionnalités temps-réel

Par ailleurs, socket.io rétablit automatiquement la connexion lors des inévitables coupures, en préservant les attributs data et rooms. Il fournit donc l’équivalent d’une table de sessions en mémoire, qui suffit pour gérer jusqu’à quelques centaines de connexions simultanées. Au delà, un déploiement multi-serveur plus complexe est nécessaire.

À noter que le rechargement de la page courante cause la destruction de toutes les ressources de la page, y compris la connection websocket. Si on souhaite maintenir l’état de session de l’application lors d’un rechargement de la page (ce qui est généralement souhaité), un mécanisme de transfert des attributs data et room de l’ancienne connexion à la nouvelle doit être explicitement ajouté.