kematjaya / auth-bundle
API-first JWT authentication & simple RBAC for Symfony (API Platform friendly). Bring your own User entity via a small interface + factory contract.
Package info
github.com/kematjaya0/auth-bundle
Type:symfony-bundle
pkg:composer/kematjaya/auth-bundle
Requires
- php: ^8.4
- gesdinet/jwt-refresh-token-bundle: ^2.0
- lexik/jwt-authentication-bundle: ^3.2
- symfony/config: ^7.0|^8.0
- symfony/dependency-injection: ^7.0|^8.0
- symfony/framework-bundle: ^7.0|^8.0
- symfony/http-foundation: ^7.0|^8.0
- symfony/http-kernel: ^7.0|^8.0
- symfony/password-hasher: ^7.0|^8.0
- symfony/rate-limiter: ^7.0|^8.0
- symfony/routing: ^7.0|^8.0
- symfony/security-bundle: ^7.0|^8.0
- symfony/security-core: ^7.0|^8.0
- symfony/serializer: ^7.0|^8.0
- symfony/uid: ^7.0|^8.0
- symfony/validator: ^7.0|^8.0
- symfony/yaml: ^7.0|^8.0
Requires (Dev)
- api-platform/symfony: ^4.0
- doctrine/doctrine-bundle: ^2.11|^3.0
- doctrine/orm: ^2.15|^3.0
- phpstan/phpstan: ^2.1
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.0|^13.0
- symfony/browser-kit: ^7.0|^8.0
Suggests
- api-platform/symfony: To document the bundle's routes in the generated OpenAPI schema.
- doctrine/orm: To persist RefreshToken entities (or bring your own storage via UserManagerInterface).
README
API-first, stateless JWT authentication and simple array-based RBAC for Symfony. Built for apps exposing a JSON API (API Platform friendly) consumed by a decoupled frontend (e.g. Next.js) - not for server-rendered/session apps. For that use case, see kematjaya/user-bundle instead.
Ships: register, login (via lexik/jwt-authentication-bundle), refresh (via
gesdinet/jwt-refresh-token-bundle), logout (refresh token revocation), change
password, GET /api/me, a per-IP+email rate limiter on login/register, and
an optional API Platform OpenAPI documentation decorator.
No UI. Frontend integration (Next.js) is a separate package, @kematjaya/auth-ui.
Why "bring your own User entity"
Your app almost certainly already has a User entity referenced by other
domain entities (e.g. Note.owner). This bundle can't own that class - it
would create a circular dependency (the bundle can't depend on the app that
depends on it), and it would make the entity disappear the moment the bundle
is removed, breaking every relation to it.
Instead the bundle depends on two small interfaces from
Kematjaya\AuthBundle\Contract:
AuthenticatableUserInterface- yourUserentity implements this (it already satisfies most of it if it implements Symfony's ownUserInterface+PasswordAuthenticatedUserInterface).UserManagerInterface- your app provides one implementation (typically a thin Doctrine-backed class) that finds/creates/persists yourUser. See.recipe/src/Security/UserManager.phpfor a ready-to-adapt example.
Installation
Requirements
- PHP 8.4+, Symfony 7.0+ or 8.0+
symfony/security-bundle,doctrine/doctrine-bundle+doctrine/ormin your app (seecomposer.jsonfor the full list this bundle requires)- A database Doctrine can reach (
DATABASE_URLset in.env/.env.local)
Quick install
composer require kematjaya/auth-bundle bash vendor/kematjaya/auth-bundle/setup.sh
You'll likely see a
cache:clearerror flash by right aftercomposer requirefinishes. That's expected, not a failure: Gesdinet's bundle needs config thatsetup.shhasn't copied in yet at that point in time. The package itself installs fine - ignore the error and runsetup.sh.
That's it for a fresh project with no User entity yet. If you already have
one, see Bring your own User entity below
before running setup.sh.
What setup.sh does
It's idempotent - safe to re-run any time, it skips any file that already
exists (--force to overwrite). It exists because this bundle isn't
published to a Flex recipes endpoint yet, so it does by hand everything a
real recipe would do automatically on composer require:
| # | Step | Result |
|---|---|---|
| 1 | Registers the bundle | adds a line to config/bundles.php |
| 2 | Copies config fragments | config/packages/lexik_jwt_authentication.yaml, gesdinet_jwt_refresh_token.yaml, kematjaya_auth_doctrine_types.yaml (registers the Doctrine uuid type the User template below uses), config/routes/kematjaya_auth.yaml |
| 3 | Copies User plumbing | src/Entity/User.php, src/Repository/UserRepository.php, src/Security/UserManager.php - skipped if you already have your own |
| 4 | Wires the DI binding | Kematjaya\AuthBundle\Contract\UserManagerInterface -> App\Security\UserManager in config/services.yaml |
| 5 | Generates the JWT keypair | lexik:jwt:generate-keypair --skip-if-exists, appends JWT_SECRET_KEY/JWT_PUBLIC_KEY/JWT_PASSPHRASE to .env (change the passphrase before deploying!) |
| 6 | Updates the schema | php bin/console doctrine:schema:update --force |
The one manual step
setup.sh prints this reminder at the end - it is not automated on
purpose:
Merge config/packages/kematjaya_auth_security.yaml.dist into your own
config/packages/security.yaml (providers / firewalls / access_control). It
was copied with a .dist suffix specifically so Symfony does not
auto-load it - it auto-loads every .yaml file under config/packages/,
and dropping a second firewalls: main: definition there would collide with
the one your app's skeleton already has. Open both files side by side and
merge the relevant blocks in yourself.
Until that merge is done, the bundle's routes exist but nothing is authenticated or protected by JWT.
Also double check that symfony/security-bundle, lexik/jwt-authentication-bundle,
and gesdinet/jwt-refresh-token-bundle all ended up registered in
config/bundles.php. Most auto-register via their own official Flex
recipes, but --no-interaction/CI installs sometimes skip contrib recipes -
setup.sh only registers this bundle, not its dependencies' bundles.
Bring your own User entity
If you already have a User entity (most real projects will), put it at
src/Entity/User.php before running setup.sh so step 3 skips it instead
of overwriting it. If you already ran setup.sh, just delete the copied
file and put your own there instead. Then:
- Make your entity
implements Kematjaya\AuthBundle\Contract\AuthenticatableUserInterface. - Adapt
src/Security/UserManager.php(still copied bysetup.sh) to your entity/repository.
See Why "bring your own User entity" above for why this last part can't be automated further.
Uninstall (clean baseline)
composer remove kematjaya/auth-bundle, remove the bundle line from
config/bundles.php, remove src/Security/UserManager.php and the
kematjaya_auth_* files under config/packages/ and config/routes/, and
remove the merged block from your own security.yaml. Your User entity
stays untouched (it never depended on this bundle at the class level) - only
remove the implements AuthenticatableUserInterface clause if you want it
gone too.
Endpoints
| Method | Path | Auth required |
|---|---|---|
| POST | /api/register |
no |
| POST | /api/login |
no |
| POST | /api/token/refresh |
no |
| POST | /api/logout |
no (revokes by possession of the refresh token) |
| GET | /api/me |
yes |
| POST | /api/change-password |
yes |
Configuration
None. This bundle is intentionally zero-config - see the "Why bring your own
User entity" section for why a user_class option was deliberately avoided.
The rate limiter (5 requests/minute per IP+email on /api/login and
/api/register) can be overridden by defining
framework.rate_limiter.kematjaya_auth_authentication yourself before this
bundle's prependExtension() runs, i.e. in your own config/packages/framework.yaml.
Testing
composer install
composer test