citizen63000/easy-api-jwt-authentication
Lightweight Laravel package for simple JWT-based API authentication. Adds helpers to issue and validate tokens for protected routes and user sessions, aiming to reduce boilerplate when securing APIs with JSON Web Tokens.
Installation:
composer require citizen63000/easy-api-jwt-authentication
Publish the package configuration and migrations:
php artisan vendor:publish --provider="Citizen63000\EasyApiJwtAuthentication\EasyApiJwtAuthenticationServiceProvider"
php artisan migrate
Configuration:
Update .env with JWT settings (e.g., JWT_SECRET, JWT_TTL). The package provides defaults in config/easy-api-jwt-authentication.php.
First Use Case:
use Citizen63000\EasyApiJwtAuthentication\Facades\JwtAuth;
$token = JwtAuth::login(['email' => 'user@example.com', 'password' => 'password']);
routes/api.php:
Route::middleware(['auth:api'])->group(function () {
Route::get('/user', function () {
return response()->json(auth()->user());
});
});
Testing:
Use the provided AuthenticationTestTrait in tests/Feature/AuthTest.php:
use Citizen63000\EasyApiJwtAuthentication\Tests\AuthenticationTestTrait;
class AuthTest extends TestCase
{
use AuthenticationTestTrait;
public function test_login()
{
$this->testRefreshToken();
}
}
Authentication Flow:
JwtAuth::login() with user credentials.JwtAuth::refresh() for token rotation.JwtAuth::logout() to invalidate tokens (if using blacklist).API Integration:
// config/auth.php
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
namespace App\Http\Middleware;
use Citizen63000\EasyApiJwtAuthentication\Facades\JwtAuth;
use Closure;
class AddCustomClaims
{
public function handle($request, Closure $next)
{
JwtAuth::setCustomClaims(['role' => auth()->user()->role]);
return $next($request);
}
}
Validation:
use Citizen63000\EasyApiJwtAuthentication\Requests\LoginRequest;
Route::post('/login', [LoginRequest::class, 'handle']);
Rate Limiting:
Route::middleware(['throttle:60,1'])->group(function () {
Route::post('/login', [LoginRequest::class, 'handle']);
});
Multi-Tenant JWTs:
JwtAuth::generateToken() to include tenant ID:
$token = JwtAuth::generateToken([
'user_id' => auth()->id(),
'tenant_id' => request()->tenant_id,
]);
Social Logins:
LoginRequest to handle OAuth tokens:
public function authorize()
{
return $this->validate([
'token' => 'required|string',
'provider' => 'required|in:google,github',
]);
}
Event Listeners:
// EventServiceProvider
protected $listen = [
\Citizen63000\EasyApiJwtAuthentication\Events\TokenGenerated::class => [
\App\Listeners\LogTokenActivity::class,
],
];
Testing:
$this->actingAsUser($user, 'api')
->withHeaders(['Authorization' => 'Bearer ' . $token]);
Token Blacklisting:
JwtAuth::logout(), ensure the blacklist table exists and is indexed. Without it, tokens won’t be revoked.php artisan migrate after publishing the package.Clock Skew:
JwtAuth::setLeeway():
JwtAuth::setLeeway(30); // 30 seconds
Password Hashing:
bcrypt hashing. If using argon2, update the users table migration:
$table->string('password')->nullable()->change();
CORS Issues:
Authorization: Bearer may be blocked by CORS. Ensure your frontend includes:
fetch('/api/user', {
headers: {
'Authorization': 'Bearer ' + token,
'Accept': 'application/json',
},
credentials: 'include',
});
PHP 8 Strict Types:
strict_types=1 in composer.json, ensure all methods returning array or mixed are updated to use PHP 8 return types (e.g., array<int, string>).Token Validation Errors:
config/easy-api-jwt-authentication.php:
'debug' => env('APP_DEBUG', false),
TokenInvalidException or TokenExpiredException.Middleware Order:
auth:api middleware runs after throttle and before bindings:
Route::middleware(['throttle:60,1', 'auth:api'])->group(...);
Database Locks:
lock wait timeout on the blacklist table. Add indexes:
Schema::table('blacklist', function (Blueprint $table) {
$table->index('token');
});
Custom Token Storage:
JwtAuth::storeToken() to use Redis or DynamoDB:
public function storeToken($token, $user)
{
Redis::set("jwt:{$user->id}", $token, 'EX', 24 * 60 * 60);
}
Dynamic TTL:
$ttl = auth()->user()->role === 'admin' ? 86400 : 3600; // 1 day vs 1 hour
JwtAuth::setTtl($ttl);
Algorithm Flexibility:
HS256 to RS256 (RSA) for better security:
'algorithm' => 'RS256',
'private_key' => storage_path('app/rsa_private.pem'),
'public_key' => storage_path('app/rsa_public.pem'),
Event Customization:
TokenGenerated event to add metadata:
class CustomTokenGenerated extends TokenGenerated
{
public function __construct($token, $user, $customData = [])
{
parent::__construct($token, $user);
$this->customData = $customData;
}
}
Environment Variables:
JWT_SECRET to be 32+ characters for HS256. Use:
openssl rand -hex 32
RS256, generate keys with:
openssl genpkey -algorithm RSA -out rsa_private.pem -pkeyopt rsa_keygen_bits:2048
openssl pkey -in rsa_private.pem -pubout -out rsa_public.pem
Caching:
JWT_CACHE if using Redis for token storage to avoid double storage.Logging:
JWT_LOG_FAILED_ATTEMPTS to track brute-force attempts:
'log_failed_attempts' => env('JWT_LOG_FAILED', false),
iat, nbf):
JwtAuth::setClaims(['sub' => $user->id, 'role' => $user->role]);
2
How can I help you explore Laravel packages today?