Navigation

Laravel

Customizing Laravel Passport for Complex Auth Flows

Master advanced Laravel Passport customizations for complex authentication flows. Learn to implement custom grant types, multi-tenant auth, dynamic scopes, and enterprise-grade security patterns with practical examples.

Master advanced Laravel Passport customizations for multi-tenant applications, custom grant types, and complex authentication workflows in enterprise environments.

Table Of Contents

Understanding Laravel Passport Architecture

Laravel Passport provides a full OAuth2 server implementation built on the League OAuth2 server package. While it works excellently out of the box for standard authentication flows, real-world enterprise applications often require customizations for specific business requirements, multi-tenant architectures, and complex user authorization scenarios.

Understanding Passport's internal architecture is crucial for implementing custom authentication flows that maintain security while meeting unique business needs. This is particularly important when building SaaS applications with Laravel that require sophisticated access control mechanisms.

Custom Grant Types

Implementing Custom Grant Types

Create custom grant types for specialized authentication scenarios:

<?php

namespace App\Passport\Grants;

use Laravel\Passport\Bridge\User;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AbstractGrant;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use Psr\Http\Message\ServerRequestInterface;

class TwoFactorGrant extends AbstractGrant
{
    protected UserRepositoryInterface $userRepository;
    
    public function __construct(
        UserRepositoryInterface $userRepository,
        RefreshTokenRepositoryInterface $refreshTokenRepository
    ) {
        $this->userRepository = $userRepository;
        $this->refreshTokenRepository = $refreshTokenRepository;
        
        $this->refreshTokenTTL = new \DateInterval('P1M');
    }
    
    public function respondToAccessTokenRequest(
        ServerRequestInterface $request,
        ResponseTypeInterface $responseType,
        \DateInterval $accessTokenTTL
    ): ResponseTypeInterface {
        // Validate the client
        $client = $this->validateClient($request);
        
        // Validate request parameters
        $parameters = $this->getRequestParameters($request);
        $this->validateParameters($parameters);
        
        // Authenticate user with email/password
        $user = $this->validateUserCredentials($parameters);
        
        // Validate 2FA token
        $this->validateTwoFactorToken($user, $parameters['two_factor_token']);
        
        // Generate access token
        $accessToken = $this->issueAccessToken(
            $accessTokenTTL,
            $client,
            $user->getIdentifier(),
            $this->validateScopes($parameters['scope'] ?? '')
        );
        
        // Generate refresh token
        $refreshToken = $this->issueRefreshToken($accessToken);
        
        $responseType->setAccessToken($accessToken);
        $responseType->setRefreshToken($refreshToken);
        
        return $responseType;
    }
    
    protected function getRequestParameters(ServerRequestInterface $request): array
    {
        $parameters = (array) $request->getParsedBody();
        
        $requiredParams = ['email', 'password', 'two_factor_token'];
        foreach ($requiredParams as $param) {
            if (!isset($parameters[$param])) {
                throw OAuthServerException::invalidRequest($param);
            }
        }
        
        return $parameters;
    }
    
    protected function validateParameters(array $parameters): void
    {
        if (empty($parameters['email']) || !filter_var($parameters['email'], FILTER_VALIDATE_EMAIL)) {
            throw OAuthServerException::invalidRequest('email', 'Invalid email address');
        }
        
        if (empty($parameters['password'])) {
            throw OAuthServerException::invalidRequest('password', 'Password is required');
        }
        
        if (empty($parameters['two_factor_token']) || !preg_match('/^\d{6}$/', $parameters['two_factor_token'])) {
            throw OAuthServerException::invalidRequest('two_factor_token', 'Invalid 2FA token format');
        }
    }
    
    protected function validateUserCredentials(array $parameters): UserEntityInterface
    {
        $user = $this->userRepository->getUserEntityByUserCredentials(
            $parameters['email'],
            $parameters['password'],
            'two_factor',
            $this->getClientEntity()
        );
        
        if ($user instanceof UserEntityInterface === false) {
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
            throw OAuthServerException::invalidCredentials();
        }
        
        return $user;
    }
    
    protected function validateTwoFactorToken(UserEntityInterface $user, string $token): void
    {
        $appUser = \App\Models\User::find($user->getIdentifier());
        
        if (!$appUser->hasTwoFactorEnabled()) {
            throw OAuthServerException::invalidRequest('two_factor_token', '2FA is not enabled for this user');
        }
        
        if (!$appUser->verifyTwoFactorToken($token)) {
            throw OAuthServerException::invalidRequest('two_factor_token', 'Invalid 2FA token');
        }
    }
    
