Jak włączyć CORS z HTTPOnly Cookie, aby zabezpieczyć token?

W tym artykule zobaczymy, jak włączyć CORS (Cross-Origin Resource Sharing) z plikiem cookie HTTPOnly, aby zabezpieczyć nasze tokeny dostępu.

Obecnie serwery backendowe i klienty frontendowe są wdrażane w różnych domenach. Dlatego serwer musi włączyć CORS, aby umożliwić klientom komunikację z serwerem w przeglądarkach.

Ponadto serwery wdrażają uwierzytelnianie bezstanowe, aby zapewnić lepszą skalowalność. Tokeny są przechowywane i utrzymywane po stronie klienta, ale nie po stronie serwera, jak sesja. Ze względów bezpieczeństwa lepiej przechowywać tokeny w plikach cookie HTTPOnly.

Spis treści:

Dlaczego żądania Cross-Origin są blokowane?

Załóżmy, że nasza aplikacja frontendowa została wdrożona pod adresem https://app.newsblog.pl.com. Skrypt załadowany w https://app.newsblog.pl.com może żądać tylko zasobów tego samego pochodzenia.

Za każdym razem, gdy próbujemy wysłać żądanie cross-origin do innej domeny https://api.newsblog.pl.com lub innego portu https://app.newsblog.pl.com:3000 lub innego schematu http://app.newsblog.pl.com, żądanie cross-origin zostanie zablokowane przez przeglądarkę.

Ale dlaczego to samo żądanie zablokowane przez przeglądarkę jest wysyłane z dowolnego serwera zaplecza za pomocą żądania curl lub wysyłane za pomocą narzędzi takich jak listonosz bez żadnego problemu z CORS. W rzeczywistości chodzi o bezpieczeństwo, aby chronić użytkowników przed atakami, takimi jak CSRF (Cross-Site Request Forgery).

Weźmy przykład, załóżmy, że jakikolwiek użytkownik zalogował się na swoje konto PayPal w swojej przeglądarce. Jeśli możemy wysłać żądanie cross-origin do paypal.com ze skryptu załadowanego w innej domenie złośliwej.com bez żadnego błędu/blokowania CORS, tak jak wysyłamy żądanie tego samego pochodzenia.

Atakujący mogą łatwo wysłać swoją złośliwą stronę https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account, konwertując ją na krótki adres URL, aby ukryć rzeczywisty adres URL. Gdy użytkownik kliknie w złośliwy link, skrypt załadowany w domenie złośliwy.com wyśle ​​żądanie cross-origin do PayPal, aby przelać kwotę użytkownika na konto PayPal osoby atakującej. Wszyscy użytkownicy, którzy zalogowali się na swoje konto PayPal i kliknęli ten złośliwy link, stracą swoje pieniądze. Każdy może łatwo ukraść pieniądze bez wiedzy użytkownika konta PayPal.

Z powyższego powodu przeglądarki blokują wszystkie żądania cross-origin.

Co to jest CORS (udostępnianie zasobów między źródłami)?

CORS to oparty na nagłówkach mechanizm bezpieczeństwa używany przez serwer do informowania przeglądarki o wysłaniu żądania cross-origin z zaufanych domen.
Serwer z włączonymi nagłówkami CORS używanymi w celu uniknięcia żądań między źródłami blokowanych przez przeglądarki.

Jak działa CORS?

Ponieważ serwer już zdefiniował swoją zaufaną domenę w konfiguracji CORS. Kiedy wysyłamy żądanie do serwera, odpowiedź poinformuje przeglądarkę, że żądana domena jest zaufana, czy nie w jej nagłówku.

Istnieją dwa rodzaje żądań CORS:

  • Prosta prośba
  • Zapytanie przed lotem

