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

Token Bundle Laravel Package

ecourty/token-bundle

Symfony bundle to manage secure, typed, revocable tokens for any Doctrine entity (password resets, email verification, share links). Supports expiry, single-use/max-uses, JSON payloads, events, subject resolution, and a purge command.

View on GitHub
Deep Wiki
Context7
## Getting Started

### Minimal Setup
1. **Install the package**:
   ```bash
   composer require ecourty/token-bundle

Ensure TokenBundle is registered in config/bundles.php (Symfony Flex handles this automatically).

  1. Create the database table:

    php bin/console doctrine:migrations:diff
    php bin/console doctrine:migrations:migrate
    
  2. First use case: Password reset token Implement TokenSubjectInterface on your User entity:

    class User implements TokenSubjectInterface {
        public function getTokenSubjectId(): string {
            return (string) $this->id;
        }
    }
    

    Generate and consume a token in a service:

    $token = $tokenManager->create(
        type: 'password_reset',
        subject: $user,
        expiresIn: '+1 hour',
        singleUse: true
    );
    

Implementation Patterns

Core Workflow: Token Lifecycle

  1. Creation:

    $token = $tokenManager->create(
        type: 'email_verification',
        subject: $user,
        expiresIn: '+24 hours',
        payload: ['ip' => $request->getClientIp()]
    );
    
    • Use payload for metadata (e.g., IP address, user agent).
    • Set singleUse: true for one-time actions (e.g., password resets).
  2. Validation & Consumption:

    try {
        $token = $tokenManager->consume($tokenString, 'email_verification');
        $user = $tokenManager->resolveSubject($token);
        // Mark user as verified...
    } catch (TokenExpiredException) {
        // Redirect to resend flow
    }
    
  3. Revocation:

    // Revoke a single token
    $tokenManager->revoke($tokenString);
    
    // Bulk revoke (e.g., on user logout)
    $tokenManager->revokeAll($user, 'email_verification');
    

Integration Patterns

  • Email Verification:

    $token = $tokenManager->create(
        type: 'email_verification',
        subject: $user,
        expiresIn: '+7 days',
        singleUse: true
    );
    // Store token in user's email template
    
  • Shareable Links:

    $token = $tokenManager->create(
        type: 'document_share',
        subject: $document,
        expiresIn: '+30 days',
        maxUses: 5,
        payload: ['permissions' => ['view']]
    );
    // Generate URL: `/documents/{id}?token={$token->getToken()}`
    
  • API Tokens:

    #[RequiresToken(type: 'api_access', resolver: BearerTokenResolver::class)]
    public function secureEndpoint(Request $request) {
        $token = $request->attributes->get('_token');
        $user = $tokenManager->resolveSubject($token);
    }
    

Event-Driven Extensions

Listen to token events for auditing or notifications:

#[AsEventListener]
public function onTokenConsumed(TokenConsumedEvent $event) {
    if ($event->token->getType() === 'password_reset') {
        $this->mailer->send(new UserPasswordResetSuccessEmail($event->token->getSubject()));
    }
}

Gotchas and Tips

Pitfalls

  1. Race Conditions:

    • Multi-use tokens use atomic increments to prevent overconsumption. Avoid manual UPDATE queries on the uses column.
    • Fix: Always use $tokenManager->consume() instead of raw SQL.
  2. Subject Deletion:

    • If a TokenSubject entity is deleted, resolveSubject() returns null. Handle this gracefully:
      $subject = $tokenManager->resolveSubject($token);
      if (!$subject) {
          throw new \RuntimeException('Subject no longer exists');
      }
      
  3. Token Length:

    • Default is 64 chars (secure). Reducing below 16 chars weakens security:
      # config/packages/token.yaml
      token:
          token_length: 32  # Minimum: 16
      
  4. QueryString Resolver:

    • Security Risk: Exposing tokens in URLs may leak them in logs/referrers.
    • Mitigation: Use HeaderTokenResolver for sensitive tokens (e.g., password resets).
  5. Bulk Revocation:

    • revokeAll() skips event dispatching for performance. Use revoke() for individual tokens if events are critical.

Debugging Tips

  • Token Not Found:

    • Verify the type matches exactly (case-sensitive).
    • Check if the token was revoked or consumed:
      try {
          $token = $tokenManager->get($tokenString, 'type');
      } catch (TokenRevokedException $e) {
          // Log or notify user
      }
      
  • Expired Tokens:

    • Ensure expiresIn uses valid DateInterval syntax (e.g., '+1 hour', '-5 minutes').
    • Test with TokenExpiredException in your error handling.
  • Payload Data:

    • Payloads are stored as JSON. Access them via:
      $data = json_decode($token->getPayload(), true);
      

Extension Points

  1. Custom Token Generators: Override the default random token generator:

    $tokenManager->setTokenGenerator(new CustomTokenGenerator());
    
  2. Token Resolvers: Create custom resolvers for non-standard token sources (e.g., cookies):

    class CookieTokenResolver implements TokenResolverInterface {
        public function resolve(Request $request): ?string {
            return $request->cookies->get('token');
        }
    }
    
  3. Token Storage: Extend the TokenRepository to add custom queries (e.g., find tokens by payload):

    $tokens = $tokenRepository->findByPayload(['key' => 'value']);
    
  4. Validation Logic: Add pre-consumption checks via a custom TokenValidator:

    $tokenManager->setValidator(new CustomTokenValidator());
    

Performance

  • Purge Command: Run token:purge periodically (e.g., via cron) to clean expired/consumed tokens:

    php bin/console token:purge --dry-run  # Test first
    
  • Indexing: Ensure the tokens table has indexes on:

    • subject_id + type (for findValid())
    • token (for get()/consume())
    • expires_at (for purging)

Security

  • Token Exposure: Avoid logging or storing tokens in plaintext. Use hashes for auditing:

    $this->logger->info('Token used', ['token_hash' => hash('sha256', $tokenString)]);
    
  • Sensitive Payloads: Avoid storing PII in token payloads. Use encrypted payloads if needed:

    $payload = $this->encoder->encode(['secret' => 'data']);
    $tokenManager->create(..., payload: $payload);
    
  • CSRF Protection: Combine tokens with CSRF tokens for forms:

    <form method="POST">
        <input type="hidden" name="token" value="{{ token.getToken() }}">
        <input type="hidden" name="_csrf_token" value="{{ csrf_token('reset_password') }}">
    </form>
    

```markdown
### Laravel-Specific Adaptations
While this bundle is Symfony-focused, Laravel developers can adapt it via:
1. **Symfony Bridge**:
   Use `symfony/http-foundation` and `symfony/event-dispatcher` as Laravel packages:
   ```bash
   composer require symfony/http-foundation symfony/event-dispatcher
  1. Service Container: Register the bundle’s services manually in config/app.php:

    'bindings' => [
        TokenManager::class => function ($app) {
            return new TokenManager(
                $app->make(TokenRepository::class),
                $app->make(TokenGenerator::class),
                $app->make(EventDispatcher::class)
            );
        },
    ],
    
  2. Route Protection: Replace #[RequiresToken] with a Laravel middleware:

    class TokenMiddleware {
        public function handle(Request $request, Closure $next) {
            $tokenResolver = new HeaderTokenResolver();
            $tokenString = $tokenResolver->resolve($request);
            $tokenManager = app(TokenManager::class);
    
            try {
                $token = $tokenManager->consume($tokenString, 'api_access');
                $request->attributes->add(['_token' => $token]);
                return $next($request);
            } catch (TokenAccessDeniedException $e) {
                abort(403, 'Invalid token');
            }
        }
    }
    
  3. Event Listeners:

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.
besmartand-pro/php-quality-config
sentix/ai-chatbot
terminal42/code-quality-tools
codifyo/ts-generator-bundle
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity