Building a Complete Authentication System with Laravel 11 and JWT
Introduction
Authentication is the backbone of any secure application. With Laravel 11's streamlined architecture and JWT (JSON Web Tokens), we can build a robust authentication system that's both secure and scalable. This guide will walk you through implementing a complete JWT authentication system with refresh tokens, role-based access control, and security best practices.
Why JWT for API Authentication?
JWT offers several advantages over traditional session-based authentication:
- Stateless: No server-side session storage required
- Scalable: Perfect for microservices and distributed systems
- Cross-domain: Works seamlessly across different domains
- Mobile-friendly: Ideal for mobile app backends
Setting Up Laravel 11 with JWT
First, let's install the necessary packages:
composer require tymon/jwt-auth
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secretUpdate your config/auth.php:
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],Preparing the User Model
Update your User model to implement the JWT contract:
getKey();
}
public function getJWTCustomClaims()
{
return [
'role' => $this->role,
'email' => $this->email
];
}
}Creating the Authentication Controller
Let's build a comprehensive authentication controller:
all(), [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
'role' => 'nullable|string|in:admin,user'
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'errors' => $validator->errors()
], 422);
}
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => $request->role ?? 'user'
]);
$token = Auth::login($user);
$refreshToken = $this->createRefreshToken($user);
return response()->json([
'success' => true,
'user' => $user,
'authorization' => [
'token' => $token,
'refresh_token' => $refreshToken,
'type' => 'bearer'
]
], 201);
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
if (!$token = Auth::attempt($credentials)) {
return response()->json([
'success' => false,
'message' => 'Invalid credentials'
], 401);
}
$user = Auth::user();
$refreshToken = $this->createRefreshToken($user);
return response()->json([
'success' => true,
'user' => $user,
'authorization' => [
'token' => $token,
'refresh_token' => $refreshToken,
'type' => 'bearer'
]
]);
}
public function refresh(Request $request)
{
try {
$refreshToken = $request->refresh_token;
// Verify refresh token (implement your own logic)
$user = $this->validateRefreshToken($refreshToken);
if (!$user) {
return response()->json([
'success' => false,
'message' => 'Invalid refresh token'
], 401);
}
$newToken = Auth::login($user);
$newRefreshToken = $this->createRefreshToken($user);
return response()->json([
'success' => true,
'authorization' => [
'token' => $newToken,
'refresh_token' => $newRefreshToken,
'type' => 'bearer'
]
]);
} catch (JWTException $e) {
return response()->json([
'success' => false,
'message' => 'Token refresh failed'
], 500);
}
}
public function logout()
{
Auth::logout();
return response()->json([
'success' => true,
'message' => 'Successfully logged out'
]);
}
private function createRefreshToken($user)
{
// Simple refresh token implementation
// In production, store this in database with expiration
return base64_encode($user->id . '|' . time() . '|' . uniqid());
}
private function validateRefreshToken($token)
{
// Implement proper refresh token validation
// Check database, expiration, etc.
$decoded = base64_decode($token);
$parts = explode('|', $decoded);
if (count($parts) !== 3) {
return null;
}
return User::find($parts[0]);
}
}Setting Up Routes and Middleware
Define your authentication routes in routes/api.php:
'auth'], function () {
Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
Route::post('refresh', [AuthController::class, 'refresh']);
Route::middleware('auth:api')->group(function () {
Route::post('logout', [AuthController::class, 'logout']);
Route::get('me', function () {
return response()->json([
'success' => true,
'user' => auth()->user()
]);
});
});
});
// Protected routes with role-based access
Route::middleware(['auth:api', 'role:admin'])->group(function () {
Route::get('admin/users', function () {
return User::all();
});
});Implementing Role-Based Middleware
Create a role-checking middleware:
php artisan make:middleware RoleMiddlewareuser() || !in_array($request->user()->role, $roles)) {
return response()->json([
'success' => false,
'message' => 'Insufficient permissions'
], 403);
}
return $next($request);
}
}Register the middleware in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->alias([
'role' => \App\Http\Middleware\RoleMiddleware::class,
]);
})Security Best Practices
To enhance security, consider these practices:
- Token Expiration: Set reasonable JWT expiration times
- Refresh Token Rotation: Generate new refresh tokens on each use
- Rate Limiting: Implement rate limiting on auth endpoints
- HTTPS Only: Always use HTTPS in production
- Token Blacklisting: Implement token blacklisting for logout
Testing Your Authentication
Test your endpoints using tools like Postman or curl:
# Register
curl -X POST http://localhost:8000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@example.com","password":"password123","password_confirmation":"password123"}'
# Login
curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"john@example.com","password":"password123"}'
# Access protected route
curl -X GET http://localhost:8000/api/auth/me \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Conclusion
You now have a complete JWT authentication system in Laravel 11 with user registration, login, token refresh, and role-based access control. This foundation provides the security and flexibility needed for modern web applications and APIs. Remember to implement proper refresh token storage, add comprehensive error handling, and follow security best practices for production deployment.
Related Posts
Building Lightning-Fast APIs with Go and Fiber: A Practical Guide
Learn how to build high-performance REST APIs using Go and the Fiber framework with practical examples and best practices.
Building Secure Authentication with Laravel Sanctum and React: A Complete Guide
Learn how to implement secure SPA authentication using Laravel Sanctum with React, including token management and CSRF protection.
Implementing Rate Limiting in Node.js APIs: Protect Your Backend from Abuse
Learn how to implement effective rate limiting strategies in Node.js to protect your APIs from abuse and ensure optimal performance.