JSON Web Tokens (JWT) är något som idag används alltmer för autentisering och sessionshantering inom webbapplikationer. Dessa tokens erbjuder en till synes smidig och skalbar metod för att verifiera användares identiteter och hantera åtkomst till resurser. Det finns dock en del problem med denna lösning, vilka kommer diskuteras i detta inlägg.
JSON Web Tokens (JWT)
För att kunna förstå utmaningarna med användning av JWT:s för sessionshantering är det viktigt att först få en grundläggande förståelse för hur de fungerar.
JSON Web Tokens var ursprungligen avsedda som en standard för att representera och överföra information mellan två parter i en säker och kompakt form. JWT är en del av en familj av tekniska specifikationer, vilka inkluderar standarder för autentisering (JWT), kryptering (JWE) och nyckelutbyte (JWK).
Mer specifikt var JWT:s ursprungligen tänkta som ett sätt att säkert överföra anspråk (claims) mellan två parter. Dessa anspråk kan inkludera information om användare, autentiseringsstatus, behörigheter och annan relevant data för att underlätta kommunikation och autentisering mellan olika delar av en applikation eller över olika system.
JWT:s bygger på JSON (JavaScript Object Notation), vilket gör dem enkla att använda och implementera i många olika programmeringsspråk och miljöer. De är självständiga, vilket innebär att de bär med sig all nödvändig information för att verifiera dess innehåll, inklusive en signatur som används för att säkerställa dess giltighet och integritet.
Ett JWT är uppbyggt av tre huvudsakliga delar: en header, en payload och en signatur. Header-delen innehåller metadata om tokenen, specificerar vilken typ av token det är, samt den använda signeringsalgoritmen. Payloaden innehåller själva användarinformationen eller anspråken som tokenen bär med sig, såsom användarens ID, behörigheter eller annan relevant data. Headern och payloaden visas som JSON-strängar och är Base64-kodade. Signaturen används för att verifiera att tokenen är giltig och inte har manipulerats under överföringen. Den genereras som en binär sträng genom att kombinera header- och payloaddelarna med en hemlig nyckel och applicera en signeringsalgoritm, till exempel HMAC eller RSA. Signaturen blir slutligen också Base64-kodad.
Sessionshantering med JWT
JWT:s har under åren blivit en populär lösning för sessionshantering, även om det som tidigare nämnt inte är vad de ursprungligen var tänka att användas till. De fördelar som många ser med användningen av JWT:s för detta ändamål är:
Självständighet (statelessness): Eftersom JWT:s bär all nödvändig information för att verifiera en användares identitet och behörigheter, behöver inte servern spara någon sessionsdata lokalt. Detta minskar belastningen på servern och gör det möjligt att bygga skalbara och distribuerade system.
Säkerhet: JWT:s kan krypteras och signeras för att säkerställa att deras innehåll förblir konfidentiellt och integriteten bevaras. Detta gör dem tillförlitliga för att överföra autentiseringsuppgifter över osäkra nätverk som internet.
Flexibilitet: JWT:s kan användas för att hantera sessioner för olika typer av applikationer och API:er, oavsett vilket programmeringsspråk eller ramverk som används. Detta då JWT:s använder JSON-strukturer för att bära data.
När en användare autentiseras, till exempel genom att logga in på en webbplats eller en applikation, genereras en JWT som innehåller information om användaren och dess behörigheter. Denna JWT skickas till klienten och används sedan för att autentisera efterföljande förfrågningar till servern.
På ytan kan JWT:s ses som en bra och smidig lösning för sessionshantering, men när vi börjar titta lite djupare så uppdagas en del allvarliga brister i de fördelar som JWT:s anses ha.
Problemen
Livstid och validitet
JWT:s självständighet, en av anledningarna till att de blivit så populära, är tyvärr också ett av de största problemen när det kommer till användningen av JWT:s för sessionshantering. Mer specifikt är problemet här JWT:s livstid.
När ett JWT utfärdas vid etablering av en session får det en utfärdningstid (issued at) och förhoppningsvis en utgångstid (expire at), vilka tillsammans definierar JWT:ns livslängd (Time-To-Live TTL), det vill säga hur länge sessionen är aktiv.
Då användningen av JWT:s gör att servern inte lagrar någon information om sessionen, så finns det inte heller någon möjlighet för servern att uppdatera eller ogiltigförklara de tokens som utfärdats. Det innebär att efter ett JWT har utfärdats vid starten av en session, förblir det aktivt tills det att dess livstid löpt ut. Till exempel:
En användare loggar in på en applikation och servern utfärdar ett JWT för sessionen med en livstid på 60 minuter. Om användaren sedan ”loggar ut” efter 10 minuter, innebär detta att sessionen fortfarande är aktiv i 50 minuter till.
Detta kan även bli ett problem med användarrättigheter. Om en användare med admin-rättigheter får sina rättigheter nedgraderade, men de använder ett aktivt JWT som utfärdades innan nedgraderingen innebär detta att de fortsätter ha admin-rättigheter tills det att den gamla JWT:n har löpt ut.
Dessa scenarion innebär stora säkerhetsrisker om JWT:s skulle bli stulna, vilket vi kommer diskutera i kommande sektion.
Stulna sessioner
En av de mest oroande säkerhetsaspekterna vid hanteringen av sessionsdata är risken för stulna sessioner, där en obehörig part lyckas få åtkomst till och utnyttja en annan användares inloggade session. Denna potentiella sårbarhet kan ha allvarliga konsekvenser, inklusive obehörig åtkomst till användarens konto eller systemet som helhet. Det är en potentiell sårbarhet även för traditionell sessionshantering med sessionskakor, men det finns vissa skillnader.
I traditionell sessionshantering med sessionskakor är en aktiv session knuten till en unik sessionskaka som lagras på klientens enhet som en HTTP-cookie. Denna sessionskaka fungerar som en referens till sessionens data, som lagras på servern. Varje gång en förfrågan görs måste klienten interagera med servern, vilken har full kontroll över sessionens livslängd och kan reglera när sessionen börjar och slutar. För att utnyttja en stulen session måste en angripare få tag i en sessionskaka för en aktiv session. Fördelen med att sessionen lagras på servern är att sessionen kan avslutas från serverns sida och inte ge angriparen tillgång med den stulna sessionskakan. Dessutom finns det väletablerade skyddsåtgärder för att skydda mot stölder av sessionskakor, så som användning av säkra HTTP-cookies samt åtgärder mot XSS och CSRF-attacker, mer om detta nedan.
I jämförelse innehåller JWT:s all nödvändig information för att verifiera och hantera en session direkt i tokenen. Om en sessions JWT blir stulen har inte servern någon kontroll över sessionstillståndet, vilket innebär som tidigare nämnt att angriparen har tillgång till sessionen tills det att JWT:ns livstid löper ut. Denna brist på kontroll över sessionen kan utgöra en ökad risk jämfört med traditionell sessionshantering med sessionskakor, särskilt om adekvata säkerhetsåtgärder inte har vidtagits för att skydda JWT:s mot stöld och manipulation.
Felaktig implementering och signeringssårbarheter
Implementeringen av JWT:s på ett säkert sätt är också en kritisk del som bör övervägas. Det är värt att notera att användningen av JWT:s för sessionshantering kan vara mer komplex att implementera än traditionell sessionshantering med sessionskakor, vilket kan introducera fler sårbarheter vid implementation. Vi har redan diskuterat livstiden som en av dessa faktorer, där implementeringen bör innehålla en relativt kort livslängd för att undvika att JWT:n kan återanvändas under en längre tid. Detta minskar risken och tiden som komprometterade tokens kan användas av potentiella angripare, men eliminerar den inte. Ur en säkerhetssynpunkt bör livslängden vara på ett par minuter, dock är detta ofta inte särskilt önskvärt ur ett användarperspektiv då användaren kommer vara tvungen att logga in igen varje gång livslängden på sessionen utlöper. Här måste man, likt alla säkerhetslösningar göra en avvägning mellan säkerhet och användarvänlighet.
En annan potentiell sårbarhet som kan komma av felaktig implementering är om osäkra signeringsalgoritmer används, att det inte finns någon kontroll för vilka signeringsalgoritmer som får användas, eller i värsta fall att ingen signeringsalgoritm alls används.
Användningen av osäkra signeringsalgoritmer kan leda till att signaturer blir lätta att gissa eller beräkna av angripare. Detta kan vidare möjliggöra token-förfalskning och obehörig åtkomst till systemet. Dessutom kan användning av osäkra algoritmer leda till bristande integritet och autenticitet hos JWT-tokens, vilket öppnar upp för risker som avlyssning, dataförfalskning och brister i konfidentialitet. För att undvika dessa sårbarheter är det avgörande att använda säkra kryptografiska algoritmer för att signera JWTs, såsom HMAC med SHA-256 eller RSA med PKCS#1 v1.5 eller RSA-PSS.
Ett annat allvarligt säkerhetsproblem vid implementeringen av signeringsalgoritmer är om implementeringen tillåter användning av "none" som signeringsalgoritm. Om detta är fallet innebär det att JWT:n helt saknar signeringskontroll, vilket gör att JWT-tokens inte verifieras och möjliggör manipulation av tokenets innehåll utan att det kan upptäckas. Detta öppnar upp för risker såsom förfalskning av tokens, sessionsstöld samt bristande dataintegritet och konfidentialitet. Användning av "none"-algoritmen resulterar i en fullständig avsaknad av säkerhet för JWT-tokens och är därför kritiskt att undvika.
Lagring
När det kommer till lagring av JWT:s finns det också potentiella sårbarheter som måste hanteras med omsorg. Då JWT:s ofta har en betydligt större storlek än vad som kan rymmas i sessionskakor, ca 4KB, kan detta innebära att de måste lagras i användarens local storage. Då enda sättet att hämta data ur local storage är användningen av JavaScript, innebär detta att möjligheten för sårbarheter som avlyssning eller stöld genom XSS (Cross-Site Scripting) - attacker har öppnats upp. Detta kan ge angripare möjligheten att få tillgång till JWT-tokens och använda dem för att få obehörig åtkomst till användarens konto eller privilegier. Om JWT:n och andra sidan är liten nog i storlek för att lagras som HTTP-cookies, men inte har lämpligt skydd mot Cross-Site Request Forgery (CSRF)-attacker kan en angripare lura användare till att utföra oönskade handlingar på webbplatsen medan de är inloggade, vilket kan resultera i kompromettering av användarens session och tillgång till systemet.
Sessionskakor har traditionellt lagrats som HTTP-cookies och är därför sårbara för samma CSRF-attacker som JWTs. Men eftersom sessionskakor vanligtvis endast innehåller en identifierare som refererar till en sessionsdata lagrad på servern, kan risken för kompromettering av användarsessioner vara mindre jämfört med JWTs.
Kryptering och Innehåll
När vi pratar om JWT:s är det också viktigt att skilja mellan kodning och kryptering. Kodning, som vi tidigare diskuterat, handlar om att omvandla data till en specifik textbaserad representation för att göra den läsbar och enkel att överföra över nätverket. Å andra sidan innebär kryptering att data faktiskt krypteras, vilket ger en nivå av sekretess och skydd.
Eftersom JWT:s även har en tendens att ha ett betydligt mer omfattande innehåll än sessionskakor, kan det vara fördelaktigt att överväga kryptering för att skydda detta innehåll mot obehörig åtkomst och manipulation, särskilt när tokenet lagras lokalt i webbläsaren. Man bör också kontrollera att JWT:n inte innehåller känsliga data som inte är nödvändig för sessionshanteringen, till exempel personlig eller konfidentiell information vilken kan utgöra en betydande risk om den exponeras för obehöriga.
Merarbete vid lösningar
Det finns ett antal lösningar föreslagna för att hantera dessa tillkortakommanden, men i många fall innebär det att vissa av de fördelar som finns med JWT:s går förlorade.
En potentiell lösning på problemet med livstiden hos JWT:s och att de inte går att ogiltigförklara efter de blivit utfärdade kan var att införa en whitelist på servern för att hålla koll på vilka JWT:s som fortfarande är giltiga. Om detta görs så förloras dock självständigheten med JWTs, då servern ändå behöver behålla data om sessionerna och vi är då återigen tillbaka på ruta ett och kan ifrågasätta varför JWT:s användes till att börja med.
En alternativ lösning på samma problem som brukar föreslås är användningen av refresh-tokens, vilka möjliggör förlängning av sessionslivslängden genom att generera nya access tokens när de gamla löper ut. Med refresh-tokens kan en kort livstid sättas för access tokens medan refresh-tokenen har en längre livslängd. Denna metod hjälper till att undvika att användaren behöver autentisera sig igen efter varje session, vilket kan förbättra användarupplevelsen och minska belastningen på autentiseringsservern.
Dock medför användningen av refresh-tokens ytterligare risker. Bland annat en ökad attackyta då ytterligare ett token introducerats. Sessionslängden kan bli längre då sessionen kan förnyas så länge refresh-tokenet är aktivt, vilket ökar exponeringstiden för attacker. Även komplexiteten i hantering och implementering av refresh-tokens ökar också vilket som tidigare nämnt kan introducera oförutsägbara sårbarheter om detta inte utförs korrekt. Dessutom kräver användningen av refresh-tokens en viss form av sessionshantering på servern, vilket likt föregående lösningsförslag minskar JWT:ns "statelessness" och självständighet.
Vidare kan även refresh-tokens bli föremål för stöld om de inte hanteras och lagras säkert. Om en angripare lyckas stjäla en refresh-token från en användares enhet eller fånga den under överföringen över nätverket, kan de använda den för att generera nya access tokens och därigenom få obehörig åtkomst till användarens konto eller systemet som helhet.
Alla potentiella säkerhetslösningar för användning av JWT:s för sessionshantering resulterar slutligen i att JWT:s självständighet kommer behöva reduceras och servern behöver lagra någon form av data om sessionerna. Detta kombinerat med komplexiteten med implementering och hantering av JWT:s för detta ändamål kan göra att risken för säkerhetsbrister ökar markant. Dessa anledningar i kombination med de uppenbara risker som finns med JWT:s för sessionshantering är klara argument till varför sessioner bör hanteras med hjälp av sessionskakor. Implementationen och bästa praxis vid användningen av sessionskakor är väletablerad, och användningen av en väletablerad lösning gör det lättare att hålla den säker och minska risken för att oförutsägbara sårbarheter introduceras.
Vad bör JWT:s användas till
Medan JWT:s kan vara mindre lämpliga för själva hanteringen av användarsessioner på grund av de nämnda säkerhetsriskerna och begränsningarna, finns det fortfarande användningsfall där JWT:s kan vara mycket värdefulla.
Exempelvis kan JWT:s vara användbara för autentisering och auktorisering inom API-tjänster, särskilt när det gäller kommunikation mellan olika tjänster eller mellan klienter och servrar. Genom att använda JWT:s som API-tokens kan utvecklare skapa säkra och skalbara API:er som tillåter autentiserade användare att få åtkomst till resurser och utföra åtgärder med minimal overhead.
Likaså kan JWT:s vara användbara i sammanhang där federation av användaridentiteter är nödvändig, såsom single sign-on (SSO) eller OpenID Connect-implementeringar. Detta genom att använda JWT:s för att säkert överföra och verifiera användaridentiteter mellan olika tjänster och domäner.
Slutsats
Även om JWT:s kan vara ett kraftfullt verktyg för autentisering och auktorisering inom webbapplikationer, är det viktigt att vara medveten om potentiella risker och begränsningar med deras användning, särskilt när det gäller sessionshantering. Trots deras popularitet och till synes fördelaktiga egenskaper som självständighet och säkerhet, har vi diskuterat flera allvarliga problem som kan uppstå vid implementeringen av JWT:s för sessionshantering.
Risken för stulna sessioner, problem med livstid och giltighet, felaktig implementering av signeringsalgoritmer, samt säker lagring av tokens. Dessa risker visar tydligt att användningen av JWT:s för sessionshantering kan medföra betydande säkerhetsrisker om de inte hanteras korrekt.
Det finns föreslagna lösningar för att mildra vissa av dessa risker, såsom användning av refresh-tokens eller implementering av whitelist-filtrering på servern. Problemet med dessa är att de antingen inte löser problemet fullständigt, eller så gör lösningen att man tappar de fördelar som varit anledningen till att JWT:s användes från början. Till följd av dessa argument bör man noga överväga om användningen av JWT:s för sessionshantering verkligen är ett bra alternativ till användning av sessionskakor.
Men, det finns fördelar och nackdelar med alla säkerhetslösningar. Det är slutligen upp till varje utvecklare och organisation att noggrant överväga sina specifika behov och krav när det gäller sessionshantering och autentisering, och att välja den mest lämpliga lösningen för deras applikation och användarbas. Vikten ligger i vanlig ordning på säker implementering och användning.
Referenser:
Stop using JWT for sessions - joepie91's Ramblings (cryto.net)
JSON Web Tokens (JWT) are Dangerous for User Sessions—Here’s a Solution - Redis