makaveli / laravel-jwt-auth
JWT authentication for Laravel
Requires
- php: ^8.2
- laravel/framework: ^10.10|^11.0|^12.0
- makaveli/laravel-core: ^1.0.3
- makaveli/laravel-logger: ^1.1.4
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^8.0
- phpunit/phpunit: ^10.0
- predis/predis: ^2.0
README
🌍 Languages
- 🇺🇸 English (default)
- 🇷🇺 Русская версия
Table of Contents
- Introduction
- Requirements
- Installation
- Configuration
- Core Components
- Database Schema
- Quick Start
- Integration with BaseRepository
- Extending the Package
- Console Commands
- Recommendations
- Useful Links
Introduction
makaveli/laravel-jwt-auth is a lightweight, self‑contained JWT authentication package for Laravel. It provides generation, verification, refresh, and blacklisting of JWT tokens without relying on external libraries like tymon/jwt-auth or firebase/php-jwt. The package supports multiple storage drivers for blacklisting (memory, Redis, database) and a variety of algorithms (HS256, RS*, ES*), making it both flexible and easy to integrate into any Laravel project.
Key features:
- Generation of access/refresh token pairs.
- Support for HS256 (default), RS*, ES* algorithms.
- Automatic signature, header, and payload verification (exp, sub, name).
- Token blacklisting on refresh/logout with configurable TTL.
- Three storage drivers for blacklist:
memory(Laravel array cache),redis,database. - Fully tested with PHPUnit + Orchestra Testbench.
- Console commands to generate JWT secret and run migrations.
- Simple configuration via
config/jwt.php.
Requirements
- PHP 8.2 or higher
- Laravel 10.x, 11.x, or 12.x
- Composer
- (Optional) Redis extension or
predis/predisfor theredisdriver - (Optional) Database driver for the
databasedriver (SQLite, MySQL, etc.)
Installation
-
Install the package via Composer:
composer require makaveli/laravel-jwt-auth
-
(Optional) Publish the configuration file to customize settings:
php artisan vendor:publish --tag=jwt-config
This will copy the configuration file to
config/jwt.php. -
Run the package migrations (required only for the
databasedriver):php artisan migrate
The package automatically loads its migrations; you do not need to publish them.
Configuration
The configuration file config/jwt.php allows you to customize the following parameters:
| Parameter | Description | Default |
|---|---|---|
algo |
JWT algorithm (HS256, RS256, ES256, etc.) |
'HS256' |
private |
Secret key (or path to private key for RSA/ECDSA) | env('JWT_SECRET', 'test-secret-key') |
ttl |
Access token lifetime in minutes | env('JWT_TTL', 60) |
refresh_ttl |
Refresh token lifetime in minutes | env('JWT_REFRESH_TTL', 120) |
allow_infinite_ttl |
Allow infinite TTL (token never expires) | false |
infinite_ttl_fallback |
Fallback TTL in seconds if infinite is not allowed | 31536000 (1 year) |
sub_payload_field |
User model field used for the sub claim |
'email' |
name_payload_fields |
User model fields to include in the name claim |
['name'] |
user_model |
User model class | \Illuminate\Database\Eloquent\Model::class |
token_storage.driver |
Blacklist storage driver (memory, redis, database) |
env('JWT_TOKEN_STORAGE_DRIVER', 'memory') |
token_storage.storage_ttl |
Blacklist TTL in seconds | env('JWT_BLACKLIST_STORAGE_TTL', 86400 * 7) (7 days) |
Example configuration for production:
return [ 'algo' => env('JWT_ALGO', 'HS256'), 'private' => env('JWT_SECRET'), 'ttl' => env('JWT_TTL', 15), 'refresh_ttl' => env('JWT_REFRESH_TTL', 10080), 'allow_infinite_ttl' => false, 'infinite_ttl_fallback'=> 31536000, 'sub_payload_field' => 'email', 'name_payload_fields' => ['name', 'email'], 'user_model' => App\Models\User::class, 'token_storage' => [ 'driver' => env('JWT_TOKEN_STORAGE_DRIVER', 'redis'), 'storage_ttl' => env('JWT_BLACKLIST_STORAGE_TTL', 604800), ], ];
Recommended .env values:
JWT_ALGO=HS256 JWT_SECRET=your-very-long-random-secret JWT_TTL=15 JWT_REFRESH_TTL=10080 JWT_TOKEN_STORAGE_DRIVER=redis JWT_BLACKLIST_STORAGE_TTL=604800
Core Components
JWTAuth Trait
The main trait that provides high‑level methods for token operations. It can be used in any class (typically a service or controller). It internally uses the underlying services and repositories.
Methods:
fromUser($user): Generates an access/refresh token pair from a user model.verify($token): Verifies a token and returns the payload (or an array withverify_failflag).refreshToken($refreshToken): Issues a new token pair using a valid refresh token.logout($accessToken): Blacklists the access token.
Example:
use JWTAuth\JWTAuth; class AuthService { use JWTAuth; public function authenticate($credentials) { if (auth()->attempt($credentials)) { [$access, $refresh] = $this->fromUser(auth()->user()); return compact('access', 'refresh'); } return null; } }
Actions
The Actions namespace contains classes for blacklist operations:
AddToBlacklist: Adds a token to the blacklist.IsBlacklisted: Checks if a token is blacklisted.RemoveExpired: Removes expired tokens from storage (used by the scheduled job).
Services
Services encapsulate the write operations:
BlacklistService: Manages blacklist writes (used byAddToBlacklist).
Repositories
Repositories handle read operations:
BlacklistRepository: Checks if a token is blacklisted (used byIsBlacklisted).
Helpers
Low‑level JWT helpers:
JWTSlice: Encodes and decodes the JWT structure (header, payload, signature).JWTVerify: Verifies the signature and payload integrity.JWTCoder: Handles encoding/decoding with the chosen algorithm.
Token Storage
The blacklist storage is abstracted behind TokenStorageInterface. Three implementations are provided:
| Driver | Storage | Best for |
|---|---|---|
memory |
Laravel ArrayStore |
Testing, development |
redis |
Redis (setex + scan) | Production (recommended) |
database |
Table blacklisted_tokens |
When Redis is not available |
Switching drivers is done via config/jwt.php or the environment variable JWT_TOKEN_STORAGE_DRIVER.
Database Schema
When the database driver is used, the package creates the table blacklisted_tokens (or the name defined in the migration) with the following structure:
| Column | Type | Description |
|---|---|---|
id |
bigint (PK) | Auto‑increment ID |
token |
string | The JWT token (or its hash) |
expires_at |
timestamp | Token expiration time (used for cleanup) |
created_at |
timestamp | When it was blacklisted |
The table is automatically created when you run the Laravel migrations.
Quick Start
1. Configure the package
Publish and edit config/jwt.php to set your secret, TTLs, and storage driver. Generate a strong secret:
php artisan jwt:secret
2. Create an authentication service
use JWTAuth\JWTAuth; use App\Models\User; class AuthService { use JWTAuth; public function login($email, $password) { $user = User::where('email', $email)->first(); if (!$user || !password_verify($password, $user->password)) { return null; } [$access, $refresh] = $this->fromUser($user); return compact('access', 'refresh'); } }
3. Use the tokens in your controller
class AuthController extends Controller { protected $authService; public function __construct(AuthService $authService) { $this->authService = $authService; } public function login(Request $request) { $tokens = $this->authService->login($request->email, $request->password); if (!$tokens) { return response()->json(['message' => 'Invalid credentials'], 401); } return response()->json($tokens); } public function refresh(Request $request) { $newTokens = $this->authService->refreshToken($request->refresh_token); if (!$newTokens) { return response()->json(['message' => 'Invalid refresh token'], 401); } return response()->json($newTokens); } public function logout(Request $request) { $this->authService->logout($request->bearerToken()); return response()->json(['message' => 'Logged out']); } }
4. Protect routes with middleware
Create a middleware that verifies the access token and attaches the user to the request.
namespace App\Http\Middleware; use Closure; use JWTAuth\JWTAuth; class JwtAuthMiddleware { use JWTAuth; public function handle($request, Closure $next) { $token = $request->bearerToken(); if (!$token) { return response()->json(['message' => 'Token missing'], 401); } $payload = $this->verify($token); if ($payload['verify_fail'] ?? false) { return response()->json(['message' => 'Invalid or expired token'], 401); } $request->merge(['jwt_user' => $payload['user']]); return $next($request); } }
Integration with BaseRepository
While this package does not directly depend on makaveli/laravel-core, you can easily use it in your existing repository architecture. For example, you might inject the JWTAuth trait into a repository that handles authentication‑related data.
use JWTAuth\JWTAuth; class AuthRepository { use JWTAuth; public function createTokensForUser($user) { return $this->fromUser($user); } }
This keeps the token logic inside the repository, following the same pattern as other makaveli packages.
Extending the Package
You can extend the package in several ways:
- New storage driver: Implement
JWTAuth\Interfaces\TokenStorageInterfaceand register it inTokenStorageFactory. - Different algorithm: Extend
JWTSliceandJWTAlgoto support custom signing methods. - Custom payload fields: Override the
getJWTPayloadmethod in your own trait or service to add custom claims. - Audit logging: Use Laravel events or observers on the
BlacklistedTokenmodel (if using the database driver) to log token blacklist events.
Console Commands
| Command | Description |
|---|---|
php artisan jwt:secret |
Generates a new random secret key and updates the .env file. |
php artisan jwt:clear-tokens |
Removes expired tokens from the blacklist (should be scheduled daily). |
To schedule the cleanup, add the following to App\Console\Kernel:
protected function schedule(Schedule $schedule) { $schedule->command('jwt:clear-tokens')->daily(); }
Recommendations
- Use a strong, random secret and rotate it occasionally.
- Choose the appropriate storage driver –
redisis recommended for production,databaseif you don’t have Redis. - Keep access token TTL short (e.g., 15 minutes) for security, and refresh token TTL longer (e.g., 7 days).
- Always validate the
subclaim and compare it with the actual user in your database. - Implement token revocation (logout) by blacklisting the access token.
- Schedule the token cleanup to keep your blacklist storage lean.
Useful Links
- Package repository: https://github.com/Ma1kaveli/laravel-jwt-auth
- Dependencies:
- makaveli/laravel-core (optional, but recommended)
- makaveli/laravel-query-builder (optional)