Jak povolit CORS pomocí HTTPOnly Cookie k zabezpečenému tokenu?

Obsah

Tento článek se zaměří na to, jak aktivovat CORS (sdílení zdrojů mezi různými původy) s využitím HTTPOnly cookies pro zvýšení bezpečnosti přístupových tokenů.

V současnosti jsou backendové servery a frontendové aplikace často nasazovány na odlišných doménách. Aby byla umožněna komunikace mezi klienty a serverem v prohlížečích, je nezbytné, aby server podporoval CORS.

Pro zajištění lepší škálovatelnosti servery stále častěji implementují bezstavovou autentizaci. V tomto modelu jsou tokeny ukládány a spravovány na straně klienta, a nikoli na serveru, jako v případě klasických relací. Z bezpečnostních důvodů je nejvhodnější ukládat tokeny do HTTPOnly cookies.

Proč jsou blokovány požadavky mezi různými původy?

Představme si, že naše frontendová aplikace běží na adrese https://app.etechblog.cz.com. Skript, který je načten z této adresy, může vyžadovat zdroje pouze ze stejného původu.

Pokud se pokusíme odeslat požadavek z jiného původu, například na https://api.etechblog.cz.com, na jiný port https://app.etechblog.cz.com:3000, nebo s odlišným schématem http://app.etechblog.cz.com, prohlížeč tento požadavek zablokuje.

Je zajímavé, že stejný požadavek, který je blokován prohlížečem, lze bez problémů odeslat ze serveru pomocí nástroje curl nebo z aplikací jako Postman. Je to z důvodu bezpečnosti, konkrétně ochrany uživatelů před útoky typu CSRF (Cross-Site Request Forgery).

Uvažujme následující situaci: Uživatel je přihlášen ke svému PayPal účtu v prohlížeči. Pokud bychom mohli odeslat požadavek z jiné domény, například malicious.com, na paypal.com bez jakéhokoli blokování CORS, tak jako při požadavku ze stejného původu.

Útočníci by mohli jednoduše vytvořit škodlivou stránku, například https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account, a zkrátit její URL adresu. Uživatel, který klikne na tento odkaz, by nevědomky spustil skript na doméně malicious.com, který by odeslal požadavek do PayPal s cílem převést peníze na účet útočníka. Tímto způsobem by kdokoliv mohl snadno okrást peníze uživatelů PayPalu bez jejich vědomí.

Z výše uvedených důvodů prohlížeče blokují všechny požadavky pocházející z odlišného původu.

Co je to CORS (Cross-Origin Resource Sharing)?

CORS je bezpečnostní mechanismus založený na HTTP hlavičkách, který server používá k informování prohlížeče o tom, ze kterých domén může přijímat požadavky. Server, který má správně nastavené CORS hlavičky, zabraňuje prohlížeči v blokování požadavků z různých původů.

Jak CORS funguje?

Server si ve své konfiguraci CORS definuje seznam důvěryhodných domén. Když odešleme požadavek na server, odpověď obsahuje informace pro prohlížeč o tom, zda je doména, ze které byl požadavek odeslán, důvěryhodná.

Existují dva typy CORS požadavků:

  • Jednoduchý požadavek
  • Předletový požadavek

