gesdinet/jwt-refresh-token-bundle
Symfony bundle for managing JWT refresh tokens with LexikJWTAuthenticationBundle. Provides secure refresh token handling, a configurable endpoint, Doctrine ORM/ODM support, and easy integration with Symfony security. Requires PHP 8.2+ and Symfony 6.4/7.2+/8.0+.
Verdict up front: this is not a Laravel package. gesdinet/jwt-refresh-token-bundle is a Symfony bundle. It extends symfony/security-bundle's firewall/authenticator system and requires LexikJWTAuthenticationBundle plus a Doctrine ORM/ODM ObjectManager. Nothing in it loads under Laravel's service container, its auth guards, or Eloquent. For a Laravel codebase, architecture fit is effectively zero without a rewrite; for a Symfony 6.4/7.2/8.0 stack it is a strong fit. Treat that distinction as the first decision gate, not a footnote.
Architecture fit (Symfony target). It slots in as a security authenticator on the same firewall as json_login and jwt, and Symfony orders it ahead of the JWT authenticator regardless of YAML order — which is precisely what allows an expired access token to be exchanged. It only claims requests matching check_path, so all other traffic authenticates normally. That is a clean, idiomatic seam: stateless short-lived JWT for authorization, a database-backed opaque-ish refresh record for revocation. The pattern is sound and worth copying conceptually even if the code isn't reusable.
Architecture fit (Laravel target). The concepts map cleanly (a refresh_tokens table, a rotation endpoint, a cleanup command, a guard/middleware), the implementation does not. A Laravel team would be reinventing roughly 300–600 lines of first-party code. Value extracted here is design guidance, not a dependency.
Integration feasibility. Good on Symfony, provided prerequisites land first: PHP ≥ 8.2, a Doctrine mapping strategy, and a firewall you control. The documented sharp edges are all integration-surface issues, not core design issues, and all have written fixes:
check_path must agree with the route; a mismatch yields "Unable to find the controller for path…" which misdirects you to the route.auto_mapping: false or multiple entity managers silently break inheritance of the mapped superclass unless you declare the bundle's mapping explicitly.doctrine:migrations:diff compares the whole database; without schema_filter it will emit drops for tables Doctrine doesn't own. This is the single scariest line in the README and it is not the bundle's bug — but it is the bundle's blast radius.Technical risk.
0/negative values. That is a maturity signal, and it also means the product decision (how long may a user be absent?) is yours to make, not the library's.Key questions.
Stack fit.
laravel/passport if OAuth2 grant semantics are genuinely required, or a maintained JWT package if stateless access tokens are mandated — vetting that package's maintenance the way we vetted this one.Migration path (Symfony 1.x → 3.0). Follow UPGRADE-RECTOR.md one release at a time rather than jumping. Rector handles the renames; the checklist covers what it can't — notably the 2.0 route change (no controller; the authenticator answers the route) and firewall check_path alignment. Do this on a branch with schema diffing disabled in CI until you've verified no destructive migration is emitted.
Compatibility. Pin to a minor (~3.x) and read the CHANGELOG at each bump. Rector + PHPStan 10 upstream means the codebase should be static-analysis-clean in your pipeline too — cheap signal that you're on a supported path.
Sequencing.
doctrine.dbal.schema_filter before generating any migration, then review the generated SQL line by line. If auto_mapping: false or multiple entity managers apply, wire the bundle's mapping explicitly.refresh_token_class, ttl, rotation settings, object_manager if non-default.refresh_jwt on the firewall with matching check_path, PUBLIC_ACCESS in access_control for login and refresh paths.gesdinet:jwt:clear, add table indexes, add alerting on refresh failure rate.check_path/route coupling and the migration caveat — these are the two things every future engineer will trip on.Maintenance. Low code churn, high version discipline. Budget roughly half a day per major upgrade plus test-cycle time; the UPGRADE docs make this predictable, which is the main reason the maintenance cost is defensible despite the bus factor. Vendor the version in composer.lock and treat upgrades as scheduled work, not reactive patching.
Support. Small community, single maintainer, issue volume that won't scale with your needs. Assume you own the failure modes: designate an internal owner (one backend engineer) who understands the authenticator priority and check_path coupling. External support is documentation-only.
Scaling. The refresh-token table grows monotonically with sessions — one row per active session, rotated on each use if single-use is enabled. That means row churn, not just growth. Concretely:
gesdinet:jwt:clear is expiry-driven and will table-scan without it.Failure modes to instrument.
check_path/route drift after a routing refactor, or a bad ttl deploy.schema_filter absent in a database Doctrine doesn't fully own. This one is unrecoverable without backups; gate it in review.Ramp-up. A mid-level Symfony engineer integrates this in about a day following the README; add one to two days for rotation testing, cleanup scheduling, and the firewall/route fiddliness. Call it 2–3 engineer-days to production-ready with tests. The conceptual model (short access token + revocable rotating refresh credential) transfers directly to any other stack, so that learning isn't wasted even if the dependency is later replaced.
How can I help you explore Laravel packages today?