Comment activer CORS avec le cookie HTTPOnly pour sécuriser le jeton ?

Publié: 2021-12-29

Dans cet article, nous voyons comment activer le CORS (Cross-Origin Resource Sharing) avec le cookie HTTPOnly pour sécuriser nos jetons d'accès.

De nos jours, les serveurs principaux et les clients frontaux sont déployés sur différents domaines. Par conséquent, le serveur doit activer CORS pour permettre aux clients de communiquer avec le serveur sur les navigateurs.

De plus, les serveurs implémentent une authentification sans état pour une meilleure évolutivité. Les jetons sont stockés et conservés côté client, mais pas côté serveur comme une session. Pour des raisons de sécurité, il est préférable de stocker les jetons dans des cookies HTTPOnly.

Pourquoi les demandes Cross-Origin sont-elles bloquées ?

Supposons que notre application frontale soit déployée sur https://app.geekflare.com . Un script chargé dans https://app.geekflare.com ne peut demander que des ressources de même origine.

Chaque fois que nous essayons d'envoyer une demande d'origine croisée vers un autre domaine https://api.geekflare.com ou un autre port https://app.geekflare.com:3000 ou un autre schéma http://app.geekflare.com , le la demande d'origine croisée sera bloquée par le navigateur.

Mais pourquoi la même demande bloquée par le navigateur peut-elle être envoyée depuis n'importe quel serveur principal à l'aide d'une requête curl ou envoyée à l'aide d'outils comme le facteur sans aucun problème CORS. C'est en fait pour la sécurité de protéger les utilisateurs contre des attaques comme CSRF (Cross-Site Request Forgery).

Prenons un exemple, supposons qu'un utilisateur se soit connecté à son propre compte PayPal dans son navigateur. Si nous pouvons envoyer une demande d'origine paypal.com à paypal.com partir d'un script chargé sur un autre domaine malicious.com sans aucune erreur/blocage paypal.com si nous paypal.com demande de même origine.

Les attaquants peuvent facilement envoyer leur page malveillante https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account en la convertissant en URL courte pour masquer l'URL réelle. Lorsque l'utilisateur clique sur un lien malveillant, le script chargé dans le domaine malicious.com enverra une demande d'origine croisée à PayPal pour transférer le montant de l'utilisateur vers le compte PayPal de l'attaquant qui sera exécuté. Tous les utilisateurs qui se sont connectés à leur compte PayPal et ont cliqué sur ce lien malveillant perdront leur argent. N'importe qui peut facilement voler de l'argent sans que l'utilisateur d'un compte PayPal ne le sache.

Pour la raison ci-dessus, les navigateurs bloquent toutes les demandes d'origine croisée.

Qu'est-ce que le CORS (partage des ressources d'origine croisée) ?

CORS est un mécanisme de sécurité basé sur l'en-tête utilisé par le serveur pour indiquer au navigateur d'envoyer une requête cross-origin à partir de domaines de confiance.
Le serveur activé avec les en-têtes CORS utilisé pour éviter les requêtes cross-origin bloquées par les navigateurs.

Comment fonctionne la CORS ?

Comme le serveur a déjà défini son domaine de confiance dans sa configuration CORS. Lorsque nous envoyons une requête au serveur, la réponse indiquera au navigateur que le domaine demandé est approuvé ou non dans son en-tête.

Il existe deux types de requêtes CORS :

  • Demande simple
  • Demande de contrôle en amont

Demande simple :