Jednoduchý požadavek:

  • Prohlížeč pošle požadavek na doménu odlišného původu, přičemž v hlavičce uvede svůj původ (např. https://app.etechblog.cz.com).
  • Server odešle odpověď, která obsahuje informace o povolených metodách a povoleném původu.
  • Po obdržení odpovědi prohlížeč zkontroluje, zda je hodnota hlavičky odeslaného původu (https://app.etechblog.cz.com) stejná jako hodnota v hlavičce Access-Control-Allow-Origin (např. https://app.etechblog.cz.com) nebo zda je tam uvedena divoká karta (*). V opačném případě je vyvolána CORS chyba.

  • Předletový požadavek:
  • V závislosti na parametrech požadavku, jako jsou metody (PUT, DELETE), vlastní hlavičky nebo jiný typ obsahu, prohlížeč může rozhodnout odeslat předběžný požadavek typu OPTIONS, aby zjistil, zda je bezpečné odeslat skutečný požadavek.

Po obdržení odpovědi (stavový kód 204, což znamená žádný obsah), prohlížeč ověří parametry Access-Control-Allow pro daný požadavek. Pokud server povolí parametry požadavku, je odeslán skutečný požadavek z odlišného původu.

Pokud je hodnota hlavičky access-control-allow-origin nastavena na *, je odpověď povolena pro všechny zdroje. To ale nemusí být vždy bezpečné a mělo by se používat jen v odůvodněných případech.

Jak povolit CORS?

Chcete-li povolit CORS pro určitou doménu, musíte nastavit hlavičky CORS, které definují povolené původy, metody, vlastní hlavičky a přihlašovací údaje atd.

  • Prohlížeč načte hlavičky CORS ze serveru a povolí aktuální požadavky od klienta, pokud jsou ověřeny parametry.
  • Access-Control-Allow-Origin: Slouží k definování konkrétních domén (např. https://app.geekflate.com, https://lab.etechblog.cz.com) nebo můžete použít divokou kartu (*).
  • Access-Control-Allow-Methods: Umožňuje povolit HTTP metody (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS), které jsou pro danou aplikaci potřeba.
  • Access-Control-Allow-Headers: Slouží k povolení specifických hlaviček (např. autorizace, csrf-token).
  • Access-Control-Allow-Credentials: Logická hodnota pro povolení přihlašovacích údajů pro požadavky z různých domén (cookies, autorizační hlavičky).

Access-Control-Max-Age: Udává prohlížeči, jak dlouho (v sekundách) má uchovávat informace o předletové kontrole v paměti.

Access-Control-Expose-Headers: Definuje hlavičky, které jsou přístupné ze skriptu na straně klienta.

Návod pro povolení CORS na webových serverech Apache a Nginx naleznete v příslušné dokumentaci.

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

Povolení CORS v ExpressJS

Následuje příklad aplikace ExpressJS bez podpory CORS:

npm install cors

V uvedeném příkladu jsou API koncové body pro uživatele dostupné pro metody POST, PUT, GET, ale ne pro DELETE.

Pro jednoduché povolení CORS v ExpressJS můžete nainstalovat knihovnu cors.

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

Access-Control-Allow-Origin

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

Povolení CORS pro všechny domény

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

Povolení CORS pro jednu doménu

Pokud chcete povolit CORS pro zdroje https://app.etechblog.cz.com a https://lab.etechblog.cz.com

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

Access-Control-Allow-Metody

Pro povolení CORS pro všechny metody stačí vynechat tuto volbu v modulu CORS v ExpressJS. Pokud chcete povolit jen specifické metody (GET, POST, PUT) tak to specifikujte.

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

Access-Control-Allow-headers

Používá se k povolení odesílání nestandardních hlaviček s požadavky.

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

Access-Control-Allow-Credentials

Tuto vlastnost vynechejte, pokud nechcete informovat prohlížeč o povolení přihlašovacích údajů v požadavku i v případě, že je parametr withCredentials nastaven na 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,
    maxAge: 600 
}));

Access-Control-Max-Age

Informuje prohlížeč, jak dlouho (v sekundách) má uchovávat informace o předletové kontrole v paměti. Vynechejte pokud nechcete cacheovat odpověď.

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

Odpověď předletové kontroly je uložena v cache prohlížeče po dobu 10 minut.

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

Access-Control-Expose-Headers

Pokud použijeme zástupný znak (*) v exponovaných hlavičkách, nebude viditelná hlavička Authorization. Proto musíme tuto hlavičku exponovat explicitně, jak je uvedeno níže.

