Building Secure JWT Authentication in Laravel with Best Practices
Introduction
JSON Web Tokens (JWT) have become the de facto standard for API authentication in modern web applications. However, implementing JWT authentication securely requires more than just installing a package and generating tokens. In this comprehensive guide, we'll build a robust JWT authentication system in Laravel that addresses common security vulnerabilities and follows industry best practices.
Setting Up JWT in Laravel
First, let's install the popular tymon/jwt-auth package and configure it properly:
composer require tymon/jwt-auth
# Publish the config
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
# Generate JWT secret
php artisan jwt:secretConfigure your config/jwt.php with security-focused settings:
'ttl' => env('JWT_TTL', 60), // 1 hour instead of default 60 minutes
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), // 2 weeks
'algo' => env('JWT_ALGO', 'HS256'),
'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
],
'persistent_claims' => [],
'lock_subject' => true,
'leeway' => env('JWT_LEEWAY', 0),
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
'decrypt_cookies' => false,
'providers' => [
'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class,
'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
]Implementing Secure User Model
Update your User model to implement the JWT contract and add security enhancements:
'datetime',
'last_login_at' => 'datetime',
'login_attempts' => 'integer'
];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [
'user_id' => $this->id,
'email' => $this->email,
'role' => $this->role ?? 'user',
'version' => $this->token_version ?? 1 // For token invalidation
];
}
public function setPasswordAttribute($password)
{
$this->attributes['password'] = Hash::make($password);
}
public function incrementLoginAttempts()
{
$this->increment('login_attempts');
$this->touch();
}
public function resetLoginAttempts()
{
$this->login_attempts = 0;
$this->last_login_at = now();
$this->save();
}
}Secure Authentication Controller
Create a robust authentication controller with rate limiting and security measures:
validate([
'email' => 'required|email',
'password' => 'required|min:8'
]);
$key = 'login.' . $request->ip();
if (RateLimiter::tooManyAttempts($key, 5)) {
$seconds = RateLimiter::availableIn($key);
return response()->json([
'error' => 'Too many login attempts. Try again in ' . $seconds . ' seconds.'
], 429);
}
$user = User::where('email', $request->email)->first();
if (!$user || $user->login_attempts >= 5) {
RateLimiter::hit($key, 300); // 5 minutes lockout
return response()->json(['error' => 'Account locked due to multiple failed attempts'], 423);
}
if (!Hash::check($request->password, $user->password)) {
$user->incrementLoginAttempts();
RateLimiter::hit($key, 60);
return response()->json(['error' => 'Invalid credentials'], 401);
}
$token = Auth::guard('api')->login($user);
$user->resetLoginAttempts();
RateLimiter::clear($key);
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60,
'user' => $user->only(['id', 'name', 'email'])
]);
}
public function refresh()
{
try {
$token = auth('api')->refresh();
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60
]);
} catch (\Exception $e) {
return response()->json(['error' => 'Token cannot be refreshed'], 401);
}
}
public function logout()
{
auth('api')->logout();
return response()->json(['message' => 'Successfully logged out']);
}
}Security Middleware and Headers
Create middleware to add security headers and validate tokens:
headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
$response->headers->set('Content-Security-Policy', "default-src 'self'");
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
return $response;
}
}Environment Configuration
Add these security-focused environment variables:
# JWT Configuration
JWT_SECRET=your-super-secure-secret-key-here
JWT_TTL=60
JWT_REFRESH_TTL=20160
JWT_ALGO=HS256
JWT_BLACKLIST_ENABLED=true
JWT_BLACKLIST_GRACE_PERIOD=0
# Security
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=strictBest Practices Summary
- Short Token Expiry: Keep access tokens short-lived (15-60 minutes) and use refresh tokens for extended sessions
- Rate Limiting: Implement aggressive rate limiting on authentication endpoints
- Account Lockout: Lock accounts after multiple failed attempts
- Token Blacklisting: Always enable JWT blacklisting for proper logout functionality
- Security Headers: Add comprehensive security headers to prevent common attacks
- HTTPS Only: Never transmit JWTs over unencrypted connections
- Secure Storage: Store tokens securely on the client side (httpOnly cookies when possible)
- Token Validation: Always validate token structure, signature, and claims
Conclusion
Implementing secure JWT authentication requires attention to multiple security layers beyond just token generation. By following these practices—including proper rate limiting, account lockout mechanisms, security headers, and token management—you'll build a robust authentication system that protects both your application and users from common security threats.
Related Posts
Implementing Multi-Factor Authentication with JWT and Time-Based OTP in Node.js
Learn to build secure MFA using JWT tokens and time-based one-time passwords with practical Node.js implementation.
Implementing OAuth 2.0 Authentication with PKCE in Modern Web Applications
Learn how to implement secure OAuth 2.0 authentication with PKCE flow to protect your web applications from common security vulnerabilities.
Building a High-Performance Authentication System with JWT and Redis
Learn to implement secure JWT authentication with Redis for session management, refresh tokens, and blacklisting in Node.js applications.