    public function getIdentifier(): string
    {
        return 'two_factor';
    }
}

Register the custom grant type in a service provider:

<?php

namespace App\Providers;

use Laravel\Passport\PassportServiceProvider;
use League\OAuth2\Server\AuthorizationServer;
use App\Passport\Grants\TwoFactorGrant;

class PassportGrantServiceProvider extends PassportServiceProvider
{
    public function boot(): void
    {
        parent::boot();
        
        app(AuthorizationServer::class)->enableGrantType(
            new TwoFactorGrant(
                app(\Laravel\Passport\Bridge\UserRepository::class),
                app(\Laravel\Passport\Bridge\RefreshTokenRepository::class)
            ),
            \DateInterval::createFromDateString('1 hour')
        );
    }
}

Multi-Tenant Grant Type

Implement tenant-aware authentication:

<?php

namespace App\Passport\Grants;

use Laravel\Passport\Bridge\User;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\PasswordGrant;
use Psr\Http\Message\ServerRequestInterface;

class TenantPasswordGrant extends PasswordGrant
{
    public function respondToAccessTokenRequest(
        ServerRequestInterface $request,
        ResponseTypeInterface $responseType,
        \DateInterval $accessTokenTTL
    ): ResponseTypeInterface {
        // Validate tenant context first
        $parameters = (array) $request->getParsedBody();
        $tenant = $this->validateTenant($parameters);
        
        // Set tenant context for user lookup
        app()->instance('auth.tenant', $tenant);
        
        return parent::respondToAccessTokenRequest($request, $responseType, $accessTokenTTL);
    }
    
    protected function validateTenant(array $parameters): \App\Models\Tenant
    {
        if (!isset($parameters['tenant_id'])) {
            throw OAuthServerException::invalidRequest('tenant_id', 'Tenant ID is required');
        }
        
        $tenant = \App\Models\Tenant::find($parameters['tenant_id']);
        
        if (!$tenant) {
            throw OAuthServerException::invalidRequest('tenant_id', 'Invalid tenant');
        }
        
        if (!$tenant->is_active) {
            throw OAuthServerException::accessDenied('Tenant account is suspended');
        }
        
        return $tenant;
    }
}

Advanced Client Management

Dynamic Client Registration

Implement dynamic client registration for multi-tenant applications:

<?php

namespace App\Services;

use Laravel\Passport\ClientRepository;
use Laravel\Passport\Client;
use App\Models\Tenant;

class TenantClientManager
{
    protected ClientRepository $clientRepository;
    
    public function __construct(ClientRepository $clientRepository)
    {
        $this->clientRepository = $clientRepository;
    }
    
    public function createTenantClient(Tenant $tenant, array $clientData): Client
    {
        $client = $this->clientRepository->create(
            $tenant->id, // Use tenant ID as user ID
            $this->generateClientName($tenant, $clientData),
            $this->generateRedirectUris($tenant, $clientData),
            null, // No specific provider
            false, // Not a personal access client
            false, // Not a password client
            true   // Is a confidential client
        );
        
        // Store additional tenant-specific metadata
        $client->update([
            'tenant_id' => $tenant->id,
            'allowed_scopes' => $this->getTenantAllowedScopes($tenant),
            'client_metadata' => json_encode([
                'tenant_name' => $tenant->name,
                'environment' => $clientData['environment'] ?? 'production',
                'contact_email' => $clientData['contact_email'] ?? $tenant->email,
                'webhook_url' => $clientData['webhook_url'] ?? null,
            ]),
        ]);
        
        return $client;
    }
    
    protected function generateClientName(Tenant $tenant, array $clientData): string
    {
        $environment = $clientData['environment'] ?? 'production';
        return "{$tenant->name} - {$environment}";
    }
    
    protected function generateRedirectUris(Tenant $tenant, array $clientData): string
    {
        $baseUris = [
            $tenant->primary_domain . '/auth/callback',
            $tenant->primary_domain . '/auth/mobile-callback',
        ];
        
        if (isset($clientData['additional_uris'])) {
            $baseUris = array_merge($baseUris, $clientData['additional_uris']);
        }
        
        return implode(',', $this->validateRedirectUris($baseUris));
    }
    
