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

Jwt Refresh Token Bundle Laravel Package

gesdinet/jwt-refresh-token-bundle

Symfony bundle to manage JWT refresh tokens for LexikJWTAuthenticationBundle. Stores and rotates refresh tokens via Doctrine ORM or MongoDB ODM (or custom object manager), with configurable TTL, security, and endpoints. Requires PHP 8.2+ and Symfony 6.4+.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to First Use

  1. Install Dependencies

    composer require doctrine/orm doctrine/doctrine-bundle gesdinet/jwt-refresh-token-bundle
    
  2. Configure the Bundle Create config/packages/gesdinet_jwt_refresh_token.yaml:

    gesdinet_jwt_refresh_token:
        refresh_token_class: App\Entity\RefreshToken
    
  3. Generate the RefreshToken Entity

    php bin/console make:entity RefreshToken
    

    Use the provided base class:

    // src/Entity/RefreshToken.php
    namespace App\Entity;
    use Doctrine\ORM\Mapping as ORM;
    use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken as BaseRefreshToken;
    #[ORM\Entity]
    #[ORM\Table(name: 'refresh_tokens')]
    class RefreshToken extends BaseRefreshToken {}
    
  4. Set Up Routes Add to config/routes.yaml:

    api_refresh_token:
        path: /api/token/refresh
    
  5. Configure Security Update config/packages/security.yaml:

    security:
        firewalls:
            api:
                pattern: ^/api
                stateless: true
                entry_point: jwt
                json_login:
                    check_path: /api/login
                jwt: ~
                refresh_jwt:
                    check_path: api_refresh_token
    
  6. Run Migrations

    php bin/console make:migration
    php bin/console doctrine:migrations:migrate
    

First Use Case

Send a POST request to /api/login with user credentials to receive an initial JWT and refresh token. Use the refresh token to call /api/token/refresh to get a new JWT without re-authenticating.


Implementation Patterns

Token Flow Workflow

  1. Authentication

    • User logs in via /api/login (using json_login).
    • Response includes:
      {
        "token": "jwt_token_here",
        "refresh_token": "refresh_token_here"
      }
      
  2. Token Refresh

    • Client sends a POST request to /api/token/refresh with the refresh_token in the request body.
    • Response includes a new JWT and optionally a new refresh token (if single_use: true is enabled).
  3. Token Invalidation

    • Call /api/token/invalidate to invalidate the refresh token (e.g., during logout).

