Sikre tilgangstokener: CORS & HTTPOnly-informasjonskapsler

Denne artikkelen undersøker hvordan man aktiverer CORS (Cross-Origin Resource Sharing) sammen med HTTPOnly-cookies for å beskytte tilgangstokenene våre.

I dagens webutvikling er det vanlig at backend-servere og frontend-klienter befinner seg på forskjellige domener. Som følge av dette er det nødvendig for serveren å aktivere CORS for å gi frontend-applikasjoner tillatelse til å kommunisere med serveren via nettlesere.

Servere benytter seg ofte av statsløs autentisering for å oppnå bedre skalerbarhet. Token lagres og vedlikeholdes på klientsiden, ikke på serversiden som en sesjon. Av sikkerhetshensyn er det en god praksis å oppbevare tokens i HTTPOnly-cookies.

Hvorfor blokkeres forespørsler på tvers av opprinnelse?

La oss anta at frontend-applikasjonen vår er distribuert på https://app.tipsbilk.net.com. Et skript som lastes inn på https://app.tipsbilk.net.com kan kun hente ressurser fra samme opprinnelse.

Hver gang vi forsøker å sende en forespørsel på tvers av opprinnelse til et annet domene, for eksempel https://api.tipsbilk.net.com, eller en annen port, som https://app.tipsbilk.net.com:3000, eller et annet skjema, som http://app.tipsbilk.net.com, vil nettleseren blokkere forespørselen.

Det er verdt å merke seg at den samme forespørselen som blokkeres av nettleseren, kan sendes fra en backend-server ved hjelp av en «curl»-forespørsel eller med verktøy som Postman uten å støte på CORS-problemer. Dette er en sikkerhetsmekanisme designet for å beskytte brukere mot angrep som CSRF (Cross-Site Request Forgery).

La oss illustrere med et eksempel: Tenk deg at en bruker er logget inn på sin PayPal-konto i nettleseren. Hvis vi var i stand til å sende en forespørsel på tvers av opprinnelse til paypal.com fra et skript som lastes på et annet domene, f.eks. malicious.com, uten CORS-feil eller blokkering, ville det være som å sende en forespørsel med samme opprinnelse.

Angripere kunne enkelt lage en ondsinnet side, f.eks. https://malicious.com/overfor-penger-til-angriper-konto-fra-bruker-paypal-konto, og konvertere den til en kort URL for å skjule den faktiske adressen. Når brukeren klikker på denne ondsinnede lenken, vil skriptet som lastes inn på malicious.com sende en forespørsel på tvers av opprinnelse til PayPal om å overføre penger fra brukerens konto til angriperens PayPal-konto. Alle brukere som er logget inn på PayPal og klikker på lenken ville dermed miste pengene sine. Dette kan gjøres uten at brukeren har kunnskap om det.

På grunn av denne risikoen blokkerer nettlesere som standard alle forespørsler på tvers av opprinnelse.

Hva er CORS (Cross-Origin Resource Sharing)?

CORS er en sikkerhetsmekanisme som benytter overskrifter. Serveren bruker disse til å instruere nettleseren om å tillate forespørsler på tvers av opprinnelse fra klarerte domener. Serveren aktiveres med CORS-overskrifter som hindrer nettleseren i å blokkere forespørsler på tvers av opprinnelse.

Hvordan fungerer CORS?

Siden serveren har definert klarerte domener i sin CORS-konfigurasjon, vil serverens svar fortelle nettleseren om domenet som forespørres er klarert eller ikke gjennom overskriftene.

Det finnes to hovedtyper av CORS-forespørsler:

  • Enkel forespørsel
  • Preflight-forespørsel

Enkel forespørsel:

  • Nettleseren sender forespørselen til et domene på tvers av opprinnelse med en opprinnelse (for eksempel https://app.tipsbilk.net.com).
  • Serveren svarer med de tillatte metodene og den tillatte opprinnelsen.
  • Når nettleseren mottar svaret, sjekker den at verdien i «origin»-overskriften (f.eks. https://app.tipsbilk.net.com) og verdien i «access-control-allow-origin»-overskriften (f.eks. https://app.tipsbilk.net.com) er like eller en jokertegn. Hvis ikke, genereres en CORS-feil.

  • Preflight-forespørsel:
  • Avhengig av de tilpassede parameterne i forespørselen på tvers av opprinnelse, som for eksempel metoder (PUT, DELETE) eller egendefinerte overskrifter eller innholdstype, vil nettleseren sende en «OPTIONS»-forespørsel som en forhåndskontroll. Dette gjøres for å bekrefte om den faktiske forespørselen er trygg å sende.

Etter å ha mottatt svaret (statuskode 204, som betyr «No Content»), vil nettleseren se etter parameterne for «access-control-allow» i den faktiske forespørselen. Hvis forespørselsparameterne er godkjent av serveren, sendes den faktiske forespørselen og tas i mot.

Dersom «access-control-allow-origin» er satt til *, tillates alle opprinnelser, men dette er generelt sett ikke sikkert med mindre det er spesielt nødvendig.

Hvordan aktivere CORS?

For å aktivere CORS for et domene, må CORS-overskrifter aktiveres for å tillate opprinnelse, metoder, egendefinerte overskrifter, legitimasjon og lignende.

  • Nettleseren leser CORS-overskriftene fra serveren og tillater forespørsler fra klienten bare etter å ha bekreftet forespørselsparameterne.
  • Access-Control-Allow-Origin: Spesifiser eksakte domener (f.eks. https://app.geekflate.com, https://lab.tipsbilk.net.com) eller bruk en jokertegn (*).
  • Access-Control-Allow-Methods: Tillat HTTP-metodene (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) som er nødvendige.
  • Access-Control-Allow-Headers: Tillat kun spesifikke overskrifter (for eksempel «authorization» og «csrf-token»).
  • Access-Control-Allow-Credentials: En boolsk verdi som brukes for å tillate legitimasjon på tvers av opprinnelser (cookies, autorisasjons-overskrifter).

Access-Control-Max-Age: Instruerer nettleseren om å bufre preflight-svaret i en spesifisert tidsperiode.

Access-Control-Expose-Headers: Spesifiser overskriftene som er tilgjengelige for skript på klientsiden.

Følg denne veiledningen for å aktivere CORS i Apache og Nginx webservere.

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

Aktiverer CORS i ExpressJS

Her er et eksempel på en ExpressJS-app uten CORS:

npm install cors

I eksemplet over har vi aktivert API-endepunktet for brukere for POST-, PUT- og GET-metodene, men ikke for DELETE-metoden.

For å enkelt aktivere CORS i en ExpressJS-app, kan du installere «cors»:

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

Access-Control-Allow-Origin

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

Aktivere CORS for alle domener

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

Aktivering av CORS for et enkelt domene

Hvis du ønsker å tillate CORS for opprinnelsen https://app.tipsbilk.net.com og https://lab.tipsbilk.net.com

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

Tilgangskontroll-Tillat-metoder

For å aktivere CORS for alle metoder, utelat dette alternativet i CORS-modulen i ExpressJS. For å aktivere spesifikke metoder (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']
}));

Access-Control-Allow-Headers

Brukes for å tillate at andre overskrifter enn standardoverskriftene sendes med forespørslene.

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-Tillat-legitimasjon

Utelat dette hvis du ikke ønsker å instruere nettleseren om å tillate legitimasjon i forespørsler, selv om withCredentials er satt til «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

For å oppmuntre nettleseren til å bufre forhåndskontrollinformasjonen i en spesifisert tidsperiode. Utelat dette hvis du ikke ønsker å bufre svaret.

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

Det bufrede forhåndskontrollsvaret vil være tilgjengelig i 10 minutter i nettleseren.

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

Hvis vi setter jokertegnet (*)

i «exposedHeaders» vil ikke autorisasjonsoverskriften vises. Derfor må vi avsløre den eksplisitt som vist under:

Ovennevnte vil avsløre alle overskrifter og autorisasjonsoverskrifter.

  • Hva er en HTTP-cookie?
  • En cookie er en liten databit som serveren sender til klientnettleseren. Ved senere forespørsler vil nettleseren sende alle cookies som er relatert til det samme domenet med hver forespørsel.
  • Cookies har attributter som kan defineres for å få en cookie til å fungere annerledes avhengig av behov.
  • Navn: Navnet på cookien.
  • Verdi: Dataene for cookien som er relatert til cookiens navn.
  • Domene: Cookies sendes kun til det definerte domenet.
  • Bane: Cookies sendes kun etter den definerte URL-prefiksbanen. Hvis vi for eksempel har definert cookie-banen som «admin/», vil cookies ikke sendes for URL-en https://tipsbilk.net.com/expire/, men vil sendes med URL-prefiks https://tipsbilk.net.com/admin/.
  • Maks-alder/Utløper (tall i sekunder): Når cookien skal utløpe. En levetid for en cookie gjør den ugyldig etter angitt tid. [Strict, Lax, None]HTTPOnly(Boolean): Backend-serveren kan få tilgang til HTTPOnly-cookien, men ikke skript på klientsiden når den er satt til «true». Sikker (boolsk): Cookies sendes kun over et SSL/TLS-domene når den er satt til «true».sameSite(streng

): Brukes til å aktivere/begrense cookies som sendes over forespørsler på tvers av nettsteder. Se MDN for mer informasjon om «sameSite».

Den aksepterer tre alternativer: «Strict», «Lax» og «None». Sikker verdi for cookies settes til «true» for cookie-konfigurasjonen sameSite=None.

Hvorfor HTTPOnly-cookies for tokens?

Lagring av tilgangstoken som sendes fra serveren i lagring på klientsiden, som lokal lagring, indeksert DB og cookie (HTTTPonly ikke satt til «true») er mer sårbart for XSS-angrep. Hvis en av sidene dine er sårbar for et XSS-angrep, kan angripere misbruke brukertokens som er lagret i nettleseren.

HTTPOnly-cookies settes/hentes kun av serveren/backend, men ikke på klientsiden.

  • Skript på klientsiden er begrenset fra å få tilgang til HTTPOnly-cookies. Derfor er HTTPOnly-cookies ikke sårbare for XSS-angrep og er sikrere, da de kun er tilgjengelige for serveren.
  • Aktiver HTTPOnly-cookie i en CORS-aktivert backend
  • Aktivering av cookie i CORS krever følgende konfigurasjon i applikasjonen/serveren.
  • Sett overskriften «Access-Control-Allow-Credentials» til «true».

«Access-Control-Allow-Origin» og «Access-Control-Allow-Headers» skal ikke være satt til jokertegn (*).

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

.

Cookie-attributtet «sameSite» skal være «None».

For å aktivere «sameSite»-verdien til «None», må «secure»-verdien settes til «true»: Aktiver backend med SSL/TLS-sertifikat for å fungere i domenenavnet.

La oss se på et eksempel hvor et tilgangstoken plasseres i en HTTPOnly-cookie etter at påloggingsinformasjonen er bekreftet.

Du kan konfigurere CORS og HTTPOnly-cookies ved å implementere de fire trinnene over i ditt backendspråk og webserver.

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

Du kan følge denne veiledningen for å aktivere CORS i Apache og Nginx ved å følge trinnene over.

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

med påloggingsinformasjon for forespørsel på tvers av opprinnelse

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

Påloggingsinformasjon (cookie, autorisasjon) sendes med samme opprinnelsesforespørsel som standard. For forespørsler på tvers av opprinnelse må vi spesifisere withCredentials til «true».

axios.defaults.withCredentials = true

XMLHttpRequest API

Hent API

JQuery AjaxAxiosKonklusjon Jeg håper at denne artikkelen har hjulpet deg med å forstå hvordan CORS fungerer og hvordan du aktiverer CORS for forespørsler på tvers av opprinnelse på serveren. Samt hvorfor det er sikrere å lagre cookies som HTTPOnly, og hvordan legitimasjon benyttes i klienter for forespørsler på tvers av opprinnelse.