JWT Authentication
This guide demonstrates practical examples of using JWT (JSON Web Tokens) with the Stilmark Base library.
Table of Contents
Prerequisites
Make sure you have these environment variables configured in your .env
file:
JWT_SECRET=your-secret-key-here
JWT_ISSUER=https://your-domain.com
JWT_ALGORITHM=HS256
Basic Usage
1. Generating a Token
use Stilmark\Base\Jwt;
// Basic token with user ID and email
$token = Jwt::generate([
'user_id' => 123,
'email' => 'user@example.com'
]);
// With custom expiration (24 hours)
$token = Jwt::generate(
['user_id' => 123],
86400 // 24 hours in seconds
);
2. Validating a Token
try {
$decoded = Jwt::validate($token);
// Access token data
$userId = $decoded->user_id;
$email = $decoded->email;
// Token expiration time
$expiresAt = date('Y-m-d H:i:s', $decoded->exp);
} catch (Exception $e) {
// Handle invalid token
error_log('JWT validation failed: ' . $e->getMessage());
http_response_code(401);
exit('Invalid token');
}
### 3. Using Cookies for JWT Storage
For web applications, it's recommended to store JWTs in secure, HTTP-only cookies:
```php
use Stilmark\Base\Jwt;
use Stilmark\Base\Helper;
// After successful authentication
$token = Jwt::generate([
'user_id' => 123,
'email' => 'user@example.com'
]);
// Set secure, HTTP-only cookie (defaults to 1 day)
Helper::setJwtCookie($token, [
'expires' => 86400, // 1 day
'sameSite' => 'Strict' // Or 'Lax' if you need cross-site requests
]);
// In subsequent requests, get the JWT from the cookie
$jwt = Helper::getCookie('jwt');
if ($jwt) {
try {
$decoded = Jwt::validate($jwt);
// User is authenticated
} catch (Exception $e) {
// Handle invalid token
Helper::deleteCookie('jwt');
}
}
// To log out (invalidate the token)
Helper::deleteCookie('jwt');
4. Complete Login Example with Cookies
// Login endpoint
$router->post('/login', function($request) {
// Validate credentials (pseudo-code)
$user = User::validateCredentials(
$request->input('email'),
$request->input('password')
);
if (!$user) {
http_response_code(401);
return ['error' => 'Invalid credentials'];
}
// Generate JWT
$token = Jwt::generate([
'user_id' => $user->id,
'email' => $user->email,
'roles' => $user->roles
]);
// Set secure cookie
Helper::setJwtCookie($token, [
'expires' => 86400, // 1 day
'sameSite' => 'Strict'
]);
return [
'status' => 'success',
'user' => [
'id' => $user->id,
'email' => $user->email
]
];
});
// Logout endpoint
$router->post('/logout', function() {
Helper::deleteCookie('jwt');
return ['status' => 'success'];
});
Using with AuthMiddleware
The AuthMiddleware
can automatically validate JWT tokens from the Authorization
header.
1. Protect a Route
// In your route definition
$router->get('/protected-route', function() {
return ['message' => 'This is a protected route'];
})->middleware([new AuthMiddleware()]);
2. Accessing User Data in Protected Routes
After successful validation, the decoded token is stored in the session:
$router->get('/profile', function($request) {
$decodedToken = $_SESSION[env('SESSION_AUTH_NAME', 'auth')]['jwt'] ?? null;
if (!$decodedToken) {
http_response_code(401);
return ['error' => 'Not authenticated'];
}
return [
'user_id' => $decodedToken->user_id,
'email' => $decodedToken->email ?? null
];
});
Handling Token Refresh
Implement a refresh token endpoint:
$router->post('/refresh-token', function($request) {
$refreshToken = $request->input('refresh_token');
try {
// Validate the refresh token (you might store these in a database)
$decoded = Jwt::validate($refreshToken);
// Issue a new access token
$newToken = Jwt::generate([
'user_id' => $decoded->user_id,
'email' => $decoded->email
]);
return [
'access_token' => $newToken,
'expires_in' => 3600 // 1 hour
];
} catch (Exception $e) {
http_response_code(401);
return ['error' => 'Invalid refresh token'];
}
});
Custom Claims and Validation
Adding Custom Claims
$token = Jwt::generate([
'user_id' => 123,
'roles' => ['admin', 'editor'],
'custom_data' => [
'preferences' => ['theme' => 'dark'],
'permissions' => ['read:users', 'write:posts']
]
]);
Validating Custom Claims
try {
$decoded = Jwt::validate($token);
// Check for required roles
if (!in_array('admin', $decoded->roles ?? [])) {
throw new Exception('Insufficient permissions');
}
// Access custom data
$theme = $decoded->custom_data->preferences->theme ?? 'light';
} catch (Exception $e) {
// Handle validation errors
}
Error Handling
Common JWT Exceptions
try {
$decoded = Jwt::validate($invalidToken);
} catch (Exception $e) {
$error = $e->getMessage();
// Handle specific error cases
if (str_contains($error, 'expired')) {
http_response_code(401);
return ['error' => 'Token has expired', 'code' => 'token_expired'];
} elseif (str_contains($error, 'signature')) {
http_response_code(401);
return ['error' => 'Invalid token signature', 'code' => 'invalid_signature'];
} else {
// Log other errors
error_log("JWT Error: $error");
http_response_code(500);
return ['error' => 'Authentication error'];
}
}
Best Practices
Secure Token Storage
For web applications, always use HTTP-only, secure cookies with SameSite attribute
Set appropriate cookie expiration times (shorter is more secure)
For mobile/SPA, use secure storage (e.g., Keychain, SecureStore)
Never store sensitive data in JWT payload
Implement proper token refresh flow
Token Expiration
Keep access tokens short-lived (e.g., 15-60 minutes)
Use refresh tokens for longer sessions
Secret Management
Never hardcode JWT_SECRET in your code
Use different secrets for different environments
Rotate secrets periodically
Token Payload
Keep the payload small (JWT is sent with every request)
Don't store sensitive data in the token
Use standard claims when possible (e.g.,
sub
,iss
,exp
)
Security Headers
Always use HTTPS
Set appropriate CORS headers
Use security headers like
Strict-Transport-Security
Complete Example: JWT Login Flow
// Login endpoint
$router->post('/login', function($request) {
$email = $request->input('email');
$password = $request->input('password');
// Validate credentials (pseudo-code)
$user = User::where('email', $email)->first();
if (!$user || !password_verify($password, $user->password)) {
http_response_code(401);
return ['error' => 'Invalid credentials'];
}
// Generate tokens
$accessToken = Jwt::generate([
'user_id' => $user->id,
'email' => $user->email,
'roles' => $user->roles
], 3600); // 1 hour
$refreshToken = Jwt::generate([
'user_id' => $user->id,
'token_type' => 'refresh'
], 2592000); // 30 days
return [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_in' => 3600,
'token_type' => 'Bearer'
];
});
This example provides a solid foundation for implementing JWT authentication in your application. Remember to adapt it to your specific security requirements and application architecture.
Last updated