wazobia / laravel-auth-guard
JWT and Project authentication middleware for Laravel with JWKS support
Requires
- php: ^8.0
- firebase/php-jwt: ^7.0
- guzzlehttp/guzzle: ^7.0
- illuminate/cache: ^9.0|^10.0|^11.0|^12.0
- illuminate/http: ^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^9.0|^10.0|^11.0|^12.0
- phpseclib/phpseclib: ^3.0
- predis/predis: ^2.0
Requires (Dev)
- orchestra/testbench: ^7.0|^8.0
- phpunit/phpunit: ^9.5|^10.0
Suggests
- nuwave/lighthouse: Required for GraphQL support (^6.0)
- predis/predis: Required for Redis caching support (^2.0)
This package is not auto-updated.
Last update: 2026-03-11 17:43:07 UTC
README
Enterprise-grade JWT and Project authentication middleware for Laravel applications
Installation โข Configuration โข Usage โข GraphQL Support โข Documentation
๐ฏ Complete Feature Parity with Node.js Implementation
Mercury GraphQL Integration
- โ ServiceAuthService - Complete CLIENT_ID/CLIENT_SECRET โ Mercury GraphQL integration
- โ generateToken() - Service token generation using Mercury API
- โ getServiceById() - Dynamic service UUID lookup from Mercury
- โ
Proper Service Validation - Validates service is in
enabled_services[]
Advanced JWKS Management
- โ
Per-tenant JWKS caching -
jwks_cache:{tenantId}pattern - โ Service JWKS endpoint - Separate endpoint for service tokens
- โ Auto-refresh on key miss - Fetches fresh JWKS when key not found
- โ Signature-based authentication - Mercury API authentication
Redis Connection Management
- โ Graceful fallback - Works with or without Redis
- โ Health checking - Automatic reconnection on failures
- โ Per-tenant secret versioning - Cached secret version validation
Complete GraphQL Directive Suite
- โ @userAuth - JWT user authentication with optional scopes
- โ @projectAuth - Platform/project token authentication with scopes
- โ @serviceAuth - Service-only authentication (CLIENT_ID/CLIENT_SECRET tokens only)
- โ @combineAuth - Dual authentication (User JWT + Platform token required)
- โ @scopes - Standalone granular permission validation
Configuration Management
- โ
Comprehensive config file -
config/auth-guard.php - โ Environment fallbacks - Config โ env โ defaults
- โ Laravel standards - Proper service provider, middleware registration
๐ฏ Features
- JWT User Authentication - Secure user authentication with RS512 algorithm and scope validation
- Platform Token Authentication - HMAC-based platform/project token validation with tenant isolation
- Service Authentication - CLIENT_ID/CLIENT_SECRET authentication for service-to-service communication
- Combined Authentication - Support for dual authentication (JWT + Platform/Project token)
- Comprehensive Scope System - Granular permission validation with scope inheritance and combination
- JWKS Support - Automatic public key rotation and caching with per-tenant isolation
- GraphQL First - Complete Lighthouse GraphQL integration with dedicated directives
- Redis-Powered - Fast token validation, caching, and revocation with Redis
- Mercury Integration - Full integration with Mercury GraphQL API for token management
- Docker-Ready - Seamless operation in containerized environments
- Laravel Standards - Follows Laravel conventions with service provider auto-discovery
๐ Table of Contents
- Requirements
- Installation
- Configuration
- GraphQL Setup
- Usage
- Testing
- Troubleshooting
- Advanced Usage
- Support
โ๏ธ Requirements
| Requirement | Version |
|---|---|
| PHP | ^8.0 |
| Laravel | ^9.0 | ^10.0 | ^11.0 | ^12.0 |
| Redis | Latest |
| Predis or PhpRedis | Latest |
| Lighthouse GraphQL | ^6.0 | ^7.0 (optional) |
๐ฆ Installation
Step 1: Install the Package
composer require wazobia/laravel-auth-guard
The service provider will be automatically registered via Laravel's package discovery.
Step 2: Install Redis Client
Option A: Predis (PHP Redis client)
composer require predis/predis
Option B: PhpRedis Extension (Better Performance)
# Ubuntu/Debian sudo apt-get install php-redis # Alpine Linux (Docker) apk add php81-pecl-redis # macOS pecl install redis
Step 3: Publish Configuration
php artisan vendor:publish --tag=auth-guard-config
This creates config/auth-guard.php in your project.
๐ง Configuration
Environment Variables
Add these mandatory variables to your .env file:
# Mercury JWKS Service (REQUIRED) MERCURY_BASE_URL=https://mercury.example.com SIGNATURE_SHARED_SECRET=your_shared_secret_key # Service Authentication (REQUIRED) CLIENT_ID=your-service-client-id CLIENT_SECRET=your-service-client-secret # Redis Authentication Database (REQUIRED) REDIS_AUTH_URL=redis://localhost:6379/5 # JWT Algorithm (REQUIRED) JWT_ALGORITHM=RS512
Optional Environment Variables:
# Mercury Configuration MERCURY_TIMEOUT=10 SIGNATURE_ALGORITHM=sha256 # Redis Standard Configuration REDIS_CLIENT=predis REDIS_URL=redis://localhost:6379/0 REDIS_HOST=redis REDIS_PORT=6379 REDIS_PASSWORD=null REDIS_DB=0 # Cache Settings CACHE_EXPIRY_TIME=900 AUTH_CACHE_TTL=900 AUTH_CACHE_PREFIX=auth_guard # Custom Headers AUTH_JWT_HEADER=Authorization AUTH_PROJECT_TOKEN_HEADER=x-project-token
Cache Settings
CACHE_EXPIRY_TIME=900 AUTH_CACHE_TTL=900 AUTH_CACHE_PREFIX=auth_guard AUTH_CACHE_DRIVER=redis
JWT Settings
JWT_ALGORITHM=RS512 JWT_LEEWAY=0
Custom Headers (Optional)
AUTH_JWT_HEADER=Authorization AUTH_PROJECT_TOKEN_HEADER=x-project-token
Logging
AUTH_GUARD_LOGGING=true AUTH_GUARD_LOG_CHANNEL=stack
> **๐ก Docker Users:** If using Docker Compose, set `REDIS_HOST=redis` (the service name), not `127.0.0.1`
### Redis Setup
Update `config/database.php`:
```php
<?php
return [
// ... other config
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
'auth' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
'prefix' => '', // No prefix!
],
],
];
Verify Redis Connection
php artisan tinker
Test inside Tinker:
Redis::ping(); // Should return: "+PONG" Redis::set('test', 'Hello'); Redis::get('test'); // Should return: "Hello" exit
Service Provider
If not using auto-discovery, add to config/app.php:
'providers' => [ // ... Wazobia\LaravelAuthGuard\AuthGuardServiceProvider::class, ],
๐จ GraphQL Setup (Lighthouse)
Step 1: Install Lighthouse
composer require nuwave/lighthouse
Step 2: Configure Directives
Edit config/lighthouse.php and add the directive namespace:
<?php return [ 'namespaces' => [ 'models' => ['App', 'App\\Models'], 'queries' => 'App\\GraphQL\\Queries', 'mutations' => 'App\\GraphQL\\Mutations', 'subscriptions' => 'App\\GraphQL\\Subscriptions', 'interfaces' => 'App\\GraphQL\\Interfaces', 'unions' => 'App\\GraphQL\\Unions', 'scalars' => 'App\\GraphQL\\Scalars', 'directives' => [ 'App\\GraphQL\\Directives', 'Wazobia\\LaravelAuthGuard\\GraphQL\\Directives', // โ Add this line ], ], ];
Step 3: Clear All Caches
php artisan cache:clear php artisan config:clear php artisan route:clear php artisan lighthouse:clear-cache composer dump-autoload
Step 4: Clear Caches and Validate
php artisan lighthouse:clear-cache php artisan lighthouse:validate-schema
๐ Usage
REST API Routes
Create routes in routes/api.php:
<?php use Illuminate\Support\Facades\Route; use Illuminate\Http\Request; // Public route (no authentication) Route::get('/public', function () { return ['message' => 'Public endpoint']; }); // JWT Authentication only Route::middleware('auth.jwt')->group(function () { Route::get('/user/profile', function (Request $request) { $user = $request->user(); return [ 'uuid' => $user->uuid, 'email' => $user->email, 'name' => $user->name, 'permissions' => $user->permissions ?? [], ]; }); }); // Project Authentication only Route::middleware('auth.project')->group(function () { Route::get('/project/info', function (Request $request) { $authProject = $request->get('auth_project', []); return [ 'project_uuid' => $authProject['project_uuid'] ?? null, 'tenant_id' => $authProject['tenant_id'] ?? null, 'scopes' => $authProject['scopes'] ?? [], ]; }); }); // Combined Authentication (JWT + Project) Route::middleware(['auth.jwt', 'auth.project'])->group(function () { Route::post('/secure/resource', function (Request $request) { return [ 'user' => $request->user(), 'project' => $request->get('auth_project', []), 'message' => 'Both authentications passed' ]; }); });
GraphQL Schema
Create or update graphql/schema.graphql:
type Query { # Public query (no authentication) hello: String! # JWT user authentication required me: User! @userAuth userProfile: User! @userAuth(scopes: ["users:read"]) # Project/platform token authentication required projectInfo: Project! @projectAuth(scopes: ["projects:manage"]) # Service-only authentication required serviceStatus: ServiceInfo! @serviceAuth(scopes: ["services:read"]) # Dual authentication (User + Project) required secureData: SecureData! @combineAuth(scopes: ["users:read", "projects:manage"]) } type Mutation { # User mutations updateProfile(name: String!): User! @userAuth(scopes: ["users:update"]) # Project mutations updateProjectSettings(settings: JSON!): Project! @projectAuth(scopes: ["projects:manage"]) # Service mutations updateServiceConfig(config: JSON!): ServiceInfo! @serviceAuth(scopes: ["services:manage"]) # Combined authentication mutations createSecureResource(data: JSON!): Resource! @combineAuth(scopes: ["users:create", "projects:manage"]) } type User { uuid: ID! email: String! name: String! permissions: [String!]! } type Project { project_uuid: ID! tenant_id: ID! enabled_services: [String!]! scopes: [String!]! } type ServiceInfo { service_name: String! client_id: String! scopes: [String!]! is_active: Boolean! } type SecureData { id: ID! content: String! user: User! project: Project! created_at: String! } type Resource { id: ID! data: JSON owner: User! project: Project! }
GraphQL Resolvers
app/GraphQL/Queries/Me.php
<?php namespace App\GraphQL\Queries; class Me { public function __invoke($rootValue, array $args, $context) { $user = $context->user; $authUser = $context->request->get('auth_user', []); return [ 'uuid' => $user->uuid ?? $authUser['uuid'] ?? null, 'email' => $user->email ?? $authUser['email'] ?? null, 'name' => $user->name ?? $authUser['name'] ?? null, 'permissions' => $authUser['permissions'] ?? [], ]; } }
app/GraphQL/Queries/ProjectInfo.php
<?php namespace App\GraphQL\Queries; class ProjectInfo { public function __invoke($rootValue, array $args, $context) { $authProject = $context->request->get('auth_project', []); $authPlatform = $context->request->get('auth_platform', []); $projectData = $authProject ?: $authPlatform; return [ 'project_uuid' => $projectData['project_uuid'] ?? null, 'tenant_id' => $projectData['tenant_id'] ?? null, 'scopes' => $projectData['scopes'] ?? [], ]; } }
app/GraphQL/Queries/ServiceStatus.php
<?php namespace App\GraphQL\Queries; class ServiceStatus { public function __invoke($rootValue, array $args, $context) { $authService = $context->request->get('auth_service', []); return [ 'service_name' => $authService['service_name'] ?? 'unknown', 'client_id' => $authService['client_id'] ?? 'unknown', 'scopes' => $authService['scopes'] ?? [], 'is_active' => !empty($authService), ]; } }
๐งช Testing
The package includes a comprehensive test suite with working GraphQL test endpoints:
Built-in Test Endpoints
The package provides ready-to-use test resolvers in app/GraphQL/Queries/TestAuth.php:
# User Authentication Tests query { testUserAuth } # Basic JWT auth query { testUserAuthWithScopes } # JWT with scope validation (@userAuth(scopes: ["projects:manage"])) # Project/Platform Authentication Tests query { testProjectAuth } # Basic platform token auth query { testProjectAuthWithScopes } # Platform token with scopes # Service Authentication Tests query { testServiceAuth } # Basic service token auth query { testServiceAuthWithScopes } # Service token with scopes (@serviceAuth(scopes: ["services:read"])) # Combined Authentication Tests query { testCombineAuth } # Dual auth (User + Platform) query { testCombineAuthWithScopes } # Dual auth with combined scope validation # Scope Tests query { testScopes } # Standalone scope validation (@scopes(scopes: ["users:read", "projects:manage"]))
Test Scripts
The package includes ready-to-run test scripts:
# Generate service token and test @serviceAuth ./tests/test_service_auth.sh # Test service authentication with scope validation ./tests/test_service_scopes.sh # Test combined authentication scenarios ./tests/test_combine_auth.sh # Test all authentication directives ./tests/test_all_auth.sh
REST API with cURL
JWT User Authentication
curl -X GET http://localhost:8000/api/user/profile \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Accept: application/json"
Project/Platform Authentication
curl -X GET http://localhost:8000/api/project/info \ -H "x-project-token: Bearer YOUR_PLATFORM_TOKEN" \ -H "Accept: application/json"
Service Authentication
curl -X GET http://localhost:8000/api/service/status \ -H "x-project-token: Bearer YOUR_SERVICE_TOKEN" \ -H "Accept: application/json"
Combined Authentication
curl -X POST http://localhost:8000/api/secure/resource \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "x-project-token: Bearer YOUR_PLATFORM_TOKEN" \ -H "Content-Type: application/json" \ -d '{"data": "test"}'
GraphQL Queries
JWT User Authentication
curl -X POST http://localhost:8000/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -d '{"query":"query { me { uuid email name permissions } }"}'
Project/Platform Token Authentication
curl -X POST http://localhost:8000/graphql \ -H "Content-Type: application/json" \ -H "x-project-token: Bearer YOUR_PLATFORM_TOKEN" \ -d '{"query":"query { projectInfo { project_uuid tenant_id scopes } }"}'
Service Authentication
curl -X POST http://localhost:8000/graphql \ -H "Content-Type: application/json" \ -H "x-project-token: Bearer YOUR_SERVICE_TOKEN" \ -d '{"query":"query { serviceStatus { service_name client_id scopes is_active } }"}'
Combined Authentication (User + Platform)
curl -X POST http://localhost:8000/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "x-project-token: Bearer YOUR_PLATFORM_TOKEN" \ -d '{"query":"query { secureData { id content user { uuid email } project { project_uuid } } }"}'
GraphQL Playground
- Access GraphQL Playground at
http://localhost:8000/graphql-playground - Add headers in the bottom left:
{
"Authorization": "Bearer YOUR_JWT_TOKEN",
"x-project-token": "Bearer YOUR_PROJECT_TOKEN"
}
- Run queries:
# Test JWT Authentication query TestUser { me { uuid email name permissions } } # Test Project Authentication query TestProject { projectInfo { project_uuid tenant_id scopes } } # Test Service Authentication query TestService { serviceStatus { service_name client_id scopes is_active } } # Test Combined Authentication query TestCombined { secureData { id content user { uuid email } project { project_uuid tenant_id } } }
๐ Troubleshooting
โ "No directive found for jwtAuth"
Solution:
- Add directive namespace to
config/lighthouse.php - Clear all caches:
php artisan config:clear php artisan lighthouse:clear-cache composer dump-autoload
โ "Class Predis\Client not found"
Solution:
composer require predis/predis php artisan config:clear
Or change .env:
REDIS_CLIENT=phpredis
โ "Could not connect to Redis"
Solution:
- Verify Redis is running:
redis-cli ping # Should return: PONG
- Check your
.env:
# For Docker REDIS_HOST=redis # For local REDIS_HOST=127.0.0.1
- Test connection:
php artisan tinker
Redis::ping();
โ "JWKS endpoint returned 401"
Solution:
Check that SIGNATURE_SHARED_SECRET in .env matches your Mercury service configuration.
โ "Token has been revoked or expired"
Solution:
The project token is either:
- Not found in Redis (expired)
- Manually revoked
Generate a new project token from your provisioning service.
Docker-Specific Issues
Redis Connection Refused
Update docker-compose.yml:
services: app: depends_on: - redis environment: - REDIS_HOST=redis redis: image: redis:alpine ports: - "6379:6379"
PhpRedis Not Installed
Add to your Dockerfile:
RUN pecl install redis && docker-php-ext-enable redis
Then rebuild:
docker-compose build --no-cache docker-compose up -d
๐ฅ Advanced Usage
Programmatic Token Validation
use Wazobia\LaravelAuthGuard\Services\JwtAuthService; use Wazobia\LaravelAuthGuard\Services\ProjectAuthService; class AuthController { public function validateJwt(JwtAuthService $jwtService, Request $request) { try { $token = $request->bearerToken(); $user = $jwtService->authenticate($token); return response()->json(['user' => $user]); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 401); } } public function validateProject(ProjectAuthService $projectService, Request $request) { try { $token = $request->header('x-project-token'); $serviceId = config('auth-guard.service_id'); $project = $projectService->authenticateWithToken($token, $serviceId); return response()->json(['project' => $project]); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 401); } } }
Token Revocation
use Wazobia\LaravelAuthGuard\Services\JwtAuthService; Route::post('/logout', function (JwtAuthService $jwtService, Request $request) { $jti = $request->input('jti'); // JWT ID from token payload $ttl = 3600; // Revoke for 1 hour $jwtService->revokeToken($jti, $ttl); return response()->json(['message' => 'Token revoked']); })->middleware('jwt.auth');
๐ Documentation
| Topic | Description |
|---|---|
| Middleware | jwt.auth, project.auth, combined.auth |
| Directives | @userAuth, @projectAuth, @serviceAuth, @combineAuth, @scopes |
| Services | JwtAuthService, ProjectAuthService, ServiceAuthService |
| Caching | Redis-based JWKS caching with per-tenant isolation |
| Token Types | JWT (RS512), Platform/Project (HMAC), Service (CLIENT_ID/SECRET) |
๐ค Support
For issues or questions:
- GitHub Issues: Report an issue
- Email: developer@wazobia.tech
- Documentation: Full Documentation
โ
Implementation Status\n\nThis Laravel Auth Guard package is production-ready with comprehensive testing:\n\n### โ
Fully Implemented Features\n- JWT User Authentication (@userAuth) - Complete with scope validation\n- Platform Token Authentication (@projectAuth) - HMAC validation with tenant isolation \n- Service Authentication (@serviceAuth) - CLIENT_ID/CLIENT_SECRET with Mercury integration\n- Combined Authentication (@combineAuth) - Dual token validation with scope merging\n- JWKS Integration - Per-tenant key caching with auto-refresh\n- Mercury GraphQL Integration - Complete service token lifecycle\n- Comprehensive Error Handling - Detailed error messages and logging\n\n### ๐ง Complete Test Suite\n- GraphQL test endpoints for all authentication directives\n- Automated test scripts with real Mercury token generation\n- Scope validation testing (success and failure scenarios)\n- Combined authentication with dual token validation\n- Service authentication with CLIENT_ID/CLIENT_SECRET flow\n\n---\n\n## ๐ License
This package is open-sourced software licensed under the MIT license.
Made with โค๏ธ by Wazobia Technologies
โญ Star us on GitHub if this helped you!