    protected function validateRedirectUris(array $uris): array
    {
        $validated = [];
        
        foreach ($uris as $uri) {
            if (filter_var($uri, FILTER_VALIDATE_URL) && $this->isAllowedScheme($uri)) {
                $validated[] = $uri;
            }
        }
        
        if (empty($validated)) {
            throw new \InvalidArgumentException('At least one valid redirect URI is required');
        }
        
        return $validated;
    }
    
    protected function isAllowedScheme(string $uri): bool
    {
        $scheme = parse_url($uri, PHP_URL_SCHEME);
        $allowedSchemes = ['https', 'http', 'myapp']; // Include custom schemes for mobile
        
        return in_array($scheme, $allowedSchemes);
    }
    
    protected function getTenantAllowedScopes(Tenant $tenant): array
    {
        $baseScopes = ['read-profile', 'write-profile'];
        
        // Add tenant-specific scopes based on subscription
        if ($tenant->hasFeature('api_access')) {
            $baseScopes[] = 'api-access';
        }
        
        if ($tenant->hasFeature('webhook_access')) {
            $baseScopes[] = 'webhook-access';
        }
        
        if ($tenant->subscription_tier === 'enterprise') {
            $baseScopes[] = 'admin-access';
        }
        
        return $baseScopes;
    }
}

Client Authentication Customization

Implement custom client authentication methods:

<?php

namespace App\Passport\Authenticators;

use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use Psr\Http\Message\ServerRequestInterface;

class CustomClientAuthenticator
{
    protected ClientRepositoryInterface $clientRepository;
    
    public function __construct(ClientRepositoryInterface $clientRepository)
    {
        $this->clientRepository = $clientRepository;
    }
    
    public function authenticateClient(ServerRequestInterface $request): ClientEntityInterface
    {
        $method = $this->getAuthenticationMethod($request);
        
        return match($method) {
            'client_secret_basic' => $this->authenticateBasicAuth($request),
            'client_secret_post' => $this->authenticatePostAuth($request),
            'client_secret_jwt' => $this->authenticateJwtAuth($request),
            'private_key_jwt' => $this->authenticatePrivateKeyJwt($request),
            default => throw OAuthServerException::invalidClient($request),
        };
    }
    
    protected function getAuthenticationMethod(ServerRequestInterface $request): string
    {
        $headers = $request->getHeaders();
        $body = $request->getParsedBody();
        
        if (isset($headers['Authorization'])) {
            return 'client_secret_basic';
        }
        
        if (isset($body['client_assertion_type'])) {
            if ($body['client_assertion_type'] === 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer') {
                return isset($body['client_secret']) ? 'client_secret_jwt' : 'private_key_jwt';
            }
        }
        
        return 'client_secret_post';
    }
    
    protected function authenticateJwtAuth(ServerRequestInterface $request): ClientEntityInterface
    {
        $body = $request->getParsedBody();
        
        if (!isset($body['client_assertion'])) {
            throw OAuthServerException::invalidRequest('client_assertion');
        }
        
        try {
            $jwt = new \Firebase\JWT\JWT();
            $jwt->leeway = 60; // Allow 60 seconds leeway for clock skew
            
            $payload = $jwt::decode(
                $body['client_assertion'],
                new \Firebase\JWT\Key($this->getClientSecret($body['client_id']), 'HS256')
            );
            
            $this->validateJwtClaims($payload, $body['client_id']);
            
            return $this->clientRepository->getClientEntity($body['client_id']);
            
        } catch (\Exception $e) {
            throw OAuthServerException::invalidClient($request);
        }
    }
    
    protected function authenticatePrivateKeyJwt(ServerRequestInterface $request): ClientEntityInterface
    {
        $body = $request->getParsedBody();
        
        if (!isset($body['client_assertion'])) {
            throw OAuthServerException::invalidRequest('client_assertion');
        }
        
        try {
            $client = $this->clientRepository->getClientEntity($body['client_id']);
            $publicKey = $this->getClientPublicKey($client);
            
            $jwt = new \Firebase\JWT\JWT();
            $payload = $jwt::decode(
                $body['client_assertion'],
                new \Firebase\JWT\Key($publicKey, 'RS256')
            );
            
            $this->validateJwtClaims($payload, $body['client_id']);
            
            return $client;
            
        } catch (\Exception $e) {
            throw OAuthServerException::invalidClient($request);
        }
    }
    
