JWT - JSON Web Token

Webtokens, også kendt som JSON Web Tokens (JWT), er en metode til at autentificere og autorisere brugere i et webmiljø. De bruges til at sikre kommunikationen mellem en klient (f.eks. en webbrowser) og en server.

En webtoken består af tre dele: en header, en payload og en signatur. Headeren indeholder information om typen af token og den anvendte krypteringsalgoritme. Payloaden indeholder brugeroplysninger eller andre data relateret til autentifikation og autorisation. Signaturen bruges til at verificere tokenets integritet og gyldighed.

Webtokens er nyttige til at sikre webapplikationer, da de tillader klienten at gemme og sende autentificeringsoplysninger i form af en token. Dette eliminerer behovet for at gemme følsomme oplysninger som adgangskoder på klienten eller i cookies. Ved at bruge webtokens kan serveren validere og verificere klientens identitet ved hjælp af den gemte token.

Untitled.png

Det er vigtigt at bemærke, at webtokens ikke er krypterede og derfor kan læses af enhver, der har adgang til dem. Derfor skal følsomme oplysninger undgås i tokenets payload. For at sikre fortrolighed kan man anvende kryptering sammen med webtokens.

Ved at implementere webtokens i en webapplikation kan man opnå en mere sikker og skalerbar autentifikations- og autorisationsløsning.

Man kan læse meget mere om det her!

JWT.IO

Why is JWT popular?

JWT - JSON Web Token explained in 4 minutes (With Visuals)

JWT med public og private key

Vores standart måde at implementere en JWT er at vi har en private nøgle som vi bruger til at hashe signaturen på vores Token. Det betyder at vi som har den private nøgle kan godkende om den faktisk er kommet fra os eller om nogen har ændret på vores token!

Hvorfor bruge dobbelt nøgle til JWT

En af fordelene ved JWT med dobbelt nøgle (asymmetrisk kryptering) er, at den tilbyder bedre sikkerhed sammenlignet med symmetrisk kryptering (hvor samme nøgle bruges til både signering og verificering). Her er årsagerne til, at vi bør implementere JWT med dobbelt nøgle:

  1. Forbedret sikkerhed: Ved at bruge et nøglepar (privat og offentlig nøgle), kan vi holde den private nøgle strengt beskyttet på vores server, mens den offentlige nøgle kan distribueres til klienter der skal verificere tokens.
  2. Separation af ansvar: En server kan udstede tokens (signere med privat nøgle), mens andre systemer kan verificere tokens (med den offentlige nøgle) uden at kunne udstede nye.
  3. Microservices arkitektur: I et distribueret system kan en central auth-service udstede tokens, mens andre tjenester kun behøver den offentlige nøgle til verifikation.
  4. Sikker token-verifikation: Selv hvis den offentlige nøgle kompromitteres, kan en angriber ikke udstede falske tokens.

Hvordan fungerer det

  1. Oprettelse af nøglepar: Vi genererer et asymmetrisk nøglepar (ofte RSA eller ECDSA).
  2. Token udstedelse: Serveren signerer JWT-payloaden med den private nøgle.
  3. Token verifikation: Modtageren bruger den offentlige nøgle til at verificere, at tokenet er autentisk.

image.png

Implementering i praksis

I .NET kan vi bruge RSA-nøgler til at signere og verificere JWT tokens. Den private nøgle holdes sikker på udstederserveren, mens den offentlige nøgle kan deles med andre tjenester der har behov for at verificere tokens.

Kodeksempel i C#

Her er et komplet eksempel på, hvordan vi kan implementere JWT med asymmetrisk kryptering i .NET:

using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;

public class AsymmetricJwtService
{
    private readonly RSA _privateKey;
    private readonly RSA _publicKey;

    public AsymmetricJwtService()
    {
// Genererer nøglepar (i produktion vil du typisk indlæse nøgler fra sikker opbevaring)
        _privateKey = RSA.Create(2048);

// Kopier den offentlige nøgle (vil typisk distribueres til andre tjenester)
        _publicKey = RSA.Create();
        _publicKey.ImportRSAPublicKey(_privateKey.ExportRSAPublicKey(), out _);
    }

    public string GenerateToken(string userId, string[] roles)
    {
        var handler = new JwtSecurityTokenHandler();

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, userId),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
        };

