JWTs Are Signed, Not Encrypted
Anyone can read the payload. Only the server can create valid ones.
Structure
header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.signature
Header: Algorithm used Payload: Claims (data) Signature: Verification
Creating Tokens
use Firebase\JWT\JWT;
$payload = [
'sub' => $user->id, // Subject
'iat' => time(), // Issued at
'exp' => time() + 3600, // Expires
'role' => $user->role,
];
$token = JWT::encode($payload, $secretKey, 'HS256');
Verifying Tokens
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
try {
$decoded = JWT::decode($token, new Key($secretKey, 'HS256'));
$userId = $decoded->sub;
} catch (ExpiredException $e) {
// Token expired
} catch (Exception $e) {
// Invalid token
}
Security Best Practices
Short expiration:
'exp' => time() + 900, // 15 minutes
Refresh tokens for long sessions:
// Access token: 15 min
// Refresh token: 7 days, stored securely
Strong secrets:
# Generate secret
openssl rand -base64 64
HTTPS only: Never send tokens over HTTP.
Common Mistakes
- Storing sensitive data - JWTs are readable
- Long expiration - Harder to revoke
- No expiration - Never do this
- Weak secrets - Use 256+ bit keys
- Algorithm confusion - Validate algorithm server-side
When to Use JWTs
- Stateless API authentication
- Cross-domain auth
- Mobile apps
When Not to Use
- Simple web apps (use sessions)
- When you need instant revocation
- Storing large amounts of data
