Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Switch User Stateless Bundle Laravel Package

bigz/switch-user-stateless-bundle

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Installation**:
   ```bash
   composer require lafourchette/switch-user-stateless-bundle

Register the bundle in config/bundles.php (Symfony Flex) or AppKernel.php (legacy):

LaFourchette\SwitchUserStatelessBundle\SwitchUserStatelessBundle::class => ['all' => true],
  1. Configuration: Add to config/packages/switch_user_stateless.yaml:

    switch_user_stateless:
        firewall: api  # Target your API firewall
        token_param: impersonate_token  # Customize token param name
        user_resolver: App\Service\UserResolver  # Optional custom resolver
    
  2. First Use Case: Trigger impersonation via a request header or query param:

    curl -H "X-Impersonate-Token: YOUR_TOKEN" http://api.example.com/endpoint
    

    Or via query string:

    curl "http://api.example.com/endpoint?impersonate_token=YOUR_TOKEN"
    
  3. Token Generation: Generate tokens for users in your admin panel or CLI:

    use LaFourchette\SwitchUserStatelessBundle\Generator\TokenGenerator;
    
    $generator = new TokenGenerator();
    $token = $generator->generateToken($user); // Returns a unique token
    

Implementation Patterns

Common Workflows

1. Stateless Impersonation Flow

  • Admin Requests Impersonation:

    // Admin calls API with token
    $response = $client->request('GET', '/api/orders', [
        'headers' => ['X-Impersonate-Token' => $token]
    ]);
    
  • Middleware Handles Token: The bundle automatically validates the token and switches the user context.

  • Cleanup: Impersonation is stateless—no manual logout needed. Tokens expire after use (configurable).

2. Custom User Resolver

Override the default resolver to fetch users from a custom source (e.g., database with soft-deletes):

// src/Service/CustomUserResolver.php
use LaFourchette\SwitchUserStatelessBundle\Resolver\UserResolverInterface;

class CustomUserResolver implements UserResolverInterface
{
    public function findUserByToken($token)
    {
        return User::withTrashed()->where('api_token', $token)->first();
    }
}

Register in config:

switch_user_stateless:
    user_resolver: App\Service\CustomUserResolver

3. Token Management

  • Generate Tokens Programmatically:
    $token = $this->get('switch_user_stateless.token_generator')->generateToken($user);
    
  • Revoke Tokens: Implement a TokenRevocator service to invalidate tokens (e.g., on user logout):
    $this->get('switch_user_stateless.token_revocator')->revokeToken($token);
    

4. Integration with API Platform

Use with API Platform’s ContextBuilder to pass the impersonated user to serializers:

// src/EventListener/SwitchUserListener.php
use LaFourchette\SwitchUserStatelessBundle\Event\SwitchUserEvent;

class SwitchUserListener
{
    public function onSwitchUser(SwitchUserEvent $event)
    {
        $event->getContextBuilder()->setUser($event->getUser());
    }
}

Register as a subscriber:

services:
    App\EventListener\SwitchUserListener:
        tags:
            - { name: kernel.event_subscriber }

5. Rate Limiting Tokens

Combine with Symfony’s RateLimiter to restrict impersonation attempts:

# config/packages/security.yaml
firewalls:
    api:
        rate_limiter: impersonation_limiter
// config/services.yaml
services:
    App\Security\ImpersonationRateLimiter:
        arguments:
            $limit: 5
            $interval: '1 minute'

Gotchas and Tips

Pitfalls

  1. Token Leakage:

    • Issue: Tokens generated via generateToken() are not stored; they must be manually persisted (e.g., in a user_api_tokens table).
    • Fix: Extend the bundle to log tokens or use a database-backed token system:
      // src/Service/DatabaseTokenGenerator.php
      class DatabaseTokenGenerator extends TokenGenerator
      {
          public function generateToken(User $user)
          {
              $token = parent::generateToken($user);
              $user->apiTokens()->create(['token' => $token]);
              return $token;
          }
      }
      
  2. Firewall Mismatch:

    • Issue: If the firewall config doesn’t match your API’s actual firewall name, impersonation silently fails.
    • Fix: Verify the firewall name in config/packages/security.yaml:
      firewalls:
          api:  # <-- Must match switch_user_stateless.firewall
              pattern: ^/api
      
  3. Token Expiration:

    • Issue: Tokens are single-use by default. Reusing a token may cause unexpected behavior.
    • Fix: Implement a TokenRevocator to clear tokens after use:
      // src/Service/TokenRevocator.php
      class TokenRevocator
      {
          public function revokeToken($token)
          {
              // Logic to mark token as used/expired
          }
      }
      
  4. Circular Dependencies:

    • Issue: If your UserResolver depends on services that require the user to be loaded (e.g., UserProvider), you’ll hit a circular dependency.
    • Fix: Use lazy-loading or pass the raw token to a separate service:
      $user = $this->userResolver->findUserByToken($token);
      if (!$user) {
          throw new \RuntimeException('Invalid token');
      }
      

Debugging Tips

  1. Enable Debugging: Add this to config/packages/dev/switch_user_stateless.yaml:

    switch_user_stateless:
        debug: true  # Logs impersonation events to Symfony's logger
    
  2. Check Events: Subscribe to SwitchUserEvent to debug impersonation:

    use LaFourchette\SwitchUserStatelessBundle\Event\SwitchUserEvent;
    
    class DebugSubscriber
    {
        public function onSwitchUser(SwitchUserEvent $event)
        {
            $this->logger->info('Switched to user', [
                'original_user' => $event->getOriginalUser(),
                'impersonated_user' => $event->getUser(),
            ]);
        }
    }
    
  3. Token Validation: Override TokenValidator to add custom logic:

    // src/Validator/CustomTokenValidator.php
    use LaFourchette\SwitchUserStatelessBundle\Validator\TokenValidatorInterface;
    
    class CustomTokenValidator implements TokenValidatorInterface
    {
        public function validate($token)
        {
            if (strpos($token, 'INVALID_') === 0) {
                throw new \RuntimeException('Invalid token prefix');
            }
            return true;
        }
    }
    

    Register in config:

    switch_user_stateless:
        token_validator: App\Validator\CustomTokenValidator
    

Extension Points

  1. Custom Token Storage: Replace the default TokenGenerator to store tokens in Redis or a dedicated table:

    // src/Generator/RedisTokenGenerator.php
    class RedisTokenGenerator extends TokenGenerator
    {
        public function generateToken(User $user)
        {
            $token = parent::generateToken($user);
            $this->redis->set("impersonation:$token", $user->id, 'EX', 3600);
            return $token;
        }
    }
    
  2. Event-Driven Workflows: Extend the bundle’s events to trigger actions (e.g., audit logs):

    // Subscribe to SwitchUserEvent
    public function onSwitchUser(SwitchUserEvent $event)
    {
        $this->auditLogger->log('USER_IMPERSONATION', [
            'impersonated_user_id' => $event->getUser()->getId(),
            'admin_user_id' => $event->getOriginalUser()->getId(),
        ]);
    }
    
  3. Multi-Tenant Support: Add tenant context to impersonation:

    // Extend SwitchUserEvent
    public function getTenant()
    {
        return $this->tenant;
    }
    

    Then pass the tenant in your resolver:

    public function findUserByToken($token, $tenantId)
    {
        return User::where('tenant_id', $tenantId)
            ->where('api_token', $token)
            ->first();
    }
    
  4. **

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
comsave/common
alecsammon/php-raml-parser
chrome-php/wrench
lendable/composer-license-checker
typhoon/reflection
mesilov/moneyphp-percentage
mike42/gfx-php
bookdown/themes
aura/view
aura/html
aura/cli
povils/phpmnd
nayjest/manipulator
omnipay/tests
psr-mock/http-message-implementation
psr-mock/http-factory-implementation
psr-mock/http-client-implementation
voku/email-check
voku/urlify
rtheunissen/guzzle-log-middleware