Prosta prośba:

  • Przeglądarka wysyła żądanie do domeny cross-origin z pochodzeniem (https://app.newsblog.pl.com).
  • Serwer odsyła odpowiednią odpowiedź z dozwolonymi metodami i dozwolonym pochodzeniem.
  • Po otrzymaniu żądania przeglądarka sprawdzi, czy przesłana wartość nagłówka pochodzenia (https://app.newsblog.pl.com) i odebrana wartość access-control-allow-origin (https://app.newsblog.pl.com) są takie same lub dzika karta

. W przeciwnym razie zgłosi błąd CORS.

  • Prośba przed lotem:
  • W zależności od niestandardowego parametru żądania z żądania cross-origin, takiego jak metody (PUT, DELETE), niestandardowe nagłówki lub inny typ zawartości itp. Przeglądarka zdecyduje się wysłać żądanie preflight OPTIONS, aby sprawdzić, czy rzeczywiste żądanie jest bezpieczne do wysłania albo nie.

Po otrzymaniu odpowiedzi (kod stanu: 204, co oznacza brak treści), przeglądarka sprawdzi parametry dostępu-kontroli-zezwól dla rzeczywistego żądania. Czy parametry żądania są dozwolone przez serwer. Rzeczywiste wysłane i odebrane żądanie cross-origin

Jeśli access-control-allow-origin: *, to odpowiedź jest dozwolona dla wszystkich źródeł. Ale to nie jest bezpieczne, chyba że tego potrzebujesz.

Jak włączyć CORS?

Aby włączyć CORS dla dowolnej domeny, włącz nagłówki CORS, aby zezwolić na pochodzenie, metody, niestandardowe nagłówki, poświadczenia itp.

  • Przeglądarka odczytuje nagłówek CORS z serwera i dopuszcza rzeczywiste żądania od klienta dopiero po zweryfikowaniu parametrów żądania.
  • Access-Control-Allow-Origin: Aby określić dokładne domeny (https://app.geekflate.com, https://lab.newsblog.pl.com) lub symbol wieloznaczny
  • Access-Control-Allow-Methods: Aby zezwolić na metody HTTP (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS), których tylko potrzebujemy.
  • Access-Control-Allow-Headers: Aby zezwolić tylko na określone nagłówki (autoryzacja, csrf-token)
  • Access-Control-Allow-Credentials: wartość logiczna używana do zezwalania na poświadczenia krzyżowe (pliki cookie, nagłówek autoryzacji).

Access-Control-Max-Age: Informuje przeglądarkę, aby przez pewien czas buforowała odpowiedź preflight.

Access-Control-Expose-Headers: Określ nagłówki, które są dostępne przez skrypt po stronie klienta.

Aby włączyć mechanizm CORS w serwerach Apache i Nginx, postępuj zgodnie z tym samouczkiem.

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

Włączanie CORS w ExpressJS

Weźmy przykładową aplikację ExpressJS bez CORS:

npm install cors

W powyższym przykładzie włączyliśmy dla użytkowników punkt końcowy API dla metod POST, PUT, GET, ale nie dla metody DELETE.

Aby łatwo włączyć CORS w aplikacji ExpressJS, możesz zainstalować cors

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

Kontrola dostępu-Zezwól-Pochodzenie

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

Włączanie CORS dla wszystkich domen

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

Włączanie CORS dla jednej domeny

Jeśli chcesz zezwolić na CORS dla pochodzenia https://app.newsblog.pl.com i https://lab.newsblog.pl.com

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

Metody kontroli dostępu

Aby włączyć CORS dla wszystkich metod, pomiń tę opcję w module CORS w ExpressJS. Ale za włączenie określonych metod (GET, POST, PUT).

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

Kontrola dostępu-Zezwól-Nagłówki

Służy do zezwalania na wysyłanie nagłówków innych niż domyślne z rzeczywistymi żądaniami.

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

Dostęp-Kontrola-Zezwalaj-Poświadczenia

Pomiń to, jeśli nie chcesz, aby przeglądarka zezwalała na poświadczenia na żądanie, nawet jeśli poświadczenia są ustawione 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 
}));

Kontrola dostępu-maksymalny wiek

Poinformować przeglądarkę o buforowaniu informacji o odpowiedziach wstępnych w pamięci podręcznej przez określoną sekundę. Pomiń to, jeśli nie chcesz buforować odpowiedzi.

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

Buforowana odpowiedź wstępna będzie dostępna w przeglądarce przez 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', ]
}));

Kontrola dostępu-Expose-Nagłówki

Jeśli umieścimy symbol wieloznaczny

w narażonychHeaders nie ujawni nagłówka Authorization. Czyli musimy wyeksponować wyraźnie jak poniżej