    protected function validateJwtClaims(\stdClass $payload, string $clientId): void
    {
        // Validate issuer
        if (!isset($payload->iss) || $payload->iss !== $clientId) {
            throw new \InvalidArgumentException('Invalid issuer');
        }
        
        // Validate subject
        if (!isset($payload->sub) || $payload->sub !== $clientId) {
            throw new \InvalidArgumentException('Invalid subject');
        }
        
        // Validate audience
        if (!isset($payload->aud) || !in_array(config('app.url') . '/oauth/token', (array)$payload->aud)) {
            throw new \InvalidArgumentException('Invalid audience');
        }
        
        // Validate expiration
        if (!isset($payload->exp) || $payload->exp < time()) {
            throw new \InvalidArgumentException('Token expired');
        }
        
        // Validate not before
        if (isset($payload->nbf) && $payload->nbf > time()) {
            throw new \InvalidArgumentException('Token not yet valid');
        }
    }
}

Custom Scope Management

Dynamic Scope Resolution

Implement context-aware scope resolution:

<?php

namespace App\Passport\Scopes;

use Laravel\Passport\Passport;
use App\Models\User;
use App\Models\Tenant;

class DynamicScopeResolver
{
    public function resolveScopes(User $user, array $requestedScopes, ?Tenant $tenant = null): array
    {
        $availableScopes = $this->getAvailableScopes($user, $tenant);
        $resolvedScopes = [];
        
        foreach ($requestedScopes as $scope) {
            if ($this->canAccessScope($user, $scope, $availableScopes, $tenant)) {
                $resolvedScopes[] = $scope;
            }
        }
        
        return $resolvedScopes;
    }
    
    protected function getAvailableScopes(User $user, ?Tenant $tenant = null): array
    {
        $baseScopes = ['read-profile'];
        
        // User-level scopes
        if ($user->hasVerifiedEmail()) {
            $baseScopes[] = 'write-profile';
        }
        
        if ($user->hasRole('admin')) {
            $baseScopes = array_merge($baseScopes, ['admin-users', 'admin-settings']);
        }
        
        // Tenant-level scopes
        if ($tenant) {
            $tenantScopes = $this->getTenantScopes($user, $tenant);
            $baseScopes = array_merge($baseScopes, $tenantScopes);
        }
        
        return $baseScopes;
    }
    
    protected function getTenantScopes(User $user, Tenant $tenant): array
    {
        $scopes = [];
        
        $membership = $user->tenantMemberships()->where('tenant_id', $tenant->id)->first();
        
        if (!$membership) {
            return $scopes;
        }
        
        // Role-based scopes
        switch ($membership->role) {
            case 'owner':
                $scopes = array_merge($scopes, [
                    'tenant-admin',
                    'manage-billing',
                    'manage-users',
                    'manage-settings'
                ]);
                break;
                
            case 'admin':
                $scopes = array_merge($scopes, [
                    'manage-users',
                    'manage-settings'
                ]);
                break;
                
            case 'member':
                $scopes[] = 'tenant-access';
                break;
        }
        
        // Feature-based scopes
        if ($tenant->hasFeature('api_access')) {
            $scopes[] = 'api-access';
        }
        
        if ($tenant->hasFeature('webhook_management') && in_array($membership->role, ['owner', 'admin'])) {
            $scopes[] = 'manage-webhooks';
        }
        
        return $scopes;
    }
    
    protected function canAccessScope(User $user, string $scope, array $availableScopes, ?Tenant $tenant = null): bool
    {
        // Check if scope is in available scopes
        if (!in_array($scope, $availableScopes)) {
            return false;
        }
        
        // Additional business logic checks
        if ($scope === 'manage-billing' && $tenant) {
            // Only allow billing access if tenant has active subscription
            return $tenant->hasActiveSubscription();
        }
        
        if (str_starts_with($scope, 'admin-') && $user->hasRole('suspended')) {
            return false;
        }
        
        return true;
    }
}

Hierarchical Scopes

Implement hierarchical scope inheritance:

<?php

namespace App\Passport\Scopes;

class HierarchicalScopeManager
{
    protected array $scopeHierarchy = [
        'tenant-owner' => [
            'tenant-admin',
            'manage-billing',
            'manage-users',
            'manage-settings',
            'tenant-access'
        ],
        'tenant-admin' => [
            'manage-users',
            'manage-settings',
            'tenant-access'
        ],
        'tenant-member' => [
            'tenant-access'
        ],
        'api-admin' => [
            'api-write',
            'api-read'
        ],
        'api-write' => [
            'api-read'
        ]
    ];
    
