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!