Le flux de requêtes simple CORS indique qu'il envoie une requête d'origine croisée mais lorsqu'il a reçu une réponse. Il vérifie les en-têtes.

  • Le navigateur envoie la demande à un domaine cross-origin avec origin (https://app.geekflare.com).
  • Le serveur renvoie la réponse correspondante avec les méthodes autorisées et l' origine autorisée.
  • Après avoir reçu la demande, le navigateur vérifiera que la valeur d'en-tête d'origine envoyée ( https://app.geekflare.com ) et la valeur d'accès-contrôle-allow-origin reçue ( https://app.geekflare.com ) sont identiques ou caractère générique (*). Sinon, il lancera une erreur CORS.

Demande de contrôle en amont :

CORS-Preflight Request Image which show the flow of cross-origin request with OPTIONS preflight request before sending actual request for verifying headers.

  • En fonction du paramètre de requête personnalisée de la requête cross-origin comme des méthodes (PUT, DELETE) ou des en-têtes personnalisés ou un type de contenu différent, etc. Le navigateur décidera d'envoyer une requête OPTIONS de contrôle en amont pour vérifier si la requête réelle est sûre à envoyer ou non.
  • Après avoir reçu la réponse (code d'état : 204, ce qui signifie pas de contenu), le navigateur vérifiera les paramètres d' autorisation d'accès pour la demande réelle. Si les paramètres de la requête sont autorisés par le serveur. La demande d'origine croisée réelle envoyée et reçue

Si access-control-allow-origin: * , alors la réponse est autorisée pour toutes les origines. Mais ce n'est pas sûr à moins que vous en ayez besoin.

Comment activer CORS ?

Pour activer CORS pour n'importe quel domaine, activez les en-têtes CORS pour autoriser l'origine, les méthodes, les en-têtes personnalisés, les informations d'identification, etc.

Le navigateur lit l'en-tête CORS du serveur et autorise les demandes réelles du client uniquement après avoir vérifié les paramètres de la demande.

  • Access-Control-Allow-Origin : pour spécifier des domaines exacts (https://app.geekflate.com, https://lab.geekflare.com) ou un caractère générique (*)
  • Access-Control-Allow-Methods : pour autoriser les méthodes HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) dont nous seuls avons besoin.
  • Access-Control-Allow-Headers : pour autoriser uniquement des en-têtes spécifiques (autorisation, jeton csrf)
  • Access-Control-Allow-Credentials : valeur booléenne utilisée pour autoriser les informations d'identification d'origine croisée (cookies, en-tête d'autorisation).
  • Access-Control-Max-Age : indique au navigateur de mettre en cache la réponse de contrôle en amont pendant un certain temps.
  • Access-Control-Expose-Headers : spécifiez les en-têtes accessibles par le script côté client.

Pour activer CORS dans Apache et le serveur Web Nginx, suivez ce didacticiel.

Activation de CORS dans ExpressJS

Prenons un exemple d'application ExpressJS sans CORS :

 const express = require('express'); const app = express() app.get('/users', function (req, res, next) { res.json({msg: 'user get'}) }); app.post('/users', function (req, res, next) { res.json({msg: 'user create'}) }); app.put('/users', function (req, res, next) { res.json({msg: 'User update'}) }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') })

Dans l'exemple ci-dessus, nous avons activé le point de terminaison de l'API des utilisateurs pour les méthodes POST, PUT, GET mais pas la méthode DELETE.

Pour activer facilement CORS dans l'application ExpressJS, vous pouvez installer les cors

 npm install cors

Contrôle d'accès-Autoriser-Origine

Activer CORS pour tous les domaines

 app.use(cors({ origin: '*' }));

Activer CORS pour un seul domaine

 app.use(cors({ origin: 'https://app.geekflare.com' }));

Si vous souhaitez autoriser CORS pour l'origine https://app.geekflare.com et https://lab.geekflare.com

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));

Méthodes de contrôle d'accès

Pour activer CORS pour toutes les méthodes, omettez cette option dans le module CORS d'ExpressJS. Mais pour activer des méthodes spécifiques (GET, POST, PUT).

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));

Contrôle d'accès-Autoriser-En-têtes

Utilisé pour autoriser l'envoi d'en-têtes autres que les valeurs par défaut avec les demandes réelles.

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'] }));

Contrôle d'accès-Autoriser-Identifiants

Omettez ceci si vous ne voulez pas dire au navigateur d'autoriser les informations d'identification sur demande, même si withCredentials est défini sur true .

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true }));

Accès-Contrôle-Max-Age

Inciter le navigateur à mettre en cache les informations de réponse de contrôle en amont dans le cache pendant une seconde spécifiée. Omettez ceci si vous ne voulez pas mettre en cache la réponse.

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600 }));

La réponse de contrôle en amont en cache sera disponible pendant 10 minutes dans le navigateur.

Contrôle d'accès-Exposer-En-têtes

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['Content-Range', 'X-Content-Range'] }));

Si nous mettons le caractère générique (*) dans exposésHeaders, il n'exposera pas l'en-tête d'autorisation. Nous devons donc exposer explicitement comme ci-dessous

 app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization', ] }));

Ce qui précède exposera également tous les en-têtes et l'en-tête d'autorisation.

Qu'est-ce qu'un cookie HTTP ?

