Vad är JWT?

JWT (JSON Web Token) är en öppen standard (RFC 7519) för att säkert överföra information mellan parter som ett JSON-objekt. Token är digitalt signerat, vilket gör att du kan verifiera och lita på informationen.

Grundläggande koncept

JWT används främst för:

  • Authentication: Bekräfta användarens identitet
  • Authorization: Bestämma vad användaren får göra
  • Information Exchange: Säkert överföra data mellan system

JWT Struktur

En JWT består av tre delar separerade med punkter (.):

header.payload.signature

Exempel JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Låt oss bryta ner varje del:

1. Header

{
  "alg": "HS256",
  "typ": "JWT"
}

Innehåller:

  • alg: Signeringsalgoritm (HS256, RS256, etc.)
  • typ: Token-typ (JWT)

Base64URL-encoded: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622
}

Innehåller claims (påståenden):

  • Registered claims: sub, iat, exp, iss, aud
  • Public claims: Standardiserade namn
  • Private claims: Dina egna custom claims

Base64URL-encoded: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

3. Signature

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

Säkerställer att token inte har manipulerats.

Base64URL-encoded: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Standard Claims

Registered Claims (RFC 7519)

iss (Issuer) - Vem skapade token:

{
  "iss": "https://auth.example.com"
}

sub (Subject) - Vad token handlar om (oftast user ID):

{
  "sub": "user123"
}

aud (Audience) - Vem token är till för:

{
  "aud": "https://api.example.com"
}

exp (Expiration Time) - När token upphör:

{
  "exp": 1735689600  // Unix timestamp
}

iat (Issued At) - När token skapades:

{
  "iat": 1735603200
}

nbf (Not Before) - Token giltig från denna tid:

{
  "nbf": 1735603200
}

jti (JWT ID) - Unikt ID för token:

{
  "jti": "a1b2c3d4"
}

Custom Claims

Lägg till din egen data:

{
  "sub": "user123",
  "email": "john@example.com",
  "role": "admin",
  "permissions": ["read", "write", "delete"],
  "organization": "acme-corp"
}

Signeringsalgoritmer

Symmetriska (HMAC)

HS256, HS384, HS512

Samma secret används för att signera och verifiera:

// Signera
const token = jwt.sign(payload, "secret-key", {
  algorithm: "HS256"
});

// Verifiera
const decoded = jwt.verify(token, "secret-key");

Fördelar:

  • Snabbt
  • Enkelt
  • Mindre token-storlek

Nackdelar:

  • Secret måste delas mellan system
  • Alla med secret kan skapa tokens

Använd när: Single server, eller trusted microservices

Asymmetriska (RSA, ECDSA)

RS256, RS384, RS512, ES256, ES384, ES512

Privat nyckel för signering, publik nyckel för verifiering:

// Signera med privat nyckel
const privateKey = fs.readFileSync('private.key');
const token = jwt.sign(payload, privateKey, {
  algorithm: "RS256"
});

// Verifiera med publik nyckel
const publicKey = fs.readFileSync('public.key');
const decoded = jwt.verify(token, publicKey);

Fördelar:

  • Publik nyckel kan delas fritt
  • Endast authorization server kan skapa tokens
  • Bättre för microservices

Nackdelar:

  • Långsammare
  • Större tokens
  • Mer komplext setup

Använd när: Distribuerade system, public APIs, microservices

Implementation Exempel

Node.js med jsonwebtoken

Installation:

npm install jsonwebtoken

Skapa token:

const jwt = require('jsonwebtoken');

function createToken(userId) {
  return jwt.sign(
    {
      sub: userId,
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 timme
      role: 'user'
    },
    process.env.JWT_SECRET,
    {
      algorithm: 'HS256'
    }
  );
}

Verifiera token:

function verifyToken(token) {
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    return { valid: true, data: decoded };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

Express Middleware:

function authenticateJWT(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  const token = authHeader.split(' ')[1]; // Bearer TOKEN
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(403).json({ error: 'Invalid token' });
  }
}

// Använd i routes
app.get('/protected', authenticateJWT, (req, res) => {
  res.json({ message: 'Protected data', user: req.user });
});

Frontend Implementation

React exempel:

// Login
async function login(email, password) {
  const response = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password })
  });
  
  const { token } = await response.json();
  localStorage.setItem('jwt', token);
  
  return token;
}