Powyższe ujawni również wszystkie nagłówki i nagłówek autoryzacji.

  • Co to jest plik cookie HTTP?
  • Plik cookie to mały fragment danych, który serwer wyśle ​​do przeglądarki klienta. W przypadku późniejszych żądań przeglądarka przy każdym żądaniu wyśle ​​wszystkie pliki cookie związane z tą samą domeną.
  • Ciasteczko ma swój atrybut, który można zdefiniować, aby ciasteczko działało inaczej, niż tego potrzebujemy.
  • Nazwa Nazwa pliku cookie.
  • wartość: dane pliku cookie odpowiadające nazwie pliku cookie
  • Domena: ciasteczka będą wysyłane tylko do zdefiniowanej domeny
  • Ścieżka: pliki cookie wysyłane tylko po zdefiniowanej ścieżce prefiksu adresu URL. Załóżmy, że zdefiniowaliśmy ścieżkę do pliku cookie, taką jak path=’admin/’. Pliki cookie nie są wysyłane dla adresu URL https://newsblog.pl.com/expire/, ale wysyłane z prefiksem adresu URL https://newsblog.pl.com/admin/
  • Max-Age/Expires (liczba w sekundach): Kiedy plik cookie powinien wygasnąć. Okres istnienia pliku cookie powoduje, że plik cookie jest nieważny po określonym czasie. [Strict, Lax, None]HTTPOnly(Boolean): serwer zaplecza może uzyskać dostęp do tego pliku cookie HTTPOnly, ale nie do skryptu po stronie klienta, gdy jest prawdziwy. Secure (Boolean): Pliki cookie wysyłane przez domenę SSL/TLS tylko wtedy, gdy są prawdziwe.ta samaWitryna(ciąg

): Służy do włączania/ograniczania plików cookie przesyłanych w ramach żądań między witrynami. Aby dowiedzieć się więcej o plikach cookie, zobacz sameSite

MDN

. Akceptuje trzy opcje Ścisłe, Lax, Brak. Bezpieczna wartość pliku cookie ustawiona na true dla konfiguracji pliku cookie sameSite=Brak.

Dlaczego plik cookie HTTPOnly dla tokenów?

Przechowywanie tokena dostępu wysłanego z serwera w pamięci po stronie klienta, takiej jak pamięć lokalna, indeksowana baza danych i plik cookie (HTTPOnly nieustawione na true) są bardziej podatne na atak XSS. Załóżmy, że któraś z twoich stron jest podatna na atak XSS. Atakujący mogą nadużywać tokenów użytkownika przechowywanych w przeglądarce.

Ciasteczka HTTPOnly są ustawiane/pobierane tylko przez serwer/zaplecze, ale nie po stronie klienta.

  • Skrypt po stronie klienta ograniczony do dostępu do tego pliku cookie HTTPonly. Tak więc pliki cookie HTTPOnly nie są podatne na ataki XSS i są bezpieczniejsze. Ponieważ jest dostępny tylko przez serwer.
  • Włącz plik cookie HTTPOnly w backendzie obsługującym CORS
  • Włączenie Cookie w CORS wymaga poniższej konfiguracji w aplikacji/serwerze.
  • Ustaw nagłówek Access-Control-Allow-Credentials na wartość true.

Nagłówki Access-Control-Allow-Origin i Access-Control-Allow-Headers nie powinny być symbolami wieloznacznymi

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

.

Atrybut sameSite pliku cookie powinien mieć wartość Brak.

Aby włączyć wartość sameSite na none, ustaw wartość bezpieczną na true: Włącz backend z certyfikatem SSL/TLS do pracy w nazwie domeny.

Zobaczmy przykładowy kod, który ustawia token dostępu w pliku cookie HTTPOnly po sprawdzeniu poświadczeń logowania.

Pliki cookie CORS i HTTPOnly można skonfigurować, wykonując powyższe cztery kroki w języku zaplecza i na serwerze internetowym.

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

Możesz skorzystać z tego samouczka dla Apache i Nginx, aby włączyć CORS, wykonując powyższe kroki.

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

withCredentials for Cross-Origin request

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

Poświadczenia (plik cookie, autoryzacja) wysyłane domyślnie z żądaniem tego samego pochodzenia. W przypadku cross-origin musimy określić withCredentials na true.

axios.defaults.withCredentials = true

Interfejs API XMLHttpRequest

Pobierz interfejs API

JQuery AjaxAksjosWniosek Mam nadzieję, że powyższy artykuł pomoże Ci zrozumieć, jak działa CORS i włączyć ten mechanizm dla żądań między źródłami na serwerze. Dlaczego przechowywanie plików cookie w HTTPOnly jest bezpieczne i jak z poświadczeniami używanymi w klientach dla żądań między źródłami.