Un cookie est une petite donnée que le serveur enverra au navigateur client. Sur les demandes ultérieures, le navigateur enverra tous les cookies liés au même domaine à chaque demande.

Cookie a son attribut, qui peut être défini pour faire fonctionner un cookie différemment selon nos besoins.

  • Nom Nom du cookie.
  • value : données du cookie relatives au nom du cookie
  • Domaine : les cookies seront envoyés uniquement au domaine défini
  • Chemin d'accès : cookies envoyés uniquement après le chemin du préfixe d'URL défini. Supposons que nous ayons défini notre chemin de cookie comme path='admin/'. Cookies non envoyés pour l'URL https://geekflare.com/expire/ mais envoyés avec le préfixe d'URL https://geekflare.com/admin/
  • Max-Age/Expires (nombre en seconde) : quand le cookie doit-il expirer. Une durée de vie du cookie rend le cookie invalide après le délai spécifié.
  • HTTPOnly(Boolean) : le serveur principal peut accéder à ce cookie HTTPOnly mais pas au script côté client lorsqu'il est vrai.
  • Sécurisé (booléen) : les cookies ne sont envoyés sur un domaine SSL/TLS que lorsqu'ils sont vrais.
  • sameSite(string [Strict, Lax, None]) : utilisé pour activer/restreindre les cookies envoyés sur les requêtes intersites. Pour en savoir plus sur les cookies sameSite voir MDN. Il accepte trois options Strict, Lax, None. Valeur sécurisée du cookie définie sur true pour la configuration du cookie sameSite=None.

Pourquoi un cookie HTTPOnly pour les jetons ?

Le stockage du jeton d'accès envoyé depuis le serveur dans un stockage côté client comme le stockage local , la base de données indexée et le cookie (HTTPOnly not set to true) sont plus vulnérables aux attaques XSS. Supposons que l'une de vos pages soit faible face à une attaque XSS. Les attaquants peuvent abuser des jetons d'utilisateur stockés dans le navigateur.

Les cookies HTTPOnly ne sont définis/obtenus que par le serveur/backend, mais pas côté client.

Script côté client limité à l'accès à ce cookie HTTP uniquement. Ainsi, les cookies HTTPOnly ne sont pas vulnérables aux attaques XSS et sont plus sécurisés. Parce qu'il n'est accessible que par le serveur.

Activer le cookie HTTPOnly dans le backend activé par CORS

L'activation du cookie dans CORS nécessite la configuration ci-dessous dans l'application/le serveur.

  • Définissez l'en-tête Access-Control-Allow-Credentials sur true.
  • Access-Control-Allow-Origin et Access-Control-Allow-Headers ne doivent pas être un caractère générique (*).
  • L'attribut cookie sameSite doit être None.
  • Pour activer la valeur de sameSite sur aucun, définissez la valeur sécurisée sur true : Activez le backend avec le certificat SSL/TLS pour qu'il fonctionne dans le nom de domaine.

Voyons un exemple de code qui définit un jeton d'accès dans le cookie HTTPOnly après avoir vérifié les identifiants de connexion.

 const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization' ] })); app.post('/login', function (req, res, next) { res.cookie('access_token', access_token, { expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for cross-request }); res.json({ msg: 'Login Successfully', access_token }); }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') });

Vous pouvez configurer les cookies CORS et HTTPOnly en implémentant les quatre étapes ci-dessus dans votre langue principale et votre serveur Web.

Vous pouvez suivre ce didacticiel pour Apache et Nginx pour activer CORS en suivant les étapes ci-dessus.

withCredentials for Cross-Origin request

Identifiants (Cookie, Autorisation) envoyés avec la demande de même origine par défaut. Pour l'origine croisée, nous devons spécifier les withCredentials à true.

API XMLHttpRequest :

 var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.geekflare.com/user', true); xhr.withCredentials = true; xhr.send(null);

Récupérer l'API :

 fetch('http://api.geekflare.com/user', { credentials: 'include' });

JQuery Ajax :

 $.ajax({ url: 'http://api.geekflare.com/user', xhrFields: { withCredentials: true } });

Axes :

 axios.defaults.withCredentials = true

Conclusion

J'espère que l'article ci-dessus vous aidera à comprendre le fonctionnement de CORS et à activer CORS pour les requêtes cross-origin sur le serveur. Pourquoi le stockage des cookies dans HTTPOnly est sécurisé et comment withCredentials est-il utilisé dans les clients pour les demandes d'origine croisée.