Skip to content

v3.0.0

Latest

Choose a tag to compare

@shakaran shakaran released this 05 Aug 15:18

Symfony 8, PHP 8.4 and LexikJWTAuthenticationBundle 3.

This release needs a schema change before the application will run. Refresh tokens gained family and family_valid columns, and Doctrine reads every mapped field.

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

Read UPGRADE-3.0.md first, and UPGRADE-RECTOR.md if you are coming from further back than 2.2.

If you are on Symfony 6.4 or 7.x, stay on 2.2. It is maintained, and Symfony 7.4 is supported until November 2028.

What a session is now

Refresh tokens belong to a chain. A token issued in place of another carries the family of the one it replaced, so a login and every refresh descending from it share one value — which is what makes a session addressable. Without it, "end this session" can only mean "delete this one token", and with single_use that token has usually already been replaced.

Everything below is built on that, and all of it is off by default.

  • reuse_detection — recognises a spent token being presented again and revokes the whole chain. Rotation on its own leaves a stolen token working until the legitimate client happens to refresh, and nobody learns why it broke. It cannot tell theft from a client racing itself, so it revokes either way and dispatches an event for the judgement it cannot make.
  • max_session_lifetime — a ceiling on how long a chain may go on for, whatever ttl says.
  • block_jwts_on_revocation — refuses the JWTs already issued to a user when revokeAllForUser() takes their refresh tokens away. Lexik's blocklist is keyed by jti and cannot do this; what is recorded instead is when the revocation happened.
  • SessionLister — the "where you are signed in" screen and the button next to each row, grouped by chain rather than by token.
  • rate_limiter — consumed before the token is looked at, so a refusal costs no query and its timing says nothing about whether the token exists.
  • cache_pool — stores the tokens in a PSR-6 pool, where expiry is the pool's job. It implements only what a pool can honour and says so about the rest.
  • Per-firewall configurationttl, single_use and the rest on the refresh_jwt authenticator, which is the longest-standing thing this bundle could not do.

Breaking changes

  • PHP 8.4, Symfony 8.0, LexikJWTAuthenticationBundle 3, doctrine/dbal 4
  • check_path is required on refresh_jwt
  • RefreshEvent takes the request, and $firewallName loses its default
  • dbal_columns, when configured, has to name the id column
  • Nine classes are final; the token models and repositories are deliberately not

The full list is in the changelog.