Security & Performance

Session Security and JWT

20 min Lesson 6 of 35

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:

<!-- Insecure Session Cookie -->
<?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:

<?php
// 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;
}
?>
Warning: IP address validation can cause issues for users on mobile networks or behind proxies. Consider user experience when implementing strict session validation.

Session Fixation Prevention

Session fixation attacks trick users into using a known session ID. Always regenerate session IDs at authentication boundaries:

<?php
// 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:

<?php
// 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();
?>
Note: The 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:

// JWT Structure
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:

<?php
// 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];
?>
Warning: JWT payloads are base64-encoded, NOT encrypted. Never store sensitive data like passwords or personal information in JWTs without additional encryption.

Token Storage Best Practices

Where you store tokens significantly impacts security:

// CLIENT-SIDE STORAGE COMPARISON

// ❌ 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;
}
Best Practice: For traditional web apps, use HTTP-only cookies. For SPAs requiring localStorage, implement additional XSS protections like Content Security Policy.

Refresh Token Rotation

Implement refresh tokens to reduce access token lifetime while maintaining user sessions:

<?php
// 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]);
}
?>
Note: Refresh token rotation ensures that even if a refresh token is stolen, it can only be used once. Store refresh tokens hashed in the database.
Exercise: Implement a secure session management system with the following requirements:
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