Dans cet article, nous voyons comment activer 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.

Why are Cross-Origin requests blocked?

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

Chaque fois que nous essayons d'envoyer une demande d'origine croisée à 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, 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 croisée à paypal.com à partir d'un script chargé sur un autre domaine malicious.com sans aucune erreur/blocage CORS comme nous envoyons la 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 le 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.

What is CORS(Cross-Origin Resource Sharing)?

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 d'origine croisée à 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 origine (https://app.geekflare.com).
  • Le serveur renvoie la réponse correspondante avec méthodes autorisées et origine autorisée.
  • Après avoir reçu la demande, le navigateur vérifiera la valeur d'en-tête d'origine envoyée (https://app.geekflare.com) et a reçu la valeur access-control-allow-origin (https://app.geekflare.com) sont identiques ou génériques (*). Sinon, il lancera une erreur CORS.

Demande de contrôle en amont :

Image de demande de contrôle en amont CORS qui montre le flux de la demande d'origine croisée avec la demande de contrôle en amont OPTIONS avant d'envoyer la demande réelle de vérification des en-têtes.

  • 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 le contrôle-accès-autoriser paramètres 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

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

How to enable 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.

  • Contrôle d'accès-Autoriser-Origine : Pour spécifier des domaines exacts (https://app.geekflate.com, https://lab.geekflare.com) ou un caractère générique (*)
  • Méthodes d'autorisation d'accès : Pour autoriser les méthodes HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) dont nous seuls avons besoin.
  • En-têtes d'autorisation d'accès : Pour autoriser uniquement des en-têtes spécifiques (autorisation, jeton csrf)
  • Contrôle d'accès-Autoriser-Identifiants : Valeur booléenne utilisée pour autoriser les informations d'identification croisées (cookies, en-tête d'autorisation).
  • Accès-Contrôle-Max-Age : Indique au navigateur de mettre en cache la réponse de contrôle en amont pendant un certain temps.
  • Contrôle d'accès-Exposer-En-têtes : Spécifiez les en-têtes accessibles par le script côté client.

Pour activer CORS dans Apache et le serveur Web Nginx, suivez ceci tutoriel.

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 le 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'
    ]
}));

Contrôle d'accès-Autoriser-Méthodes

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']
}));

En-têtes de contrôle d'accès

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']
}));

Accès-Contrôle-Autoriser-Informations d'identification

Omettez ceci si vous ne voulez pas dire au navigateur d'autoriser les informations d'identification sur demande, même sur avecCredentials est mis à vrai.

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
}));

Contrôle d'accès-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.

En-têtes de contrôle d'accès-exposer

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 joker (*) dans En-têtes exposés, 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.

What is an HTTP cookie?

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.
  • valeur: données du cookie respectives au nom du cookie
  • Domaine:  les cookies seront envoyés uniquement au domaine défini
  • Chemin: cookies envoyés uniquement après le chemin de préfixe 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é.
  • HTTP uniquement (booléen) : Le serveur principal peut accéder à ce cookie HTTPOnly mais pas au script côté client lorsqu'il est vrai.
  • Sécurisé (booléen) : Cookies envoyés sur un domaine SSL/TLS uniquement lorsqu'ils sont vrais.
  • mêmeSite(chaîne [Strict, Lax, Aucun]): Utilisé pour activer/restreindre les cookies envoyés lors de demandes intersites. Pour en savoir plus sur les cookies sameSite lire 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.

Why HTTPOnly cookie for tokens?

Stockage du jeton d'accès envoyé depuis le serveur dans un stockage côté client comme stockage local, BD indexée, et gâteau (HTTPOnly pas défini sur true) sont plus vulnérables à Attaque 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.

Enable HTTPOnly cookie in CORS enabled backend

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.

Tu peux suivre ça tutoriel 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);

Fetch API

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

JQuery Ajax

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

Axios

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 d'origine croisée 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.