packagist gesdinet/jwt-refresh-token-bundle v2.2.0

latest releases: v2.2.2, v2.2.1
4 hours ago

Two features for keeping refresh tokens under control, one for documenting them, and a fix that made the recommended configuration work as written.

See UPGRADE-2.2.md before upgrading. One configuration is now rejected; everything else is additive or a correction.

An expired JWT can be exchanged

With jwt and refresh_jwt on the same firewall, the JWT authenticator was reached first and rejected the expired token before the refresh authenticator saw it — so the configuration in the README did not work as written, and the advice given for years, to reorder them on the firewall, could never have helped. Symfony orders authenticators by the priority each factory declares, not by the file. This one now sits above Lexik's.

Nothing to change. Applications that split the refresh endpoint into its own firewall to get around it can collapse it back.

Storing hashes instead of tokens

gesdinet_jwt_refresh_token:
    hash_tokens:
        enabled: true

A refresh token gets its holder back into an account without a password, so a copy of the table was a copy of everybody's credentials, next to the passwords that are hashed for exactly that reason. What is stored is now sha256$ and the hash, and a leaked table cannot be used.

Off by default, and turning it on signs nobody out: tokens already stored are taken as they are and rewritten hashed the first time they are used. getRefreshToken() then returns the stored hash, which is worth reading about in the upgrade guide first.

A limit on sessions per user

gesdinet_jwt_refresh_token:
    max_tokens_per_user: 5

Every login stores a token, deliberately, so that a user's devices are separate sessions. Nothing bounded how many accumulated. Signing in beyond the limit revokes the session that has gone longest without being refreshed, expired ones first.

Documented in API Platform

gesdinet_jwt_refresh_token:
    api_platform:
        enabled: true

Lexik documents the login endpoint, but its response schema only carries the JWT, because the refresh token beside it is added by this bundle. The refresh endpoint was documented by nobody, being a firewall authenticator rather than a controller. Both are covered now, from the bundle's own configuration — so with the cookie replacing the body, no refresh_token field is promised that never arrives.

Also

  • A DBAL backend (dbal_connection) storing tokens through a plain connection, with no object manager and no unit of work.
  • refresh_token_manager naming a manager of your own, wiring none of the bundle's storage, so the tokens can live in a PDO repository or anywhere else and Doctrine need not be installed at all.
  • single_use_ttl_update: false so a token issued in place of a single use one inherits the expiry of the one it replaced, bounding the chain.
  • ListRefreshTokenManagerInterface::findAllForUser() and revokeAllForUser() for showing a user their sessions and ending them.
  • One query per refresh instead of two.
  • Symfony 7.0 and 7.1 are installable; two components stopped at ^7.2 while the rest allowed ^7.0, and Composer resolves the intersection.
  • Fixes to the cookie expiry, logout invalidating somebody else's token, reading the token from a body with no Content-Type, delete() reporting the rows it actually removed, and cookie.same_site from an environment variable.

The issue tracker is empty as of this release: 64 issues were reviewed and closed, several of them fixed here.

Don't miss a new jwt-refresh-token-bundle release

NewReleases is sending notifications on new releases.