// API request med JWT
async function fetchProtectedData() {
  const token = localStorage.getItem('jwt');
  
  const response = await fetch('/api/protected', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  return response.json();
}

// Axios interceptor
axios.interceptors.request.use((config) => {
  const token = localStorage.getItem('jwt');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

Refresh Tokens

Access tokens bör ha kort livslängd (15-30 min). Använd refresh tokens för längre sessions:

// Skapa båda tokens
function createTokens(userId) {
  const accessToken = jwt.sign(
    { sub: userId, type: 'access' },
    process.env.JWT_SECRET,
    { expiresIn: '15m' }
  );
  
  const refreshToken = jwt.sign(
    { sub: userId, type: 'refresh' },
    process.env.REFRESH_SECRET,
    { expiresIn: '7d' }
  );
  
  return { accessToken, refreshToken };
}

// Refresh endpoint
app.post('/api/refresh', async (req, res) => {
  const { refreshToken } = req.body;
  
  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
    
    if (decoded.type !== 'refresh') {
      throw new Error('Invalid token type');
    }
    
    // Skapa ny access token
    const accessToken = jwt.sign(
      { sub: decoded.sub, type: 'access' },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );
    
    res.json({ accessToken });
  } catch (error) {
    res.status(403).json({ error: 'Invalid refresh token' });
  }
});

Säkerhet Best Practices

1. Använd starka secrets

// DÅLIGT
const secret = "mysecret";

// BRA
const secret = crypto.randomBytes(64).toString('hex');
// "a1b2c3d4e5f6..."  (128 tecken)

Lagra i environment variables, aldrig i kod!

2. Sätt alltid expiration

// FARLIGT - token gäller för evigt!
jwt.sign(payload, secret);

// SÄKERT
jwt.sign(payload, secret, { expiresIn: '15m' });

3. Validera ALL claims

function validateToken(token) {
  try {
    const decoded = jwt.verify(token, secret, {
      algorithms: ['HS256'],  // Specifiera tillåtna algoritmer
      issuer: 'https://auth.example.com',
      audience: 'https://api.example.com'
    });
    
    // Extra validering
    if (!decoded.sub) {
      throw new Error('Missing subject claim');
    }
    
    return decoded;
  } catch (error) {
    throw new Error('Invalid token');
  }
}

4. Använd HTTPS

JWT i klartext över HTTP kan interceptas!

// Production check
if (process.env.NODE_ENV === 'production' && !req.secure) {
  return res.status(403).json({ error: 'HTTPS required' });
}

5. Implementera token blacklist

// Redis blacklist
const blacklist = new Set();

async function revokeToken(token) {
  const decoded = jwt.decode(token);
  const ttl = decoded.exp - Math.floor(Date.now() / 1000);
  
  await redis.setex(`blacklist:${token}`, ttl, '1');
}

async function isTokenBlacklisted(token) {
  return await redis.exists(`blacklist:${token}`);
}

// Middleware
async function checkBlacklist(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (await isTokenBlacklisted(token)) {
    return res.status(401).json({ error: 'Token revoked' });
  }
  
  next();
}

6. Undvik känslig data i payload

// FARLIGT - payload är bara base64-encoded!
jwt.sign({
  sub: userId,
  password: "secret123",  // ❌
  creditCard: "1234-5678-9012-3456"  // ❌
}, secret);

// SÄKERT
jwt.sign({
  sub: userId,
  role: "user",  // ✅
  permissions: ["read", "write"]  // ✅
}, secret);

Kom ihåg: JWT payload är INTE krypterad, bara encoded!

Common Pitfalls

1. Algorithm Confusion Attack

// SÅRBART
jwt.verify(token, publicKey);  // Accepterar alla algoritmer

// SÄKERT
jwt.verify(token, publicKey, { algorithms: ['RS256'] });

2. Lagra känsliga data

// FEL - Vem som helst kan läsa detta!
const payload = {
  ssn: "123-45-6789",
  creditCard: "4111111111111111"
};

// RÄTT - Bara identifierare
const payload = {
  sub: "user123",
  role: "admin"
};

3. Ingen expiration

// Evigt giltiga tokens = säkerhetsrisk
jwt.sign(payload, secret);  // ❌

// Alltid sätt expiration
jwt.sign(payload, secret, { expiresIn: '1h' });  // ✅

4. Lita på alg: none

// Validera algoritm
const decoded = jwt.verify(token, secret, {
  algorithms: ['HS256', 'RS256']  // Tillåt endast dessa
});

När INTE använda JWT

JWT är inte alltid rätt lösning:

❌ Undvik JWT för:

Session management

  • Cookies + server-side sessions är bättre
  • Enklare att revoke
  • Mindre data skickas

Känslig data

  • Payload är synlig
  • Använd encrypted tokens eller session storage

Stora payloads

  • Varje request blir stor
  • Databaser är bättre för stor data

När du behöver revoke

  • JWT är stateless = svårt att revoke
  • Session tokens är lättare

✅ Använd JWT för:

  • Distribuerade system
  • Microservices
  • Public APIs
  • Single Sign-On (SSO)
  • Mobile apps
  • Stateless authentication

Verktyg

Använd våra JWT-verktyg:

Slutsats

JWT är ett kraftfullt verktyg för modern authentication, men kräver:

✅ Gör:

  • Använd starka secrets
  • Sätt kort expiration (15-30 min)
  • Implementera refresh tokens
  • Validera ALL claims
  • Använd HTTPS
  • Specifiera algoritmer

❌ Undvik:

  • Känslig data i payload
  • Långa expiration times
  • Svag secret key
  • alg: none
  • Stora payloads
  • HTTP (utan HTTPS)

Med rätt implementation är JWT en säker och skalbar lösning för authentication!