gesdinet/jwt-refresh-token-bundle
Implements a refresh token system over Json Web Tokens in Symfony
v3.0.0
18M downloads/mo
#1144 most downloaded on Packagist
markitosgv/JWTRefreshTokenBundle
What this package is like to depend on
Last release 18 days ago
05 Aug 2026
Release timing varies
gaps range from 2 weeks to 1.2 years
Rarely documented
notes for 13 of 54 stable releases
Nothing withdrawn
no release was ever pulled
11 years old
59 releases · first in 2015
8 releases in the last 12 months
see the full history below
Release timeline
59 releases · Oct 2015 to Aug 2026Releases
latest 59-
v3.0.005 Aug 2026Release notes
Open source →Symfony 8, PHP 8.4 and LexikJWTAuthenticationBundle 3.
This release needs a schema change before the application will run. Refresh tokens gained
familyandfamily_validcolumns, 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_usethat 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, whateverttlsays.block_jwts_on_revocation— refuses the JWTs already issued to a user whenrevokeAllForUser()takes their refresh tokens away. Lexik's blocklist is keyed byjtiand 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 configuration —
ttl,single_useand the rest on therefresh_jwtauthenticator, 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_pathis required onrefresh_jwtRefreshEventtakes the request, and$firewallNameloses its defaultdbal_columns, when configured, has to name theidcolumn- Nine classes are
final; the token models and repositories are deliberately not
The full list is in the changelog.
Release notes
Open source →See UPGRADE-3.0.md for what to check before upgrading, and UPGRADE-RECTOR.md if you are coming from further back than 2.2.
This release needs a schema change before the application will run. Refresh tokens gained
familyandfamily_validcolumns, and Doctrine reads every mapped field.Added
- Refresh tokens belong to a family: 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. That is what makes a session addressable — without it, "end this session" can only mean "delete this one token", which the next refresh has already replaced. Kept in
Model\FamilyAwareRefreshTokenInterfaceandModel\RefreshTokenFamilyTrait, separate fromRefreshTokenInterfaceso a token class of your own is untouched.Model\FamilyRefreshTokenManagerInterface::revokeFamily()revokes a whole chain reuse_detection, which recognises a single use refresh token being presented after it was spent and revokes the chain it belonged to. Rotation alone leaves a stolen token working until the legitimate client happens to refresh, and nobody learns why it broke; a spent token is deleted, so a replay is indistinguishable from any other unknown token unless spent ones are remembered. Off by default, refused withoutsingle_use, and it dispatchesRefreshTokenReuseDetectedEventbecause the bundle cannot tell theft from a client racing itselfmax_session_lifetime, a ceiling on how long a chain of refreshes may go on for, whateverttlsays. A ttl that starts over on every rotation means a session never ends. The deadline is set when a chain starts and carried along it unchangedblock_jwts_on_revocation, which refuses the JWTs already issued to a user whenrevokeAllForUser()takes their refresh tokens away. Lexik's blocklist cannot do this — it is keyed byjti, so it withdraws a token you are holding, and these are in clients — so what is recorded is when the revocation happened, per user, and any JWT issued at or before it is refused on decoderate_limiter, bounding how often the refresh endpoint will answer. Consumed before the token is looked at, so a refusal costs no query and its timing says nothing about whether the token exists. Refused requests answer429withRetry-After. Keyed by IP or by token, which is a trade-off rather than a detail. Needssymfony/rate-limiterSession\SessionLister, for showing a user where they are signed in and letting them end one. Grouping by chain is what turnsfindAllForUser()from a list of moments into a list of sessions.end()checks the chain belongs to the caller, since a session list is exactly where such an identifier gets handed outcache_pool, storing the tokens in a PSR-6 pool instead of a database. Expiry is then the pool's job, so nothing has to be scheduled to clear them. It implements only what a pool can honour, andmax_tokens_per_userandreuse_detectionare configuration errors alongside it rather than options that quietly do nothing- The refresh behaviour can be configured per firewall:
ttl,ttl_update,token_parameter_name,single_use,single_use_ttl_update,max_session_lifetime,max_tokens_per_user,return_expirationandreturn_expiration_parameter_nameon therefresh_jwtauthenticator. Every one defaults to null, meaning "whatever the bundle says", which is not the same as defaulting to its current value. Cookie settings stay global block_previous_jwt, which blocks the JWT a refresh replaces through LexikJWTAuthenticationBundle 3's blocklist, so refreshing no longer leaves the previous JWT usable for the rest of its lifetime. A request carrying no JWT, and a JWT that no longer parses, are left alone: an expired one is refused everywhere already. Off by default, and reported at compile time when Lexik'sblocklist_tokenis not on- Rector rule sets for every hop from 1.5 to 3.0, under
rector/sets, with the upgrade path in UPGRADE-RECTOR.md. Only the 1.5 to 2.0 set rewrites anything; the other three are empty and say why
Changed
- BC break: PHP 8.4 or later, Symfony 8.0 or later, and LexikJWTAuthenticationBundle 3. Symfony 6.4 and the 7 branch are dropped, which takes PHP 8.2 and 8.3 with them since Symfony 8 needs 8.4
- BC break:
check_pathis required on therefresh_jwtauthenticator. It defaulted to/login_check, Lexik's login path, which is never right for a refresh endpoint: left alone the authenticator took no requests and the router reported the refresh route as having no controller - BC break:
RefreshEventtakes the request the refresh was made with, and$firewallNameloses its default. Listeners gaingetRequest(); only code constructing the event is affected - BC break: doctrine/dbal 3 is dropped, along with the shims for
quoteIdentifier()andsetPrimaryKey() dbal_columns, when configured, has to name theidcolumn. A map without one produced a table whose expired tokens could never be revoked: batches are deleted by identifier, so with none to delete by,gesdinet:jwt:clearread the same batch forever- BC break: the exceptions, the bundle class, the failure response and the post-refresh security token are
final. The token models,AbstractRefreshTokenand the two repositories are deliberately left extendable, being the documented way to bring your own - Every file declares
strict_types, so the calls this bundle makes pass their arguments without coercion - The codebase uses the PHP 8.4 syntax its minimum already requires, and
rector.phpand.php-cs-fixer.phpnow keep it that way.rector/rectorhad been a development dependency for a long time with nothing configured to run it
Fixed
- DBAL index names include the table name.
UNIQ_REFRESH_TOKEN,IDX_USERNAMEandIDX_VALIDwere fixed whatever the table was called, and index names are scoped to the schema on PostgreSQL and to the whole database on SQLite — so a second table managed by the bundle could not be created, and the error named an index rather than anything identifying this bundle. Existing tables are untouched, since the schema is only built when absent Session\SessionListerkeys chains byarray-keyrather thanstring. A family is 32 hex characters, and PHP turns one that happens to be all digits into an integer key- The nineteen open code scanning alerts. Seven were real, including a missing mutation annotation on
RefreshTokenFamilyTraitthat stopped psalm's taint analysis reasoning about where a family came from, and four array shapes that were sealed promises about keys the method never looks at. The rest are by design or belong to Symfony, Doctrine and API Platform, and are suppressed inpsalm.xml.distscoped to the files they concern, each with the reason
-
v2.2.204 Aug 2026Release notes
Open source →2.2.1 did not actually fix #431 for everyone. If you are on Symfony 7.0, 7.1, 7.2 or 7.3, or on a patch of 6.4 older than 6.4.37 or of 8.0 older than 8.0.9, upgrade to this instead.
What 2.2.1 got wrong
The check on
ttlwas moved from avalidate()closure tomin(), becauseNumericNodeskipsmin()while it is handling the placeholder an environment variable stands in as.It does — in symfony/config 6.4.37, 7.4.9, 8.0.9 and 8.1 onwards. That skip was backported to the maintained branches only, and 7.0 through 7.3 are end of life, so they never received it and never will. On any of those, 2.2.1 rejected
%env(int:...)%exactly as 2.2.0 did.What this does
Neither
ttlnormax_tokens_per_useris validated in the configuration tree any more.On the affected versions there is no check that can tell the sample value of
0— which Symfony substitutes to see whether the configuration would accept the variable — from a0written by hand. Refusing to boot an application that reads its ttl from the environment is the worse of the two failures, so the check is gone rather than made conditional.A
ttlof0or less is therefore accepted again, as it was in 2.1.0 and earlier. It still produces a token that has expired by the time it is handed over, so it is worth not writing one.gesdinet_jwt_refresh_token: ttl: '%env(int:JWT_REFRESH_TOKEN_LIFETIME_IN_SECONDS)%'
works on every Symfony this release supports.
-
v2.2.104 Aug 2026Release notes
Open source →Fixes a regression in 2.2.0, reported by @dennismetz in #431.
ttlandmax_tokens_per_userfrom an environment variablegesdinet_jwt_refresh_token: ttl: '%env(int:JWT_REFRESH_TOKEN_LIFETIME_IN_SECONDS)%'
failed to compile with:
Invalid configuration for path "gesdinet_jwt_refresh_token.ttl": The "ttl" must be a positive number of seconds, and 0 is not.An environment variable is a placeholder at compile time, and Symfony compiles the configuration a second time with a sample value of the declared type in its place, to check it would be accepted. For an integer that sample is
0, and the check added in 2.2.0 rejects anything below1— so it rejected every integer environment variable, whatever the variable held.NumericNodeskipsmin()andmax()while it is handling a placeholder. Avalidate()closure is not skipped, which is why the built-in constraint is now used for both nodes. The validation itself is unchanged:0and negative values are still rejected when written literally.2.1.0 is not affected — the
ttlcheck did not exist before 2.2.0.Only
ttlandmax_tokens_per_userwere affected.default_invalid_batch_sizealready usedmin(), andcookie.same_sitewas fixed for the same underlying reason in 2.2.0.Release notes
Open source →Fixed
ttlandmax_tokens_per_usercan be read from an environment variable again. Both were checked with avalidate()closure, which rejects every%env(int:...)%put in front of them: the container is compiled a second time with a sample value of the declared type in place, and for an integer that sample is0.min()is skipped while a placeholder is being handled and a closure is not, so the built-in constraint is used instead. Reported as #431 against 2.2.0, where thettlcheck was introduced; 2.1.0 has no such check and is unaffected
-
v2.2.004 Aug 2026Release notes
Open source →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
jwtandrefresh_jwton 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_tokenfield 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_managernaming 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: falseso a token issued in place of a single use one inherits the expiry of the one it replaced, bounding the chain.ListRefreshTokenManagerInterface::findAllForUser()andrevokeAllForUser()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.2while 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, andcookie.same_sitefrom an environment variable.
The issue tracker is empty as of this release: 64 issues were reviewed and closed, several of them fixed here.
Release notes
Open source →Released 2026-08-04. See UPGRADE-2.2.md for what to check before upgrading.
Changed
- An expired JWT can be exchanged with
jwtandrefresh_jwton the same firewall. Symfony orders authenticators by the priority their factories declare rather than by the order written on the firewall, and this one sat below the JWT authenticator, which rejected the expired token before the refresh authenticator was reached. Reordering them insecurity.yamlnever had any effect. It now sits above it, and since it only takes over requests matching itscheck_path, nothing else changes - A refresh makes one query for the token rather than two. The authenticator loads it to authenticate with, and the listener loaded it again; Symfony puts the authenticated token in storage before calling the success handler, so it is already to hand. The value is compared rather than trusted on type alone, so a token left in storage by anything else is never acted on — which also means hashed storage, where the comparison cannot match, queries as it did before
object_managernaming a service that does not exist is reported as such, with the object managers there are, rather than as Symfony being unable to replace an alias with a definition. The usual cause is giving the name an entity manager is configured under instead of its service id, which the message now says- The refresh token is read from a JSON body whatever the request declares its content type to be. A client that sets no header, which is what
fetch()does when given none, or a proxy that strips it, was answered as though no token had been supplied delete()reports what the storage actually deleted rather than one row after reading the token back. Two callers racing for the same token were both told they had deleted it, which is the answer a single use token needs to tell them apart- Logging out invalidates the refresh token of the user logging out and not one belonging to somebody else, which is answered as a token that no longer exists. A request with no authenticated user still invalidates the token it carries
gesdinet:jwt:clearreports how many tokens it revoked and lists them only with-v. A run clearing a backlog revokes thousands, and listing them all buried the count- A
ttlof0or less is rejected. It describes a token that has expired by the time it is handed over, so every refresh made with one fails, and it is what an application reaching for a token that never expires tends to try first cookie.same_sitecan be read from an environment variable. It accepted a fixed list of words, and an environment variable is checked at compile time against an empty sample value of its type, so every variable put in front of it was rejected whatever it held. It now accepts whatSymfony\Component\HttpFoundation\Cookieitself documents: the three values in any case, or an empty one to leave the attribute off the cookie- The refresh token cookie expires when the token inside it does rather than a
ttlfrom when it was set. The two only ever agreed because the token was issued with a fullttl, whichsingle_use_ttl_updateno longer guarantees - The manager service is defined once by the backend in use.
config/services.phpalso defined it, naming a class it never imported and an object manager the DBAL backend does not have, which went unnoticed only because both backends overwrote it
Added
single_use_ttl_update, on by default, which keeps a token issued in place of a single use one starting its ttl over. Turned off, the replacement expires when the one it replaced would have, so refreshing cannot be chained indefinitely and the user signs in again attlafter the first token was issued- A DBAL backend, configured with
dbal_connection, storing the tokens through a plain connection rather than the ORM or the ODM. The table and its columns are named withdbal_table_nameanddbal_columns, anddbal_auto_create_tablecreates the table on the first request when a migration is not practical RevokeRefreshTokenManagerInterface, aliased to the manager so it can be injected by type, whoserevokeAllForUser()revokes every refresh token issued to a user, for a password reset or an account being disabled, and returns how many were revoked. It is deleted by the database, so no token is hydrated and no life-cycle event is raisedDeleteRefreshTokenRepositoryInterface::deleteByUser()backs it. Both are separate interfaces, so an existing manager or repository keeps working without themListRefreshTokenManagerInterface::findAllForUser(), which returns every refresh token issued to a user, the one expiring last first, for showing somebody the sessions they have open. Expired ones are included, since they are still rows, andisValid()tells them apartRevokeRefreshTokenManagerInterfaceandListRefreshTokenManagerInterfaceare aliased to the manager for every backend, the DBAL one included. Revoking by user was only offered to the ORM and the ODMhash_tokens, which storessha256$and the hash of a refresh token rather than the token, so a copy of the database cannot be used to refresh. Off by default. Turning it on signs nobody out: tokens already stored are taken as they are and rewritten hashed the first time they are used, untilaccept_stored_in_the_clearis turned off. Note thatgetRefreshToken()then returns the stored hash, since that is what is storedmax_tokens_per_user, a limit on how many refresh tokens a user may hold at once, which is a limit on signed-in devices since each login stores one. Signing in beyond it revokes the session that has gone longest without being refreshed, expired tokens first. Unlimited when not setRevokeRefreshTokenManagerInterface::revokeAllButNewestForUser()andDeleteRefreshTokenRepositoryInterface::deleteAllButNewestForUser()back itapi_platform.enabled, which documents the refresh token in the OpenAPI specification API Platform generates: therefresh_tokenLexik's login schema was missing, and the refresh endpoint nobody documented, one path per firewall the authenticator is on. It follows the bundle's own configuration, so the cookie replacing the body is documented as such rather than promising a field that never arrives. Off by default, since an application documenting it by hand would end up with it twicerefresh_token_manager, naming a service of your own, which replaces the manager the bundle would build and wires none of its storage, so the tokens can live in a PDO repository or anywhere else and Doctrine need not be installed at all.RefreshTokenManagerInterfaceis now held to the same test suite from outside the bundle, so it stays implementable
- A DBAL backend (
-
v2.1.003 Aug 2026Release notes
Open source →Fixes across the batch revocation, the MongoDB ODM and the values read from the request. Nothing has to be changed in an application using the bundle through its configuration and its services: see UPGRADE-2.1.md for the return values an application may be reading.
Fixed
revokeAllInvalidBatch()returned the last batch read, which is empty once the loop ends, so it always returned an empty array andgesdinet:jwt:clearreported that there was nothing to revoke after deleting tokensrevokeAllInvalidBatch()looped forever with the MongoDB ODM, as its condition tested the repository result withempty(), which is never true for the iterator the ODM returnsrevokeAllInvalidBatch()skipped expired tokens: each batch is deleted before the next is read, so the remaining tokens shift down and the offset has to stay where it is- The document repository reads its results through
Query::getIterator(), so they are the iterable the interface promises delete()returns0when the token is not in storage, which the ODM reported as1regardless- The success listener no longer brings the request down when a token has no expiration date
- A refresh token without a username is rejected with an
InvalidTokenExceptioninstead of aTypeErrorwhile building the passport refresh_token_classreports a configuration error when the class cannot be loaded, instead of aTypeErrorwhile building the containergesdinet:jwt:clearrejects a--batch-sizethat is not a positive number, which read no tokens and reported success while leaving every expired token in place- Both request extractors check what they read before returning it, and the authentication token checks the serialized state it is given
Changed
AuthenticationSuccessHandler::onAuthenticationSuccess()is typed?Response, matching the handler it decorates. What is returned at runtime has not changedRefreshTokenRepositoryInterfacedocuments, through a@methodtag, thatfindOneBy()takes an optional$orderByargument- The
phpconstraint is written as^8.2, the same minimum without claiming support for a future PHP 9
Quality
Test coverage is at 98%, PHPStan runs at level 10 and the security scan runs the project's own Psalm. The suite supports PHPUnit 10.5, 12 and 13.
Full Changelog: v2.0.0...v2.1.0
Release notes
Open source →Fixed
revokeAllInvalidBatch()returned the last batch read, which is empty once the loop ends, so it always returned an empty array andgesdinet:jwt:clearreported that there was nothing to revoke after deleting tokensrevokeAllInvalidBatch()looped forever with the MongoDB ODM, as its condition tested the repository result withempty(), which is never true for the iterator the ODM returnsrevokeAllInvalidBatch()skipped expired tokens: each batch is deleted before the next is read, so the remaining tokens shift down and the offset has to stay where it is- The document repository reads its results through
Query::getIterator(), so they are the iterable the interface promises delete()returns0when the token is not in storage, which the ODM reported as1regardless- The success listener no longer brings the request down when a token has no expiration date, and both of its checks for a usable token string now agree
- The logout listener clears the cookie on the response it just built rather than reading it back from the event
- A refresh token without a username is rejected with an
InvalidTokenExceptioninstead of aTypeErrorwhile building the passport refresh_token_classreports a configuration error when the class cannot be loaded, instead of aTypeErrorwhile building the containergesdinet:jwt:clearrejects a--batch-sizethat is not a positive number, which read no tokens and reported success while leaving every expired token in place- Both request extractors check what they read before returning it, and
PostRefreshTokenAuthenticationTokenchecks the serialized state it is given
Changed
AuthenticationSuccessHandler::onAuthenticationSuccess()is typed?Response, matching the handler it decorates. What is returned at runtime has not changedRefreshTokenRepositoryInterfacedocuments, through a@methodtag, thatfindOneBy()takes an optional$orderByargument- The
phpconstraint is written as^8.2, the same minimum without claiming support for a future PHP 9 - Dropped the compatibility shims for Symfony versions below 6.4, which is already the minimum
See UPGRADE-2.1.md for the details.
-
v2.0.029 Dec 2025Release notes
Open source →What's Changed
- [1.x] Add forward compat layer for changed logout listener configuration by @mbabker in #350
- 2.0 Proposal by @mbabker in #347
- Use modern filesystem layout by @mbabker in #401
- Use PHPUnit 10 only by @mbabker in #400
- Adjust setup for the ORMTestCase to avoid some deprecations with ORM 3.4 by @mbabker in #399
- Improve the batch cleanup implementation by @mbabker in #403
- feat: implement delete with return number deletions. Issue #404 by @shakaran in #405
- Add support for Symfony 8 by @Kyzegs in #415
- fix: request->get is gone, bc fallback by @endroid in #417
- build(deps-dev): update rector/jack requirement from ^0.2.5 to ^0.5.0 by @dependabot[bot] in #420
- feat: bump rector version 2.2 from 2.1 by @shakaran in #421
New Contributors
- @Kyzegs made their first contribution in #415
- @dependabot[bot] made their first contribution in #420
Full Changelog: v1.5.0...v2.0.0
Release notes
Open source →- [B/C Break] Removed the
Gesdinet\JWTRefreshTokenBundle\EventListener\LogoutEventListenerservice definition; if needed, an abstractgesdinet_jwt_refresh_token.security.listener.logoutdefinition replaces it and does not have akernel.event_listenertag - [B/C Break] The
logout_firewallconfig node default value is now null - Deprecated the
logout_firewallconfig node, theinvalidate_token_on_logoutoption should be set on therefresh_jwtauthenticator - Added support for
doctrine/persistence4.0
-
2.0.0-beta29 Dec 2025 pre-releaseRelease notes
Open source →What's Changed
- [1.x] Add forward compat layer for changed logout listener configuration by @mbabker in #350
- 2.0 Proposal by @mbabker in #347
- Use modern filesystem layout by @mbabker in #401
- Use PHPUnit 10 only by @mbabker in #400
- Adjust setup for the ORMTestCase to avoid some deprecations with ORM 3.4 by @mbabker in #399
- Improve the batch cleanup implementation by @mbabker in #403
- feat: implement delete with return number deletions. Issue #404 by @shakaran in #405
- Add support for Symfony 8 by @Kyzegs in #415
- fix: request->get is gone, bc fallback by @endroid in #417
- build(deps-dev): update rector/jack requirement from ^0.2.5 to ^0.5.0 by @dependabot[bot] in #420
- feat: bump rector version 2.2 from 2.1 by @shakaran in #421
New Contributors
Full Changelog: v1.5.0...v2.0.0-beta
This is a pre-release as non-production ready for testing. Bugs or testing is welcome
-
v1.5.103 Aug 2026Release notes
Open source →Fixed
-
gesdinet:jwt:clearcrashed witharray_map(): Argument #2 ($array) must be of type array, Doctrine\ODM\MongoDB\Iterator\CachingIterator givenwhen using the MongoDB ODM.The ORM repository returns an array, but the ODM one returns a
CachingIterator, andRefreshTokenManager::revokeAllInvalid()passed it straight through to the command. It now always returns theRefreshTokenInterface[]documented byRefreshTokenManagerInterface, whatever the repository returns.
Reported in #424 by @melkamar.
Full Changelog: v1.5.0...v1.5.1
-
-
v1.5.024 Jun 2025Release notes
Open source →What's Changed
- Update CI to run against PHP 8.4 and latest Symfony release by @mbabker in #390
- Fix Deprecation Notice in RefreshTokenAuthenticator::start Method by @gzim324 in #389
- Allow doctrine/persistence 4.0 by @fmata in #397
- feat: import namespaces classes via rector by @shakaran in #380
- Update documentation (#316) by @sebastianstucke87 in #318
New Contributors
- @gzim324 made their first contribution in #389
- @fmata made their first contribution in #397
- @shakaran made their first contribution in #380
- @sebastianstucke87 made their first contribution in #318
Full Changelog: v1.4.0...v1.5.0
-
v1.4.023 Nov 2024 -
v1.3.010 Jan 2024 -
v1.2.126 Dec 2023Nothing published for this version
-
v1.2.011 Nov 2023Release notes
Open source →- Added support for
LexikJWTAuthenticationBundle3.0 - Added support for Symfony 7.0
- Added support for
-
v1.1.301 Jul 2023Nothing published for this version
-
v1.1.228 Jun 2023Nothing published for this version
-
v1.1.111 Apr 2022Nothing published for this version
-
v1.1.011 Apr 2022Release notes
Open source →- [B/C Break] Changed the object mappings to mapped superclasses, this requires updating your app's configuration
- Added support for checking the request path in the
refresh_jwtauthenticator - Deprecated not configuring the request path to check in the
refresh_jwtauthenticator - Added feature to add the expiration timestamp on the response
-
v1.0.125 Jan 2022Nothing published for this version
-
v1.0.011 Jan 2022Release notes
Open source →- Dropped support for MongoDB ODM 1.x
- Dropped support for Symfony 3.4
- Added support for Symfony 6.0
- Added a LogoutEventListener that will invalidate the supplied refresh token and clear the cookie (if configured) when a LogoutEvent is triggered on the configured firewall.
-
v1.0.0-beta429 Jul 2021 pre-releaseRelease notes
Open source →- Added
Gesdinet\JWTRefreshTokenBundle\Doctrine\RefreshTokenRepositoryInterface Gesdinet\JWTRefreshTokenBundle\Doctrine\RefreshTokenManagernow requires all object repositories implementGesdinet\JWTRefreshTokenBundle\Doctrine\RefreshTokenRepositoryInterface
- Added
-
v1.0.0-beta319 Jul 2021 pre-releaseNothing published for this version
-
v1.0.0-beta212 Jul 2021 pre-releaseRelease notes
Open source →- Added
Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterfaceas an interface for extracting the refresh token from the request, implementations provided by this bundle include:Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ChainExtractor- Calls all registered extractors to find the request token (by default, this extractor is aliased to the interface in the DI container)Gesdinet\JWTRefreshTokenBundle\Request\Extractor\RequestBodyExtractor- Decodes a JSON request body and loads the token from itGesdinet\JWTRefreshTokenBundle\Request\Extractor\RequestParameterExtractor- Loads the refresh token by calling$request->get()
- Removed the
Gesdinet\JWTRefreshTokenBundle\Request\RequestRefreshTokenclass, aGesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterfaceimplementation should be used instead Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterfacenow extendsStringable, refresh token models now require a__toString()method
- Added
-
v1.0.0-beta01 Jul 2021 pre-releaseNothing published for this version
-
v0.12.023 Apr 2021Nothing published for this version
-
v0.11.105 Mar 2021Nothing published for this version
-
v0.10.104 Jan 2021Nothing published for this version
-
v0.10.023 Dec 2020Nothing published for this version
-
v0.9.104 Feb 2020Nothing published for this version
-
v0.9.005 Dec 2019Nothing published for this version
-
v0.8.312 Nov 2019Nothing published for this version
-
v0.8.214 Oct 2019Nothing published for this version
-
v0.8.110 Oct 2019Nothing published for this version
-
v0.7.103 Jul 2019Nothing published for this version
-
v0.7.001 Jul 2019Nothing published for this version
-
v0.6.204 Apr 2019Nothing published for this version
-
v0.6.104 Apr 2019Nothing published for this version
-
v0.6.022 Feb 2019Nothing published for this version
-
v0.5.406 Dec 2018Nothing published for this version
-
v0.5.319 Sep 2018Nothing published for this version
-
v0.5.218 Sep 2018Nothing published for this version
-
v0.5.117 Sep 2018Nothing published for this version
-
v0.5.011 Sep 2018Nothing published for this version
-
v0.4.026 Apr 2018Nothing published for this version
-
v0.3.331 Mar 2018Nothing published for this version
-
v0.3.220 Jan 2018Nothing published for this version
-
v0.3.119 Jan 2018Nothing published for this version
-
v0.3.016 Jan 2018Nothing published for this version
-
v0.2.125 Jul 2017Nothing published for this version
-
v0.2.011 Feb 2017Nothing published for this version
-
v0.1.824 Jul 2016Nothing published for this version
-
v0.1.712 Apr 2016Nothing published for this version
-
v0.1.607 Apr 2016Nothing published for this version
-
v0.1.516 Mar 2016Nothing published for this version
-
v0.1.404 Feb 2016Nothing published for this version
-
v0.1.318 Dec 2015Nothing published for this version
-
v0.1.216 Dec 2015Nothing published for this version
-
v0.1.111 Oct 2015Nothing published for this version
-
v0.1105 Mar 2021Nothing published for this version
-
v0.101 Oct 2015Nothing published for this version