happyr/auth0-bundle
A Symfony bundle that integrates Auth0 authentication into your app, letting you secure routes and authenticate users through Auth0 with minimal configuration. It provides security integration and user handling for Symfony projects.
Start by accepting that this is a thin glue layer, not a full auth stack. The bundle wires auth0/auth0-php into Symfony's container, gives you a user-provider contract, and (historically) a guard authenticator. Budget an hour to read the source; there are ~10 classes total and the README is stale relative to the code.
Install and inspect before configuring.
composer require happyr/auth0-bundle
The critical decision is the transitive auth0/auth0-php version. The bundle's last release is 2021-09, so expect an old major (v7-era API, Auth0\SDK\Auth0). Check composer why auth0/auth0-php and pin it explicitly in your root composer.json — otherwise a future auth0/auth0-php major will resolve in and break method signatures.
Register the bundle in config/bundles.php:
Happyr\Auth0Bundle\HappyrAuth0Bundle::class => ['all' => true],
Create the Auth0 application as a Regular Web Application. Set Allowed Callback URLs, Allowed Logout URLs, and Allowed Web Origins to your local/dev/prod hosts. Copy domain, client ID, and client secret.
Look here first, not at the README: src/DependencyInjection/Configuration.php. This is the authoritative list of config keys (domain, client_id, client_secret, audience, scope, callback_route, cookie settings). Whatever the docs say, the Configuration tree wins.
# config/packages/happyr_auth0.yaml
happyr_auth0:
domain: '%env(AUTH0_DOMAIN)%' # bare host, no https://
client_id: '%env(AUTH0_CLIENT_ID)%'
client_secret: '%env(AUTH0_CLIENT_SECRET)%'
callback_route: 'auth0_callback'
First use case: a "Sign in with Auth0" button on your login page that redirects to the SDK-generated authorize URL, a public callback route, and a user provider that maps the Auth0 sub claim to a local User entity. Get that happy path green before touching roles, refresh tokens, or API audiences.
The user provider is the integration point. Implement the bundle's provider interface so the authenticator can resolve an Auth0 identity to your UserInterface:
class Auth0UserProvider implements Auth0UserProviderInterface
{
public function __construct(private UserRepository $users) {}
public function loadUserByAuth0Id(string $auth0Id): UserInterface
{
return $this->users->findOneBy(['auth0Id' => $auth0Id])
?? $this->users->provisionFromAuth0($auth0Id); // JIT provisioning
}
// ... loadUserByIdentifier / refreshUser / supportsClass
}
Register it and point the firewall at it. The provider is the natural place to translate Auth0 claims (roles, org, email verified) into Symfony roles and to do just-in-time user creation. Keep this class small and testable — it's the only piece you genuinely own.
Prefer the service over the authenticator. For anything on Symfony 5.3+, the bundle's guard-style authenticator is a liability (guard was removed in Symfony 6). A pragmatic split: use the bundle only for the Auth0Service (SDK configuration, login URL generation, token/userinfo retrieval) and write your own AbstractAuthenticator around it:
public function authenticate(Request $request): Passport
{
$auth0 = $this->container->get('happyr.auth0.service');
$user = $auth0->getUser(); // exchanges code for tokens, returns profile array
if (!$user) {
throw new AuthenticationException('No Auth0 session');
}
$identifier = $user['sub'];
// stash claims in a badge / custom passport
}
This gets you the SDK wiring and DI for free while keeping you on a supported Symfony security model.
Callback controller should be a tiny redirector — the heavy lifting is in the service's code exchange. Do not render HTML on the callback; redirect to a target stored in the session (validate it against an allowlist).
Logout is the SDK logout URL plus returnTo, and you must also invalidate the local session — Auth0's logout does not clear your Symfony session automatically.
Roles/claims: add a Rule/Action in Auth0 that namespaces custom claims (e.g. https://yourapp/roles), then read them in the user provider. Never trust unnamespaced custom claims on the ID token.
auth0/auth0-php version conflict during composer update, not a runtime bug. Pin aggressively and add a composer.json note; if you hit Symfony 6/7 constraints, copying src/ into your app is often faster than a PR.tenant.eu.auth0.com). A scheme prefix produces confusing discovery/JWKS 404s.audience, the access token is a JWT for your API, not an OIDC ID token. Decide early which token you verify and which claims you read; mixing them up is the most common source of "user has no roles" bugs.state for you. Verify the round trip yourself (session-stored state, compared in the callback) — do not assume the bundle does it.security.access_control before your authenticated catch-all, or you'll get a redirect loop.exp/nbf.dump($auth0->getUser()) in the callback; a null there almost always means the code exchange failed silently.happyr.auth0.user_provider service in your app's services.yaml, decorate the Auth0Service to add custom scopes, and replace the bundle's user/token model if you need richer identity — don't subclass the authenticator, it's the most brittle part.Auth0Service in kernel tests and fake the profile array; for end-to-end, stub the token endpoint or use Auth0's dev tenant rather than hitting the real one in CI.How can I help you explore Laravel packages today?