Minimal-dependency JWT authentication for first-party Laravel apps (Laravel 12/13) — "first-party" meaning you own both the client and the API, so there's no third party to delegate to and no OAuth ceremony to perform.
Unofficial, independent package. Not affiliated with, endorsed by, or maintained by the Laravel team. "Laravel", "Sanctum", and "Fortify" are referenced only to describe compatibility and design influence; they are trademarks of their respective owners.
Pre-1.0 — expect breaking changes. lukk is in the
0.xseries. The core is production-minded and fully tested (100% coverage), but per semantic versioning for initial development, the public API, config, and database schema may change between minor versions without a major bump. Pin an exact version and read the UPGRADE guide (and the CHANGELOG) before upgrading — a release may require re-running migrations. The 1.0 release will mark API/schema stability.
- Short-lived access JWTs (HS256, 15 min) — stateless, verified on every request with the algorithm pinned and
iss/audasserted. - Opaque rotating refresh tokens (30 days) — stored only as a
sha256hash, rotated on every use. - Reuse detection — replaying a consumed token revokes the whole session (token family).
- Concurrency grace window — multiple tabs / SSR refresh without false logouts.
- Instant revocation — a cache-backed denylist kills an access token or a whole session within one request.
- Optional two-factor auth (TOTP + recovery codes) and passkeys (WebAuthn / FIDO2), each opt-in and feature-gated.
- Sanctum/Fortify-style design — a contract per swappable piece, single-purpose Actions, a static
Lukkconfig hub, a dedicated guard, and Responsable response contracts.
The single runtime dependency is firebase/php-jwt, the audited JWS primitive — never hand-roll JWT. Everything else is Laravel core.
| Access token | HS256 JWT, 15 min. Claims iss/aud/sub/fid/jti/iat/nbf/exp, header typ=at+jwt. Verified every request: alg pinned, iss/aud asserted, denylist checked by jti and fid. |
| Refresh token | Opaque 256-bit secret, 30 days. Returned once; stored only as sha256. Rotated on every refresh; reuse after the grace window revokes the whole family. |
HS256 is correct while this app is its own sole verifier. RS256/ES256 + a JWKS endpoint + kid key rotation are built in (behind the same contracts) for when an independent service must verify tokens — flip LUKK_ALGORITHM and run php artisan lukk:keygen. See Architecture & Security.
- PHP
^8.3 - Laravel
^12.0 | ^13.0 firebase/php-jwt^7.0
composer require lukk/lukk
php artisan vendor:publish --tag=lukk-config # config/lukk.php
php artisan vendor:publish --tag=lukk-migrations # refresh_tokens migration
php artisan migrate
php artisan lukk:secret # writes LUKK_SECRET to .envMap a guard to the lukk-jwt driver in config/auth.php:
'guards' => [
'api' => ['driver' => 'lukk-jwt', 'provider' => 'users'],
],Then protect routes with auth:api as usual:
Route::middleware('auth:api')->get('/me', fn (Request $r) => $r->user());The package registers login, refresh, logout, and session-revocation (DELETE /sessions, DELETE /sessions/others) routes automatically. See Installation and Authentication for the full walkthrough.
📚 Full documentation: stsepelin.github.io/lukk-docs
lukk (the Laravel package) and lukk-js (the TypeScript/Nuxt client) are documented together on one site — each feature page covers both the server and the client. Start with the Introduction or jump to Installation.
For AI assistants: the docs are exposed as /llms.txt + /llms-full.txt (llms.txt convention), and AGENTS.md has integration + contribution rules.
composer install
vendor/bin/pestlukk stands on the shoulders of the Laravel ecosystem, and its design is a deliberate synthesis of ideas from the first-party packages. Sincere thanks to their authors and maintainers — lukk isn't affiliated with or endorsed by any of them (see the note at the top); these are simply the works that shaped it:
- Laravel Sanctum — the architectural model.
HasRefreshTokensis theHasApiTokensanalog, the staticLukkhub mirrors Sanctum's configuration style, and the publish-only migration convention is borrowed wholesale. - Laravel Fortify — the customization philosophy: single-purpose actions + rebindable response contracts, hooks like
Lukk::registerUsing()(à laFortify::createUsersUsing()), thePassword::defaults()policy, and the configurable login identifier. - Laravel Passport — conventions for token issuance, publish-only migrations, and keeping models extensible.
- Laravel Jetstream & Breeze — the reference for two-factor + single-use recovery codes and step-up ("sudo") confirmation, and — with Inertia and the Nuxt ecosystem — the first-party client-side patterns that shaped lukk-js.
- tymon/jwt-auth — the established Laravel JWT package; its
lock_subjectapproach to cross-guard isolation directly informed lukk's multi-guard design.
And the libraries lukk is built on: firebase/php-jwt (the sole runtime dependency), pragmarx/google2fa (TOTP), and web-auth/webauthn-lib by Spomky-Labs (passkeys) — plus the Laravel framework and community that make all of this possible. 🙏
MIT.