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:
- JWT Decoder/Validator - Inspektera och validera JWTs
- Base64URL Converter - Decode JWT delar
- Hash Generator - Testa olika algoritmer
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!