PHPackages                             dimkinthepro/jwt-auth-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. dimkinthepro/jwt-auth-bundle

ActiveSymfony-bundle[Authentication &amp; Authorization](/categories/authentication)

dimkinthepro/jwt-auth-bundle
============================

This bundle provides JWT authentication

0.3.1(2w ago)014MITPHPPHP &gt;=8.4

Since Jul 6Pushed 2w ago1 watchersCompare

[ Source](https://github.com/dimkinthepro/jwt-auth-bundle)[ Packagist](https://packagist.org/packages/dimkinthepro/jwt-auth-bundle)[ Docs](https://github.com/dimkinthepro/jwt-auth-bundle)[ RSS](/packages/dimkinthepro-jwt-auth-bundle/feed)WikiDiscussions master Synced 2w ago

READMEChangelog (5)Dependencies (42)Versions (6)Used By (0)

JWT authentication bundle for Symfony
=====================================

[](#jwt-authentication-bundle-for-symfony)

### 1. Installation:

[](#1-installation)

```
composer require dimkinthepro/jwt-auth-bundle
```

### 2. Check bundles config:

[](#2-check-bundles-config)

```
# config/bundles.php

return [
#...
    Dimkinthepro\JwtAuth\DimkintheproJwtAuthBundle::class => ['all' => true],
];
```

### 3. Create bundle configuration:

[](#3-create-bundle-configuration)

```
# config/packages/dimkinthepro_jwt_auth.yaml
dimkinthepro_jwt_auth:
    public_key_path: '%kernel.project_dir%/var/dimkinthepro/jwt-auth-bundle/public.pem'
    private_key_path: '%kernel.project_dir%/var/dimkinthepro/jwt-auth-bundle/private.pem'
    passphrase: 'SomeRandomPassPhrase' # required, keep it stable: changing it invalidates all issued tokens
    token_ttl: 900 # 15 minutes
    algorithm: 'RS512'
    refresh_token_ttl: 2592000 # 1 month
    refresh_token_length: 128 # random bytes of token entropy; only a sha256 hash is stored in the DB
    issuer: 'my-app' # optional "iss" claim; omitted and not validated when null
    audience: 'my-client' # optional "aud" claim; omitted and not validated when null
    clock_skew_leeway: 60 # tolerated clock skew in seconds for "exp"/"nbf"/"iat" validation
    blocklist: # instant access token revocation by the "sid" claim
        enabled: false # costs one cache lookup per authenticated request
        cache_pool: 'cache.app' # PSR-6 pool; entries expire together with the tokens they block
    token_extractors: # enabled extractors are chained in this priority order
        authorization_header:
            enabled: true # "Authorization: Bearer "
        split_cookie:
            enabled: false # "header.payload" in a JS-readable cookie + signature in an HttpOnly cookie
            payload_cookie_name: 'jwt_hp'
            signature_cookie_name: 'jwt_sig'
        cookie:
            enabled: false # whole token in a single cookie
            name: 'jwt_token'
        query_parameter:
            enabled: false # for WebSocket/SSE only: tokens in URLs leak into access logs
            name: 'jwt_token'
```

### 4. Add security configuration

[](#4-add-security-configuration)

```
# config/packages/security.yaml

security:
  #...
  main:
      lazy: true
      auth_jwt: ~
      pattern: ^/api/
      stateless: true
      provider: your_app_user_provider
      json_login:
          check_path: /api/user/login
          username_path: email
          success_handler: Dimkinthepro\JwtAuth\Infrastructure\Security\SuccessAuthenticationHandler
          failure_handler: Dimkinthepro\JwtAuth\Infrastructure\Security\FailAuthenticationHandler
```

### 5. Add doctrine configuration

[](#5-add-doctrine-configuration)

```
# config/packages/doctrine.yaml
doctrine:
    #...
    orm:
        #...
        mappings:
            #...
            DimkintheproJwtAuthBundle:
                is_bundle: true
                type: xml
                prefix: Dimkinthepro\JwtAuth\Domain\Entity
```

### 6. Add Routes

[](#6-add-routes)

```
# config/routes.yaml
api_login:
  path: /api/login
  methods: [POST]

api_token_refresh:
  path: /api/token-refresh
  controller: Dimkinthepro\JwtAuth\Infrastructure\Controller\TokenRefreshAction
  methods: [POST]

api_sessions_list:
  path: /api/sessions
  controller: Dimkinthepro\JwtAuth\Infrastructure\Controller\SessionListAction
  methods: [GET]

api_session_revoke:
  path: /api/sessions/{sessionId}
  controller: Dimkinthepro\JwtAuth\Infrastructure\Controller\SessionRevokeAction
  methods: [DELETE]
```

### 7. Generate migrations:

[](#7-generate-migrations)

```
php bin/console doctrine:migrations:diff

php bin/console doctrine:migrations:migrate
```

### 8. Generate key pair:

[](#8-generate-key-pair)

```
php bin/console dimkinthepro:jwt-auth:generate-key-pair
```

### 9. Schedule expired refresh tokens purge (e.g. daily cron):

[](#9-schedule-expired-refresh-tokens-purge-eg-daily-cron)

```
php bin/console dimkinthepro:jwt-auth:purge-expired-refresh-tokens
```

### 10. Add custom JWT claims (optional):

[](#10-add-custom-jwt-claims-optional)

Listen to `JwtTokenCreatedEvent` — reserved claims (`identifier`, `iat`, `exp`) cannot be overridden.

```
use Dimkinthepro\JwtAuth\Application\Component\Event\JwtTokenCreatedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
class UserRolesClaimsListener
{
    public function __invoke(JwtTokenCreatedEvent $event): void
    {
        $event->setClaims($event->getClaims() + ['role' => 'admin']);
    }
}
```

Read the claims back from the verified token:

```
use Dimkinthepro\JwtAuth\Application\UseCase\JwtToken\JwtTokenDecoder;

$jwtToken = $jwtTokenDecoder->decodeTokenFromString($encodedToken);
$role = $jwtToken->getClaim('role');
```

### 11. Hook into the token lifecycle with events (optional):

[](#11-hook-into-the-token-lifecycle-with-events-optional)

EventWhenWhat listeners can do`JwtTokenCreatedEvent`before the token is signedadjust claims (`getClaims()`/`setClaims()`)`JwtTokenDecodedEvent`after a token passed validationrun extra checks, `markAsInvalid()` to reject`JwtTokenAuthenticatedEvent`request authenticated with a JWTadd passport attributes from token claims`JwtAuthenticationSuccessEvent`successful login, before the responseenrich response data (`getData()`/`setData()`)`JwtTokenNotFoundEvent`protected endpoint hit without a tokenreplace the default 401 response`JwtTokenInvalidEvent`authentication failed: bad tokenreplace the default 401 response`JwtTokenExpiredEvent`authentication failed: expired tokenreplace the default 401 responseThe header and the reserved claims (`identifier`, `iat`, `exp`) cannot be changed from listeners.

```
use Dimkinthepro\JwtAuth\Infrastructure\Event\JwtAuthenticationSuccessEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

#[AsEventListener]
class EnrichLoginResponseListener
{
    public function __invoke(JwtAuthenticationSuccessEvent $event): void
    {
        $event->setData($event->getData() + ['userEmail' => $event->getUser()->getUserIdentifier()]);
    }
}
```

### 12. Device sessions:

[](#12-device-sessions)

Every refresh token represents a device session. On login the bundle captures the optional `deviceName` field of the JSON body (native clients know their exact model), the `User-Agent`header and the client IP:

```
{ "email": "user@example.com", "password": "...", "deviceName": "iPhone 13 Pro" }
```

The session identity (`sessionId`, `createdAt`, `deviceName`) survives token rotation; `lastUsedAt` is updated on every refresh, and each issued JWT carries its session id in the `sid` claim.

`GET /api/sessions` (authenticated) returns the devices of the current user, marking the session the request was made from:

```
{ "data": { "sessions": [ {
    "sessionId": "1f0d…", "deviceName": "iPhone 13 Pro", "userAgent": "…", "ip": "…",
    "createdAt": "2026-07-06T10:00:00+00:00", "lastUsedAt": "2026-07-06T12:30:00+00:00",
    "current": true
} ] } }
```

`DELETE /api/sessions/{sessionId}` revokes a session (204; foreign or unknown ids give 404), `DELETE /api/sessions` revokes every session of the user (e.g. on account compromise).

Without the blocklist a revoked device keeps access until its short-lived JWT expires; with `blocklist.enabled: true` the outstanding access tokens die instantly.

With the blocklist enabled every token must carry the `sid` claim: a token without a session id could never be revoked, so it is rejected. Tokens issued by the login and refresh endpoints always have it; when creating tokens manually, pass a session id to `JwtTokenManager::create()`.

### 13. Split cookies for browser SPAs (optional):

[](#13-split-cookies-for-browser-spas-optional)

Enable the `split_cookie` extractor and set the cookies on login with a `kernel.response` listener — the signature cookie is `HttpOnly`, so an XSS attack can never read a complete usable token:

```
use Symfony\Component\HttpFoundation\Cookie;

$signatureOffset = (int) strrpos($encodedToken, '.');
$response->headers->setCookie(
    Cookie::create('jwt_hp', substr($encodedToken, 0, $signatureOffset))->withHttpOnly(false)->withSecure(true)
);
$response->headers->setCookie(
    Cookie::create('jwt_sig', substr($encodedToken, $signatureOffset + 1))->withHttpOnly(true)->withSecure(true)
);
```

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance97

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity45

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~174 days

Total

5

Last Release

16d ago

PHP version history (2 changes)1.0.0PHP &gt;=8.1

0.1.0PHP &gt;=8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/8753e58b7caa98494a6c70f4870cb382ec93566e7839def58e8c050624e398dd?d=identicon)[dimkin.the.pro](/maintainers/dimkin.the.pro)

---

Top Contributors

[![dimkinthepro](https://avatars.githubusercontent.com/u/104203723?v=4)](https://github.com/dimkinthepro "dimkinthepro (10 commits)")

---

Tags

jwtjwt-authjwt-authenticationjwt-tokenphp81symfony-bundlejwtsymfonybundle

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/dimkinthepro-jwt-auth-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/dimkinthepro-jwt-auth-bundle/health.svg)](https://phpackages.com/packages/dimkinthepro-jwt-auth-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k18.3M429](/packages/easycorp-easyadmin-bundle)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k6.0M777](/packages/sylius-sylius)[chameleon-system/chameleon-base

The Chameleon System core.

1029.4k6](/packages/chameleon-system-chameleon-base)[contao/core-bundle

Contao Open Source CMS

1301.7M3.1k](/packages/contao-core-bundle)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M235](/packages/sulu-sulu)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M672](/packages/shopware-core)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
