anzusystems / auth-bundle
Anzu authorization services
Package info
github.com/anzusystems/auth-bundle
Type:symfony-bundle
pkg:composer/anzusystems/auth-bundle
Requires
- php: >=8.4
- ext-json: *
- ext-redis: *
- anzusystems/common-bundle: ^11.5 || dev-85893_mcp_log_search_by_id
- doctrine/common: ^3.3
- lcobucci/clock: ^3.0
- lcobucci/jwt: ^5.5
- symfony/console: ^7.3|^8.0
Requires (Dev)
- doctrine/doctrine-bundle: ^2.11|^3.0
- doctrine/orm: ^2.13|^3.0
- nelmio/api-doc-bundle: ^5.9
- slevomat/coding-standard: 8.20.0
- symfony/test-pack: ^1.0
- symplify/easy-coding-standard: ^13.0
- vimeo/psalm: ^6.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Provides authorization functionality among Anzusystems' projects.
Installation
From within container execute the following command to download the latest version of the bundle:
$ composer require anzusystems/auth-bundle --no-scripts
Step 3: Use the Bundle
Configure the AnzuAuthBundle in config/anzu_systems_auth.yaml:
anzu_systems_auth: cookie: domain: .anzusystems.localhost secure: false # use true for PROD environment! jwt: audience: anz algorithm: ES256 # enum (ES256|RS256), default "ES256" public_cert: '%env(base64:AUTH_JWT_PUBLIC_CERT)%' # string representation of a public certificate private_cert: '%env(base64:AUTH_JWT_PRIVATE_CERT)%' # string representation of a private certificate authorization: enabled: true refresh_token: storage: redis: service_id: SharedTokenStorageRedis # service id of \Redis instance auth_redirect_default_url: http://admin-dam.anzusystems.localhost auth_redirect_query_url_allowed_pattern: '^https?://(.*)\.anzusystems\.localhost(:\d{2,5})$' type: json_credentials
Configure the SecurityBundle in config/security.yaml:
security: providers: app_user_provider_email: entity: class: App\Entity\User property: email auth: pattern: ^/api/auth/ stateless: true provider: app_user_provider_email json_login: check_path: auth_login success_handler: AnzuSystems\AuthBundle\Security\AuthenticationSuccessHandler failure_handler: AnzuSystems\AuthBundle\Security\AuthenticationFailureHandler logout: path: auth_logout access_control: - { path: ^/api/auth/, roles: PUBLIC_ACCESS }
Configure routing:
$routes ->import('@AnzuSystemsAuthBundle/Controller/Api/JsonCredentialsAuthController.php', type: 'attribute') ->prefix('/api/auth/');
Personal access tokens
Opt-in personal access token (PAT) authentication: an sha256-hashed bearer token bound to a user, with optional expiration, revocation, cached authentication, expiry notifications and management API. Disabled by default — a project that does not enable it needs no schema or configuration changes after a bundle upgrade.
Enable it by subclassing the mapped superclass and pointing the config to it:
use AnzuSystems\AuthBundle\Domain\PersonalAccessToken\Repository\PersonalAccessTokenRepository; use AnzuSystems\AuthBundle\Entity\AbstractPersonalAccessToken; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: PersonalAccessTokenRepository::class)] #[ORM\Table(name: 'personal_access_token')] #[ORM\Index(name: 'IDX_revokedAt_expiresAt', fields: ['revokedAt', 'expiresAt'])] #[ORM\UniqueConstraint(name: 'UNIQ_tokenHash', fields: ['tokenHash'])] class PersonalAccessToken extends AbstractPersonalAccessToken { }
anzu_systems_auth: personal_access_token: enabled: true entity_class: App\Domain\PersonalAccessToken\Entity\PersonalAccessToken user_entity_class: App\Domain\User\Entity\User auth_cache_pool: 'some_redis.cache'
The user relation targets AnzuSystems\Contracts\Entity\AnzuUser — make sure doctrine
resolve_target_entities maps it to the project user class. Add the doctrine mapping for the bundle's Entity
namespace and generate the migration with doctrine:migrations:diff. user_entity_class must name the same class
as the common-bundle settings.user_entity_class — the authenticator and CurrentAnzuUserProvider would otherwise
load different user classes. The entity relies on constructor-less proxies, so use doctrine/orm 3 (lazy ghosts);
the ORM 2 legacy proxy strategy conflicts with the final entity constructor.
Wire the authenticator into a firewall protecting the API that accepts the tokens:
security: firewalls: mcp: pattern: ^/api/mcp stateless: true provider: app_user_provider_id entry_point: AnzuSystems\AuthBundle\Security\Authentication\PersonalAccessTokenAuthenticator custom_authenticators: - AnzuSystems\AuthBundle\Security\Authentication\PersonalAccessTokenAuthenticator
Management API routes (list/create/revoke) are provided by
AnzuSystems\AuthBundle\Controller\Api\PersonalAccessTokenController attribute routes — import them with a prefix:
$routes ->import('@AnzuSystemsAuthBundle/Controller/Api/PersonalAccessTokenController.php', type: 'attribute') ->prefix('/api/adm/v1');
Authorization uses the auth_personalAccessToken_(create|read|revoke) permissions (see
AnzuSystems\AuthBundle\Security\PersonalAccessTokenPermission); creation additionally requires the role
configured via create_role (default ROLE_MCP).
Deleting a user: call PersonalAccessTokenFacade::deleteByUser($user) before removing the user entity — it deletes the
user's tokens in one flush and invalidates their auth cache entries. The user join column also declares onDelete: CASCADE,
but that is best-effort only: the created_by/modified_by columns of the user's own tokens still reference the user, so
the explicit deleteByUser() call is the contract.
Console commands:
anzu:personal-access-token:create <userId> --name=<label> [--expires-at=...] [--never-expires]— prints the plaintext token once.--never-expirescreates a token withexpiresAt = NULL(skipped by the expiry notifications, mutually exclusive with--expires-at). It is command-only — the management API never sets it, so a never-expiring token can only be issued deliberately for a system user (e.g. a service integration).anzu:personal-access-token:notify-expiring— daily cron; notifies owners of tokens expiring in 7 days or 1 day throughPersonalAccessTokenExpiryNotifierInterface(no-op by default — alias your own implementation). The final-notice windows of consecutive runs overlap, so the implementation must be idempotent per (token, daysRemaining) pair — e.g. dispatch under an event name containing both.
When migrating from an app-level PAT implementation, rename existing permission grants to the
auth_personalAccessToken_* keys — grants stored under the old keys stop matching silently.