Výše uvedené nastavení zobrazí všechny hlavičky, včetně autorizační hlavičky.

  • Co je HTTP cookie?
  • Cookie je malý kus dat, který server odešle do klientského prohlížeče. Při každém dalším požadavku prohlížeč odešle všechny cookies, které patří ke stejné doméně.
  • Cookie má své atributy, které určují jeho chování.
  • Název: Název cookie.
  • hodnota: Data uložená v cookie.
  • Doména: Cookie se odesílá jen pro definovanou doménu.
  • Cesta: Cookie se odesílá jen pro určenou cestu URL. Pokud definujeme cestu cookie jako ‚admin/‘, cookie se nebude odesílat pro https://etechblog.cz.com/expire/, ale bude odeslán pro URL s předponou https://etechblog.cz.com/admin/.
  • Max-Age/Expires (v sekundách): Určuje dobu platnosti cookie. Po této době bude cookie neplatný.
  • HTTPOnly (Boolean): Pokud je nastaveno na true, cookie je přístupný jen serverem a nikoli skriptem na straně klienta.
  • Secure (Boolean): Pokud je nastaveno na true, cookie se odesílá pouze přes protokol SSL/TLS.
  • sameSite(řetězec): Používá se k povolení nebo omezení odesílání cookies mezi weby. Další informace o sameSite cookies naleznete na MDN.

): Používá se k povolení/omezení souborů cookie zasílaných na požadavky mezi weby. Další podrobnosti o souborech cookie sameSite viz

MDN

Přijímá tři možnosti: Strict, Lax a None. Pokud chcete povolit sameSite=None, je nutné nastavit atribut Secure na true.

Proč HTTPOnly cookies pro tokeny?

Ukládání přístupového tokenu, který je odeslán ze serveru, do úložiště na straně klienta (např. local storage, indexovaná databáze) nebo do cookies (pokud není nastaven atribut HTTPOnly na true) je zranitelné vůči XSS útokům. V případě, že je vaše stránka zranitelná vůči XSS útoku, může útočník zneužít uživatelský token uložený v prohlížeči.

HTTPOnly cookies jsou nastavovány a získávány pouze serverem (backendem), a nikoli na straně klienta.

  • Skripty na straně klienta mají omezený přístup k HTTPOnly cookies, což je chrání před XSS útoky. Protože je přístupný jen ze serveru jsou bezpečnější.
  • Povolení HTTPOnly cookies v backendu s povoleným CORS.
  • Pro povolení cookies v CORS je nutné nastavit následující konfiguraci v aplikaci/serveru:
  • Nastavte hlavičku Access-Control-Allow-Credentials na true.

Access-Control-Allow-Origin a Access-Control-Allow-Headers by neměly používat zástupný znak (*).

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

.

Atribut cookie sameSite by měl být None.

Aby fungovala hodnota sameSite=none, je nutné nastavit atribut Secure na true a server musí mít platný certifikát SSL/TLS a fungovat na HTTPS.

Následuje příklad kódu, který nastaví přístupový token do HTTPOnly cookie po ověření přihlašovacích údajů.

CORS a HTTPOnly cookies můžete nakonfigurovat dle výše uvedených kroků ve vašem backendovém jazyce a na webovém serveru.

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

Návod pro povolení CORS v Apache a Nginx dle výše uvedených kroků naleznete v dokumentaci k těmto serverům.

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

withCredentials pro požadavky mezi různými původy

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

Přihlašovací údaje (cookies, autorizace) jsou standardně odesílány s požadavkem ze stejného původu. Pro požadavky z odlišných původů musíme nastavit withCredentials na true.

axios.defaults.withCredentials = true

XMLHttpRequest API

Fetch API

JQuery Ajax Axios Závěr Doufám, že tento článek vám pomohl pochopit, jak funguje CORS, jak povolit CORS pro požadavky na serveru z různých zdrojů a proč je ukládání cookies do HTTPOnly bezpečné a jak se používá sCredentials v klientech pro požadavky z různých zdrojů.