Session Security and JWT
Session Security and JWT
Session and token management are critical components of web application security. Understanding how to properly secure sessions and implement JWT (JSON Web Tokens) protects against common authentication attacks.
Session Security Fundamentals
Sessions maintain user state across HTTP requests. Without proper security measures, sessions can be vulnerable to various attacks:
<?php
session_start();
// Default settings expose vulnerabilities
?>
<!-- Secure Session Configuration -->
<?php
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_only_cookies', 1);
ini_set('session.use_strict_mode', 1);
session_start();
?>
Session Hijacking Prevention
Session hijacking occurs when an attacker steals a valid session ID to impersonate a user. Multiple defense layers are required:
// Regenerate session ID on privilege changes
function secureLogin($userId) {
session_regenerate_id(true);
$_SESSION['user_id'] = $userId;
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['last_activity'] = time();
}
// Validate session on each request
function validateSession() {
if (!isset($_SESSION['user_id'])) {
return false;
}
// Check IP address (optional - can break mobile users)
if ($_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
session_destroy();
return false;
}
// Check user agent
if ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
session_destroy();
return false;
}
// Check session timeout (30 minutes)
if (time() - $_SESSION['last_activity'] > 1800) {
session_destroy();
return false;
}
$_SESSION['last_activity'] = time();
return true;
}
?>
Session Fixation Prevention
Session fixation attacks trick users into using a known session ID. Always regenerate session IDs at authentication boundaries:
// Before login - attacker may know this ID
session_start();
// After successful authentication
if (authenticateUser($username, $password)) {
// Regenerate ID to invalidate old session
session_regenerate_id(true);
$_SESSION['authenticated'] = true;
$_SESSION['user_id'] = $userId;
}
// Also regenerate on logout
function logout() {
session_start();
$_SESSION = [];
session_destroy();
session_regenerate_id(true);
}
?>
Secure Cookie Attributes
Cookie attributes control how browsers handle session cookies. Each attribute provides specific security protections:
// Set secure cookie attributes
session_set_cookie_params([
'lifetime' => 0, // Session cookie (expires on browser close)
'path' => '/', // Available site-wide
'domain' => '.example.com', // Include subdomains
'secure' => true, // HTTPS only
'httponly' => true, // No JavaScript access
'samesite' => 'Strict' // CSRF protection
]);
session_start();
?>
Secure attribute requires HTTPS. The HttpOnly attribute prevents XSS attacks from stealing cookies. The SameSite attribute prevents CSRF attacks.JWT (JSON Web Tokens) Fundamentals
JWTs are stateless tokens containing encoded claims. They consist of three parts: header, payload, and signature:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // Header (base64)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ. // Payload (base64)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // Signature
<?php
// Creating JWT with PHP
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$secretKey = getenv('JWT_SECRET_KEY');
$issuedAt = time();
$expirationTime = $issuedAt + 3600; // 1 hour
$payload = [
'iss' => 'example.com', // Issuer
'aud' => 'example.com', // Audience
'iat' => $issuedAt, // Issued at
'exp' => $expirationTime, // Expiration
'sub' => $userId, // Subject (user ID)
'data' => [
'userId' => $userId,
'email' => $userEmail
]
];
$jwt = JWT::encode($payload, $secretKey, 'HS256');
// Verifying JWT
try {
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
$userData = $decoded->data;
} catch (Exception $e) {
// Invalid token
http_response_code(401);
die('Unauthorized');
}
?>
JWT Vulnerabilities
JWTs have specific vulnerabilities that must be addressed:
// 1. Algorithm Confusion Attack
// VULNERABLE: Allowing 'none' algorithm
$decoded = JWT::decode($jwt, null, ['HS256', 'none']);
// SECURE: Specify exact algorithm
$decoded = JWT::decode($jwt, new Key($secretKey, 'HS256'));
// 2. Weak Secret Keys
// VULNERABLE: Predictable key
$secretKey = 'secret123';
// SECURE: Strong random key (256+ bits)
$secretKey = bin2hex(random_bytes(32));
// 3. Missing Expiration Validation
// VULNERABLE: Token never expires
$payload = ['userId' => $userId];
// SECURE: Always set expiration
$payload = [
'userId' => $userId,
'exp' => time() + 3600
];
// 4. Sensitive Data in Payload
// VULNERABLE: Storing passwords in JWT
$payload = ['password' => $hashedPassword];
// SECURE: Store only necessary claims
$payload = ['userId' => $userId, 'role' => $role];
?>
Token Storage Best Practices
Where you store tokens significantly impacts security:
// ❌ LocalStorage - Vulnerable to XSS
localStorage.setItem('jwt', token);
// ❌ SessionStorage - Vulnerable to XSS
sessionStorage.setItem('jwt', token);
// ✅ HTTP-Only Cookie - Best for web apps
<?php
setcookie('jwt', $token, [
'expires' => time() + 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
?>
// ✅ Memory-only (SPAs) - XSS resistant
// Store token in JavaScript closure or React state
// Never persist to localStorage/sessionStorage
let authToken = null;
function setToken(token) {
authToken = token;
}
function getToken() {
return authToken;
}
Refresh Token Rotation
Implement refresh tokens to reduce access token lifetime while maintaining user sessions:
// Token generation function
function generateTokenPair($userId) {
$accessTokenExpiry = time() + 900; // 15 minutes
$refreshTokenExpiry = time() + 604800; // 7 days
$accessToken = JWT::encode([
'sub' => $userId,
'exp' => $accessTokenExpiry,
'type' => 'access'
], getenv('JWT_SECRET'), 'HS256');
$refreshToken = bin2hex(random_bytes(32));
// Store refresh token in database
storeRefreshToken($userId, $refreshToken, $refreshTokenExpiry);
return [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => 900
];
}
// Refresh endpoint
function refreshAccessToken($refreshToken) {
// Verify refresh token in database
$tokenData = validateRefreshToken($refreshToken);
if (!$tokenData) {
http_response_code(401);
return ['error' => 'Invalid refresh token'];
}
// Rotate refresh token (generate new one)
invalidateRefreshToken($refreshToken);
// Generate new token pair
return generateTokenPair($tokenData['user_id']);
}
// Database storage for refresh tokens
function storeRefreshToken($userId, $token, $expiry) {
$db->prepare(
'INSERT INTO refresh_tokens (user_id, token, expires_at) ' .
'VALUES (?, ?, FROM_UNIXTIME(?))' .
'ON DUPLICATE KEY UPDATE token = VALUES(token), expires_at = VALUES(expires_at)'
)->execute([$userId, hash('sha256', $token), $expiry]);
}
?>
1. Configure secure session cookie attributes
2. Implement session regeneration on login/logout
3. Add session timeout after 30 minutes of inactivity
4. Validate user agent on each request
5. Create a logout function that properly destroys sessions
Then enhance it with JWT:
6. Generate access and refresh token pairs
7. Implement token rotation on refresh
8. Store refresh tokens securely in database
9. Add proper expiration handling
10. Test both session and JWT authentication flows