Integration Tips

  • Custom Token Storage Use the cookie configuration to store refresh tokens in HTTP-only cookies for enhanced security:

    gesdinet_jwt_refresh_token:
        cookie:
            enabled: true
            http_only: true
            secure: true
    
  • Single-Use Tokens Enable single-use tokens to improve security:

    gesdinet_jwt_refresh_token:
        single_use: true
    
  • Custom Refresh Token Class Extend the base RefreshToken class to add custom fields or logic:

    // src/Entity/CustomRefreshToken.php
    namespace App\Entity;
    use Doctrine\ORM\Mapping as ORM;
    use Gesdinet\JWTRefreshTokenBundle\Entity\RefreshToken;
    #[ORM\Entity]
    #[ORM\Table(name: 'custom_refresh_tokens')]
    class CustomRefreshToken extends RefreshToken {
        #[ORM\Column(type: 'string', nullable: true)]
        private ?string $customField = null;
    }
    

    Update the bundle config:

    gesdinet_jwt_refresh_token:
        refresh_token_class: App\Entity\CustomRefreshToken
    
  • Token TTL Management Configure token expiration and auto-refresh:

    gesdinet_jwt_refresh_token:
        ttl: 2592000  # 1 month in seconds
        ttl_update: true  # Auto-update TTL on refresh
    
  • Event Listeners Listen to RefreshTokenEvent to customize token behavior:

    // src/EventListener/RefreshTokenListener.php
    namespace App\EventListener;
    use Gesdinet\JWTRefreshTokenBundle\Event\RefreshTokenEvent;
    use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
    #[AsEventListener(event: RefreshTokenEvent::class, method: 'onRefreshToken')]
    class RefreshTokenListener {
        public function onRefreshToken(RefreshTokenEvent $event) {
            // Custom logic here
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Missing Doctrine Dependencies Forgetting to install doctrine/orm or doctrine/mongodb-odm will cause installation errors. Ensure dependencies are included:

    composer require doctrine/orm doctrine/doctrine-bundle
    
  2. Incorrect Route Configuration The refresh_jwt firewall configuration must match the route name (api_refresh_token). Mismatches will result in 404 errors.

  3. Token Parameter Name Mismatch The default refresh_token parameter name must match the request payload. Customize it if needed:

    gesdinet_jwt_refresh_token:
        token_parameter_name: custom_refresh_token
    
  4. Single-Use Tokens and Auto-Refresh If single_use: true is enabled, ensure your frontend handles the new refresh token in the response to avoid token reuse issues.

  5. Cookie Configuration Conflicts When using cookies, ensure secure: true and http_only: true are set for HTTPS environments. Misconfigurations can lead to security vulnerabilities.

  6. Logout Event Listener Not Triggering The LogoutEventListener requires a logout path to be defined in the firewall. Without it, refresh tokens won’t be invalidated on logout:

    security:
        firewalls:
            api:
                logout:
                    path: api_token_invalidate
    

Debugging Tips

  1. Check Token Existence Use Doctrine queries to verify refresh tokens are stored:

    $refreshToken = $entityManager->getRepository(RefreshToken::class)->findOneBy(['token' => $token]);
    
  2. Enable Debugging for Events Listen to RefreshTokenEvent to debug token refresh logic:

    #[AsEventListener(RefreshTokenEvent::class)]
    public function onRefreshToken(RefreshTokenEvent $event) {
        dump($event->getRefreshToken());
    }
    
  3. Validate Token Payload Ensure the JWT payload includes the username or user_id claim, as the bundle relies on it to fetch the user:

    {
      "username": "user@example.com",
      "exp": 1234567890
    }
    
  4. Clear Cache After Configuration Changes After modifying gesdinet_jwt_refresh_token.yaml, clear the Symfony cache:

    php bin/console cache:clear
    

Extension Points

  1. Custom User Provider Override the default user provider for complex user logic:

    security:
        firewalls:
            api:
                refresh_jwt:
                    provider: custom_user_provider
    
  2. Custom Token Generator Extend the Gesdinet\JWTRefreshTokenBundle\Services\RefreshTokenGenerator service to generate tokens with custom logic.

  3. Custom Validation Logic Implement a custom UserCheckerInterface to validate users during token refresh:

    use Symfony\Component\Security\Core\User\UserCheckerInterface;
    use Symfony\Component\Security\Core\User\UserInterface;
    class CustomUserChecker implements UserCheckerInterface {
        public function checkPreAuth(UserInterface $user) {
            // Custom validation logic
        }
        public function checkPostAuth(UserInterface $user) {}
    }
    

    Register it in security.yaml:

    security:
        firewalls:
            api:
                user_checker: custom_user_checker
    
  4. Custom Serializer for Responses Override the default JSON response format by extending the Gesdinet\JWTRefreshTokenBundle\Serializer\RefreshTokenSerializer.

  5. Database Schema Customization Modify the RefreshToken entity to add custom columns or constraints:

    #[ORM\Table(name: 'refresh_tokens', options: ['charset' => 'utf8mb4'])]
    class RefreshToken extends BaseRefreshToken {
        #[ORM\Column(type: 'datetime', nullable: true)]
        private ?\DateTimeInterface $createdAt = null;
    }
    
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.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
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
christhompsontldr/laravel-inky
spatie/mailcoach-vapor