    public function expandScopes(array $scopes): array
    {
        $expandedScopes = [];
        
        foreach ($scopes as $scope) {
            $expandedScopes[] = $scope;
            
            if (isset($this->scopeHierarchy[$scope])) {
                $childScopes = $this->expandScopes($this->scopeHierarchy[$scope]);
                $expandedScopes = array_merge($expandedScopes, $childScopes);
            }
        }
        
        return array_unique($expandedScopes);
    }
    
    public function hasScope(array $userScopes, string $requiredScope): bool
    {
        $expandedScopes = $this->expandScopes($userScopes);
        return in_array($requiredScope, $expandedScopes);
    }
    
    public function getMinimalScopes(array $scopes): array
    {
        $minimal = [];
        
        foreach ($scopes as $scope) {
            $isRedundant = false;
            
            foreach ($scopes as $otherScope) {
                if ($scope !== $otherScope && $this->isChildScope($scope, $otherScope)) {
                    $isRedundant = true;
                    break;
                }
            }
            
            if (!$isRedundant) {
                $minimal[] = $scope;
            }
        }
        
        return $minimal;
    }
    
    protected function isChildScope(string $childScope, string $parentScope): bool
    {
        if (!isset($this->scopeHierarchy[$parentScope])) {
            return false;
        }
        
        $childScopes = $this->expandScopes($this->scopeHierarchy[$parentScope]);
        return in_array($childScope, $childScopes);
    }
}

Token Customization

Custom Token Claims

Add custom claims to access tokens:

<?php

namespace App\Passport;

use Laravel\Passport\Token;
use Laravel\Passport\PersonalAccessTokenResult;
use App\Models\User;
use Firebase\JWT\JWT;

class CustomTokenGenerator
{
    public function generateTokenWithClaims(User $user, array $scopes, array $customClaims = []): PersonalAccessTokenResult
    {
        $token = $user->createToken('API Token', $scopes);
        
        // Add custom claims to the token
        $this->addCustomClaims($token->token, $user, $customClaims);
        
        return $token;
    }
    
    protected function addCustomClaims(Token $token, User $user, array $customClaims): void
    {
        $claims = array_merge([
            'user_id' => $user->id,
            'email' => $user->email,
            'email_verified' => $user->hasVerifiedEmail(),
            'roles' => $user->roles->pluck('name')->toArray(),
            'tenant_id' => $user->current_tenant_id,
            'subscription_tier' => $user->currentTenant?->subscription_tier,
            'last_login' => $user->last_login_at?->toISOString(),
            'created_at' => $user->created_at->toISOString(),
        ], $customClaims);
        
        // Store claims in token's attributes or separate table
        $token->update([
            'custom_claims' => json_encode($claims)
        ]);
    }
    
    public function createJwtFromToken(Token $token): string
    {
        $payload = [
            'iss' => config('app.url'),
            'aud' => config('app.url'),
            'iat' => now()->timestamp,
            'exp' => $token->expires_at->timestamp,
            'sub' => $token->user_id,
            'jti' => $token->id,
            'scopes' => $token->scopes,
        ];
        
        // Add custom claims
        if ($token->custom_claims) {
            $customClaims = json_decode($token->custom_claims, true);
            $payload = array_merge($payload, $customClaims);
        }
        
        return JWT::encode($payload, config('passport.jwt_key'), 'RS256');
    }
}

Token Middleware Enhancement

Create enhanced token validation middleware:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
use Laravel\Passport\Token;
use Laravel\Passport\TokenRepository;

class EnhancedTokenValidation
{
    protected TokenRepository $tokenRepository;
    
    public function __construct(TokenRepository $tokenRepository)
    {
        $this->tokenRepository = $tokenRepository;
    }
    
    public function handle(Request $request, Closure $next, ...$scopes)
    {
        $token = $request->user()->token();
        
        // Validate token hasn't been revoked
        if ($token->revoked) {
            return response()->json(['error' => 'Token has been revoked'], 401);
        }
        
        // Validate token hasn't expired
        if ($token->expires_at->isPast()) {
            return response()->json(['error' => 'Token has expired'], 401);
        }
        
        // Validate hierarchical scopes
        if (!$this->hasRequiredScopes($token, $scopes)) {
            return response()->json(['error' => 'Insufficient scope'], 403);
        }
        
        // Validate tenant context
        if (!$this->validateTenantContext($request, $token)) {
            return response()->json(['error' => 'Invalid tenant context'], 403);
        }
        
        // Update token usage statistics
        $this->updateTokenUsage($token);
        
        return $next($request);
    }
    