// Tilføj roller som claimsforeach (var role in roles)
        {
            claims.Add(new Claim(ClaimTypes.Role, role));
        }

        var credentials = new SigningCredentials(
            new RsaSecurityKey(_privateKey),
            SecurityAlgorithms.RsaSha256);

        var token = new JwtSecurityToken(
            issuer: "myissuer.dk",
            audience: "myaudience.dk",
            claims: claims,
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: credentials
        );

        return handler.WriteToken(token);
    }

    public ClaimsPrincipal ValidateToken(string token)
    {
        var handler = new JwtSecurityTokenHandler();

        var parameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new RsaSecurityKey(_publicKey),
            ValidateIssuer = true,
            ValidIssuer = "myissuer.dk",
            ValidateAudience = true,
            ValidAudience = "myaudience.dk",
            ClockSkew = TimeSpan.Zero
        };

        return handler.ValidateToken(token, parameters, out _);
    }
}

Sikkerhedsovervejelser

Ved implementering af JWT med dobbelt nøgle, bør vi tage hensyn til følgende sikkerhedsaspekter:

  1. Beskyttelse af privat nøgle: Den private nøgle bør opbevares i sikre nøglelagre som Azure Key Vault, AWS KMS eller lignende.
  2. Rotation af nøgler: Nøgler bør roteres periodisk for at begrænse skadevirkningen, hvis en nøgle kompromitteres.
  3. Begrænset gyldighedsperiode: Tokens bør have en kort gyldighedsperiode (typisk timer eller minutter) for at minimere risikoen ved kompromitterede tokens.
  4. Nøglelængde: Brug mindst 2048 bit for RSA-nøgler for at sikre tilstrækkelig kryptografisk styrke.

Testning med CURL

For at teste JWT-verifikation kan vi bruge CURL. Først får vi et token fra vores auth-service:

curl -X POST <https://myauthservice.dk/token> \\
  -H "Content-Type: application/json" \\
  -d '{"username":"brugernavn","password":"kodeord"}'

Derefter kan vi bruge dette token til at kalde en beskyttet tjeneste:

curl -X GET <https://myprotectedservice.dk/resource> \\
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

Fordele i mikroservicearkitektur

I en mikroservicearkitektur er JWT med dobbelt nøgle særligt fordelagtigt:

  1. Central authentication: En dedikeret auth-service kan udstede tokens med den private nøgle.
  2. Distribueret verifikation: Alle mikroservices kan verificere tokens uafhængigt med den offentlige nøgle.
  3. Ingen afhængighed: Mikroservices behøver ikke kontakte auth-servicen for at validere tokens.
  4. Skalerbarhed: Dette mønster skalerer godt, da tokene er selvindeholdte og validering kan ske lokalt.

Ved at bruge JWT med dobbelt nøgle opnår vi både forbedret sikkerhed og bedre arkitekturmæssige fordele i distribuerede systemer.

Ulemper ved asymmetrisk kryptering til JWT

Selvom dobbelt nøgle (asymmetrisk kryptering) giver mange fordele, har denne metode også nogle ulemper sammenlignet med symmetrisk kryptering:

For mindre applikationer eller systemer med færre komponenter kan den ekstra kompleksitet ved asymmetrisk kryptering overskygge fordelene. I disse tilfælde kan symmetrisk kryptering (som HMAC-SHA256) være tilstrækkelig og lettere at implementere.

image.png

  1. Kompleksitet: Implementering og administration af et nøglepar er mere komplekst end at administrere en enkelt hemmelig nøgle.
  2. Ressourceforbrug: Asymmetrisk kryptering er beregningsmæssigt mere krævende end symmetrisk kryptering, hvilket kan påvirke ydelsen ved høj belastning.
  3. Større tokens: JWT'er signeret med RSA eller andre asymmetriske algoritmer har generelt større signaturer end de, der er signeret med HMAC, hvilket resulterer i større token-størrelse.
  4. Nøgleadministration: Sikker opbevaring, distribution og rotation af offentlige nøgler til alle verificerende parter kræver en robust infrastruktur.
  5. Certificeringskompleksitet: I virksomhedsmiljøer kan det være nødvendigt at etablere en PKI (Public Key Infrastructure) for at administrere certifikater og nøgler.

Valget mellem symmetrisk og asymmetrisk kryptering bør baseres på systemets kompleksitet, sikkerhedskrav og arkitektur. For mikroservice-arkitekturer og distribuerede systemer er asymmetrisk kryptering ofte den bedste løsning på trods af disse ulemper.