Dans cet article, nous verrons 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 backend et les clients frontend sont déployés sur des domaines différents. Par conséquent, le serveur doit activer CORS pour permettre aux clients de communiquer avec le serveur sur les navigateurs.
En outre, les serveurs mettent en œuvre l’authentification sans état pour une meilleure évolutivité. Les jetons sont stockés et conservés du côté client, mais pas du côté serveur comme la session. Pour des raisons de sécurité, il est préférable de stocker les jetons dans des cookies HTTPOnly.
Pourquoi les requêtes “Cross-Origin” sont-elles bloquées ?
Supposons que notre application frontale soit déployée à l’adresse https://app.geekflare.com/fr.
Un script chargé sur https://app.geekflare.com/frcan
ne demande que des ressources de même origine.
Lorsque nous essayons d’envoyer une requête cross-origin à un autre domaine https://api.geekflare.com/fr
ou à un autre port https://app.geekflare.com/fr:3000
ou à un autre schéma http://app.geekflare.com/fr,
la requête cross-origin sera bloquée par le navigateur.
Mais pourquoi la même requête bloquée par le navigateur peut-elle être envoyée à partir de n’importe quel serveur dorsal à l’aide d’une requête curl ou à l’aide d’outils tels que postman sans aucun problème CORS ? Il s’agit en fait d’une mesure de sécurité visant à protéger les utilisateurs contre des attaques telles que CSRF (Cross-Site Request Forgery).
Prenons un exemple : supposons qu’un utilisateur se connecte à son propre compte PayPal dans son navigateur. Si nous pouvons envoyer une requête cross-origin à paypal.com
à partir d’un script chargé sur un autre domaine malicious.com
sans erreur/blocage CORS comme nous envoyons une requête same-origin.
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 cacher l’URL réelle. Lorsque l’utilisateur clique sur un lien malveillant, le script chargé dans le domaine malicious.com
envoie une requête cross-origin à PayPal pour transférer le montant de l’utilisateur sur le compte PayPal de l’attaquant. Tous les utilisateurs qui se sont connectés à leur compte PayPal et qui ont cliqué sur ce lien malveillant perdront leur argent. N’importe qui peut facilement voler de l’argent à l’insu de l’utilisateur d’un compte PayPal.
Pour cette raison, les navigateurs bloquent toutes les demandes d’origine croisée.
Qu’est-ce que CORS (Cross-Origin Resource Sharing) ?
CORS est un mécanisme de sécurité basé sur des en-têtes utilisé par le serveur pour indiquer au navigateur d’envoyer une requête cross-origin à partir de domaines de confiance.
L’activation des en-têtes CORS par le serveur permet d’éviter que les navigateurs ne bloquent les requêtes cross-origin.
Comment fonctionne CORS ?
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 indique au navigateur si le domaine demandé est fiable ou non dans son en-tête.
Il existe deux types de requêtes CORS :
- Demande simple
- Demande de contrôle préalable
Demande simple :
- Le navigateur envoie la demande à un domaine à origine croisée avec origine (https://app.geekflare.com/fr).
- 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érifie que la valeur de l’en-tête origin envoyée(https://app.geekflare.com/fr) et la valeur access-control-allow-origin reçue(https://app.geekflare.com/fr) sont identiques ou qu’elles contiennent des caractères génériques (*). Dans le cas contraire, il génère une erreur CORS.
Demande de contrôle en amont :
- En fonction des paramètres personnalisés de la demande inter-origine, tels que les méthodes (PUT, DELETE), les en-têtes personnalisés ou un type de contenu différent, etc. Le navigateur décidera d’envoyer une demande OPTIONS de contrôle préalable pour vérifier si la demande réelle peut être envoyée en toute sécurité ou non.
- Après avoir reçu la réponse (code d’état : 204, ce qui signifie qu’il n’y a pas de contenu), le navigateur vérifie les paramètres de contrôle d’accès et d’autorisation de la demande réelle. Si les paramètres de la demande sont autorisés par le serveur. La requête cross-origin envoyée et reçue
Si access-control-allow-origin :
*la réponse est autorisée pour toutes les origines. Mais ce n’est pas sûr, sauf si vous en avez 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 n’autorise les demandes réelles du client qu’après avoir vérifié les paramètres de la demande.
- Contrôle d’accès – Autoriser l’origine : Pour spécifier des domaines exacts (https://app.geekflate.com, https://lab.geekflare.com/fr) ou des caractères génériques (*)
- Contrôle d’accès – Autoriser les méthodes : Pour autoriser les méthodes HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) dont nous sommes les seuls à avoir besoin.
- Access-Control-Allow-Headers (Contrôle d’accès – Autoriser les en-têtes) : Pour n’autoriser que des en-têtes spécifiques (Authorization, csrf-token)
- Access-Control-Allow-Credentials : Valeur booléenne utilisée pour autoriser les références croisées (cookies, en-tête d’autorisation).
- Access-Control-Max-Age : Indique au navigateur de mettre en cache la réponse du contrôle en amont pendant un certain temps.
- Access-Control-Expose-Headers : Spécifie les en-têtes qui sont accessibles par le script côté client.
Pour activer CORS dans les serveurs web Apache et Nginx, suivez ce 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 API des utilisateurs pour les méthodes POST, PUT, GET mais pas pour la méthode DELETE.
Pour faciliter l’activation de CORS dans l’application ExpressJS, vous pouvez installer le programme cors
npm install cors
Contrôle d’accès – Autoriser l’origine
Activation de CORS pour tous les domaines
app.use(cors({
origin : '*'
})) ;
Activation de CORS pour un seul domaine
app.use(cors({
origin : 'https://app.geekflare.com/fr'
})) ;
Si vous voulez autoriser CORS pour les origines https://app.geekflare.com/fr et https://lab.geekflare.com/fr
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
]
})) ;
Contrôle d’accès – Autoriser les méthodes
Pour activer CORS pour toutes les méthodes, omettez cette option dans le module CORS de l’ExpressJS. Mais pour activer des méthodes spécifiques (GET, POST, PUT).
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
],
methods : ['GET', 'PUT', 'POST']
})) ;
Contrôle d’accès – Autoriser les en-têtes
Utilisé pour autoriser l’envoi d’en-têtes autres que les en-têtes par défaut avec les requêtes réelles.
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
],
methods : ['GET', 'PUT', 'POST'],
allowedHeaders : ['Content-Type', 'Authorization', 'x-csrf-token']
})) ;
Contrôle d’accès – Autoriser les informations d’identification
Oubliez ceci si vous ne voulez pas dire au navigateur d’autoriser les informations d’identification à la demande même si withCredentials est fixé à true.
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
],
methods : ['GET', 'PUT', 'POST'],
allowedHeaders : ['Content-Type', 'Authorization', 'x-csrf-token'],
credentials : true
})) ;
Contrôle d’accès-Max-Age
Pour demander au navigateur de mettre en cache les informations de la réponse preflight dans le cache pendant une seconde spécifiée. Omettez cette option si vous ne souhaitez pas mettre la réponse en cache.
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
],
methods : ['GET', 'PUT', 'POST'],
allowedHeaders : ['Content-Type', 'Authorization', 'x-csrf-token'],
credentials : true,
maxAge : 600
})) ;
La réponse de contrôle en amont mise en cache sera disponible pendant 10 minutes dans le navigateur.
En-têtes d’exposition du contrôle d’accès
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
],
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 exposedHeaders, l’en-tête Authorization ne sera pas exposé. Nous devons donc l’exposer explicitement comme suit
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
],
methods : ['GET', 'PUT', 'POST'],
allowedHeaders : ['Content-Type', 'Authorization', 'x-csrf-token'],
credentials : true,
maxAge : 600,
exposedHeaders : ['*', 'Authorization', ]
})) ;
La méthode ci-dessus exposera tous les en-têtes ainsi que l’en-tête Authorization.
Un cookie est un petit morceau de données que le serveur envoie au navigateur du client. Lors des requêtes ultérieures, le navigateur enverra tous les cookies liés au même domaine à chaque requête.
Le cookie a ses propres attributs, qui peuvent être définis pour faire fonctionner un cookie différemment selon nos besoins.
- Nom : Nom du cookie.
- valeur : données du cookie correspondant au nom du cookie
- Domaine : les cookies ne sont envoyés qu’au domaine défini
- Chemin : les cookies ne sont envoyés qu’après le préfixe URL défini. Supposons que nous ayons défini le chemin de notre cookie comme path=’admin/’. Les cookies ne sont pas envoyés pour l’URL https://geekflare.com/fr/expire/ mais sont envoyés avec le préfixe URL https://geekflare.com/fr/admin/
- Max-Age/Expires (nombre en secondes) : Quand le cookie doit-il expirer. Une durée de vie du cookie rend le cookie invalide après le temps spécifié.
- HTTPOnly(booléen) : Le serveur dorsal peut accéder à ce cookie HTTPOnly, mais pas le script côté client s’il est vrai.
- Secure(Booléen) : Les cookies ne sont envoyés que par l’intermédiaire d’un domaine SSL/TLS lorsque l’option est activée.
- sameSite(string [Strict, Lax, None]) : Utilisé pour activer/restricter les cookies envoyés lors de requêtes intersites. Pour en savoir plus sur les cookies
sameSite
, consultez MDN. Il accepte trois options : Strict, Lax, None. Valeur sécurisée du cookie fixée à true pour la configuration du cookie sameSite=None.
Le stockage du jeton d’accès envoyé par le serveur dans le stockage côté client, comme le stockage local, la base de données indexée et le cookie (HTTPOnly n’est pas défini sur true), est plus vulnérable aux attaques XSS. Supposons que l’une de vos pages soit vulnérable à 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 du côté du client.
Les scripts côté client ne peuvent pas accéder aux cookies HTTPOnly. Les cookies HTTPOnly ne sont donc pas vulnérables aux attaques XSS et sont plus sûrs. Parce qu’ils ne sont accessibles que par le serveur.
L’activation des cookies dans CORS nécessite la configuration suivante dans l’application/serveur.
- Attribuez la valeur true à l’en-tête Access-Control-Allow-Credentials.
- Les en-têtes Access-Control-Allow-Origin et Access-Control-Allow-Headers ne doivent pas être des caractères génériques (*).
- L’attribut cookie sameSite doit être None.
- Pour activer la valeur sameSite à none, définissez la valeur secure à true : Activez le backend avec un certificat SSL/TLS pour travailler 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 informations d’identification.
const express = require('express') ;
const app = express() ;
const cors = require('cors') ;
app.use(cors({
origin : [
'https://app.geekflare.com/fr',
'https://lab.geekflare.com/fr'
],
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, // uniquement pour le backend
sameSite : 'none' // défini à none pour les requêtes croisées
}) ;
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 mettant en œuvre les quatre étapes ci-dessus dans votre langage backend et votre serveur web.
Vous pouvez suivre ce tutoriel pour apache et Nginx pour activer CORS en suivant les étapes ci-dessus.
withCredentials pour les requêtes Cross-Origin
Les informations d’identification (cookie, autorisation) sont envoyées par défaut avec la requête de même origine. Pour les requêtes inter-origines, nous devons spécifier que la valeur de withCredentials est true.
API XMLHttpRequest
var xhr = new XMLHttpRequest() ;
xhr.open('GET', 'http://api.geekflare.com/fr/user', true) ;
xhr.withCredentials = true ;
xhr.send(null) ;
API Fetch
fetch('http://api.geekflare.com/fr/user', {
credentials : 'include'
}) ;
JQuery Ajax
$.ajax({
url : 'http://api.geekflare.com/fr/user',
xhrFields : {
withCredentials : true
}
}) ;
Axios
axios.defaults.withCredentials = true
Conclusion
J’espère que l’article ci-dessus vous aidera à comprendre comment fonctionne CORS et à activer CORS pour les requêtes inter-origines sur le serveur. Pourquoi le stockage des cookies en HTTPOnly est sécurisé et comment withCredentials est utilisé dans les clients pour les requêtes inter-origines.