    protected function hasRequiredScopes(Token $token, array $requiredScopes): bool
    {
        if (empty($requiredScopes)) {
            return true;
        }
        
        $scopeManager = app(HierarchicalScopeManager::class);
        $tokenScopes = $token->scopes;
        
        foreach ($requiredScopes as $scope) {
            if (!$scopeManager->hasScope($tokenScopes, $scope)) {
                return false;
            }
        }
        
        return true;
    }
    
    protected function validateTenantContext(Request $request, Token $token): bool
    {
        $currentTenant = app('current_tenant');
        
        if (!$currentTenant) {
            return true; // No tenant context required
        }
        
        $customClaims = json_decode($token->custom_claims ?? '{}', true);
        $tokenTenantId = $customClaims['tenant_id'] ?? null;
        
        return $tokenTenantId === $currentTenant->id;
    }
    
    protected function updateTokenUsage(Token $token): void
    {
        // Update usage statistics asynchronously
        dispatch(function () use ($token) {
            $token->increment('usage_count');
            $token->update(['last_used_at' => now()]);
        })->afterResponse();
    }
}

Security Enhancements

Rate Limiting by Scope

Implement scope-based rate limiting:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Cache\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

class ScopeBasedRateLimit
{
    protected RateLimiter $limiter;
    
    protected array $scopeLimits = [
        'api-read' => ['attempts' => 1000, 'decay' => 3600], // 1000 per hour
        'api-write' => ['attempts' => 100, 'decay' => 3600],  // 100 per hour
        'admin-api' => ['attempts' => 10000, 'decay' => 3600], // 10000 per hour
        'webhook-access' => ['attempts' => 50, 'decay' => 60], // 50 per minute
    ];
    
    public function __construct(RateLimiter $limiter)
    {
        $this->limiter = $limiter;
    }
    
    public function handle(Request $request, Closure $next, string $scope): Response
    {
        $token = $request->user()->token();
        $key = $this->resolveRequestSignature($request, $token, $scope);
        
        $limit = $this->getScopeLimit($scope);
        
        if ($this->limiter->tooManyAttempts($key, $limit['attempts'])) {
            return $this->buildRateLimitResponse($key, $limit['attempts']);
        }
        
        $this->limiter->hit($key, $limit['decay']);
        
        $response = $next($request);
        
        return $this->addHeaders(
            $response,
            $limit['attempts'],
            $this->calculateRemainingAttempts($key, $limit['attempts'])
        );
    }
    
    protected function resolveRequestSignature(Request $request, $token, string $scope): string
    {
        return sha1(implode('|', [
            $token->id,
            $scope,
            $request->ip(),
        ]));
    }
    
    protected function getScopeLimit(string $scope): array
    {
        return $this->scopeLimits[$scope] ?? ['attempts' => 60, 'decay' => 3600];
    }
    
    protected function buildRateLimitResponse(string $key, int $maxAttempts): Response
    {
        $retryAfter = $this->limiter->availableIn($key);
        
        return response()->json([
            'error' => 'Rate limit exceeded',
            'message' => "Too many requests. Limit: {$maxAttempts} requests per hour.",
            'retry_after' => $retryAfter,
        ], 429)->withHeaders([
            'X-RateLimit-Limit' => $maxAttempts,
            'X-RateLimit-Remaining' => 0,
            'Retry-After' => $retryAfter,
        ]);
    }
    
    protected function calculateRemainingAttempts(string $key, int $maxAttempts): int
    {
        return max(0, $maxAttempts - $this->limiter->attempts($key));
    }
    
    protected function addHeaders(Response $response, int $maxAttempts, int $remainingAttempts): Response
    {
        return $response->withHeaders([
            'X-RateLimit-Limit' => $maxAttempts,
            'X-RateLimit-Remaining' => $remainingAttempts,
        ]);
    }
}

Customizing Laravel Passport for complex authentication flows requires deep understanding of OAuth2 specifications and careful consideration of security implications. These patterns enable building sophisticated authentication systems that meet enterprise requirements while maintaining security and performance. Whether you're building multi-tenant applications or complex API ecosystems, these customizations provide the flexibility needed for advanced use cases.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel