A Laravel package for easy implementation of JWT (JSON Web Token) authentication. Fully compliant with JWT standard (RFC 7519).
- 🔐 JWT Standard (RFC 7519) Compliant
- 🔄 Automatic Token Refresh with Grace Period
- 🎯 Custom User Properties Support (props)
- 🚀 Seamless Integration with Laravel Auth System
Install the package via composer:
composer require jskorlol/laravel-jwt-authPublish the configuration file:
php artisan vendor:publish --provider="Jskorlol\JwtAuth\JwtAuthServiceProvider"Add the JWT secret key to your .env file:
JWT_SECRET_KEY=your-secret-key-hereImplement JwtUserInterface and use JwtTrait in your User model:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Jskorlol\JwtAuth\Interfaces\JwtUserInterface;
use Jskorlol\JwtAuth\Traits\JwtTrait;
class User extends Authenticatable implements JwtUserInterface
{
use JwtTrait;
// Optional: Define custom properties to include in JWT payload
public function getJwtProps(): array
{
return [
'name' => $this->name,
'email' => $this->email,
'role' => $this->role,
// Add any other user properties you need in the token
];
}
}Add the JWT guard to your config/auth.php file:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],This example shows how to implement a standard email/password login endpoint:
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AuthController extends Controller
{
public function login(Request $request): JsonResponse
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!auth()->attempt($credentials)) {
return response()->json(['message' => 'Invalid credentials'], 401);
}
$user = auth()->user();
$tokenData = $user->generateJwt();
return response()->json([
'success' => true,
'data' => [
'token' => $tokenData->accessToken,
'expires_at' => $tokenData->expiresAt,
'refresh_token' => $tokenData->refreshToken,
],
]);
}
}Integrate social login providers (Google, Facebook, GitHub, etc.) with JWT authentication:
When using Laravel Socialite with API routes (JWT authentication), you MUST include the stateless() method:
// ✅ Correct for API routes
Socialite::driver($provider)->stateless()->redirect();
Socialite::driver($provider)->stateless()->user();
// ❌ Wrong for API routes - will cause session errors
Socialite::driver($provider)->redirect();
Socialite::driver($provider)->user();The stateless() method disables session state verification, which is required for API routes since they don't use sessions.
Example for API routes (routes/api.php):
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
class OAuthController extends Controller
{
public function redirectToProvider($provider)
{
return Socialite::driver($provider)->stateless()->redirect();
}
public function handleProviderCallback($provider): JsonResponse
{
$providerUser = Socialite::driver($provider)->stateless()->user();
// Find or create user based on OAuth provider data
$user = User::firstOrCreate([
'provider_id' => $providerUser->getId(),
'provider' => $provider,
], [
'email' => $providerUser->getEmail(),
'name' => $providerUser->getName() ?? $providerUser->getNickname(),
'nickname' => Str::random(16),
]);
// Generate JWT token for the authenticated user
$tokenData = $user->generateJwt();
return response()->json([
'success' => true,
'data' => [
'token' => $tokenData->accessToken,
'expires_at' => $tokenData->expiresAt,
'refresh_token' => $tokenData->refreshToken,
],
]);
}
}Use the auth:api middleware to protect your API endpoints. This middleware will validate the JWT token and authenticate the user:
use Illuminate\Support\Facades\Route;
// Protected routes - requires valid JWT token
Route::middleware('auth:api')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::get('/profile', [ProfileController::class, 'show']);
Route::put('/profile', [ProfileController::class, 'update']);
Route::post('/logout', [AuthController::class, 'logout']);
});
// Public routes - no authentication required
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);
Route::post('/refresh', [AuthController::class, 'refresh']);
// OAuth routes
Route::get('/auth/{provider}', [OAuthController::class, 'redirectToProvider']);
Route::get('/auth/{provider}/callback', [OAuthController::class, 'handleProviderCallback']);Implement token refresh functionality to get a new access token using a refresh token:
use Jskorlol\JwtAuth\Services\AuthService;
use Jskorlol\JwtAuth\Exceptions\InvalidRefreshToken;
public function refresh(Request $request): JsonResponse
{
$refreshToken = $request->input('refresh_token');
if (!$refreshToken) {
return response()->json(['message' => 'Refresh token required'], 400);
}
try {
$tokenData = app(AuthService::class)->refreshAccessToken($refreshToken);
return response()->json([
'token' => $tokenData->accessToken,
'expires_at' => $tokenData->expiresAt,
'refresh_token' => $tokenData->refreshToken,
]);
} catch (InvalidRefreshToken $e) {
return response()->json(['message' => 'Invalid refresh token'], 401);
}
}When user information changes, you need to refresh the JWT token to include updated properties:
use Jskorlol\JwtAuth\Facades\JwtAuth;
// Update user profile
$user->update(['name' => 'New Name']);
// Generate new JWT with updated props
$newToken = JwtAuth::refreshToken();
return response()->json([
'message' => 'Profile updated successfully',
'data' => [
'token' => $newToken,
],
]
]);This package provides automatic token refresh functionality to seamlessly renew tokens before or after expiration.
Add the middleware to your API routes in bootstrap/app.php:
use Jskorlol\JwtAuth\Middleware\AutoRefreshedTokenMiddleware;
->withMiddleware(function (Middleware $middleware) {
$middleware->api(append: [
AutoRefreshedTokenMiddleware::class,
]);
})Configure auto-refresh behavior in config/jwt-auth.php:
'auto_refresh' => [
'enabled' => true,
'grace_period' => 3600, // Allow refresh up to 1 hour after expiration
'cache_ttl' => 10, // Cache refreshed tokens for 10 seconds
'preemptive_refresh' => [
'enabled' => true,
'threshold' => 600, // Start refreshing 10 minutes before expiration
],
],- Preemptive Refresh: When a token is close to expiration (within threshold), a new token is automatically generated
- Grace Period: Even after expiration, tokens can be refreshed within the grace period
- Response Header: New tokens are returned in the
X-Refreshed-Tokenresponse header - Caching: Refreshed tokens are cached to prevent multiple refreshes for the same request
When auto-refresh is enabled, clients MUST check for the X-Refreshed-Token header in every response. If present, the client must immediately replace their current Bearer token with the new token provided in this header.
// Example: Axios interceptor for automatic token update
axios.interceptors.response.use(
(response) => {
const refreshedToken = response.headers['x-refreshed-token'];
if (refreshedToken) {
// Update stored token
localStorage.setItem('auth_token', refreshedToken);
// Update axios default header
axios.defaults.headers.common['Authorization'] = `Bearer ${refreshedToken}`;
}
return response;
}
);Example of JWT payload generated by this package:
{
"jti": "1:moySfYsHMzrDSJC7Qxfy4sWpaSdYUSOi",
"iat": 1749997727,
"exp": 1749997757,
"sub": "1",
"props": {
"name": "John Doe",
"email": "john@example.com",
"role": "admin"
}
}This package includes artisan commands for testing JWT functionality:
# Test JWT generation with current configuration
php artisan jwt:test
# Decode and verify an existing JWT token
php artisan jwt:test "your.jwt.token"
# Test refresh token functionality
php artisan jwt:test-refresh "your-refresh-token"These commands are useful for:
- Verifying your JWT configuration
- Debugging token issues
- Testing token generation and validation
// Generate token with custom lifetime (in seconds)
$tokenData = $user->generateJwt(lifetime: 7200); // 2 hoursSupported algorithms in config/jwt-auth.php:
'algorithm' => 'HS256', // Options: HS256, HS384, HS512Refresh tokens are stored using Laravel's cache system. Configure your preferred cache driver in .env:
CACHE_DRIVER=redis # Recommended for production- PHP 8.4+
- Laravel 10.x, 11.x, or 12.x
The MIT License (MIT). See LICENSE.md for more information.