wazobia/laravel-auth-guard

JWT and Project authentication middleware for Laravel with JWKS support

Maintainers

Package info

github.com/wazobiatech/laravel-jwt-auth

pkg:composer/wazobia/laravel-auth-guard

Statistics

Installs: 20

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.3 2026-03-11 16:51 UTC

This package is not auto-updated.

Last update: 2026-03-11 17:43:07 UTC


README

Laravel PHP Redis License

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

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

  1. Access GraphQL Playground at http://localhost:8000/graphql-playground
  2. Add headers in the bottom left:
{
  "Authorization": "Bearer YOUR_JWT_TOKEN",
  "x-project-token": "Bearer YOUR_PROJECT_TOKEN"
}
  1. 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:

  1. Add directive namespace to config/lighthouse.php
  2. 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:

  1. Verify Redis is running:
redis-cli ping  # Should return: PONG
  1. Check your .env:
# For Docker
REDIS_HOST=redis

# For local
REDIS_HOST=127.0.0.1
  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:

โœ… 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!