intracto/fas-open-id-bundle
Symfony bundle that integrates FAS OpenID Connect authentication, simplifying federated login, user identity handling, and secure access for applications using FAS as an identity provider.
This bundle is a Symfony integration for FAS (the Belgian Federal Authentication Service) over OpenID Connect. In practice it is a thin, opinionated wrapper around an OIDC client that gives you: an authorization-code redirect, a token exchange, and a user object you can plug into Symfony's security layer. Treat it as "an authenticator + a client service," not as a full identity solution — user persistence and authorization are still yours.
Minimal path:
Install and enable.
composer require intracto/fas-open-id-bundle
Register it in config/bundles.php (the class name is in the package's composer.json under autoload.psr-4; it follows the Intracto\FasOpenIdBundle\... namespace).
Configure the client. Start from the bundle's config root — read src/DependencyInjection/Configuration.php first, because that file is the authoritative, version-specific list of options. Expect to supply: client ID, client secret, the FAS discovery/well-known URL (or explicit authorize/token/userinfo endpoints), and a redirect URI that is an absolute, externally reachable URL.
Wire the firewall. The first real use case is "anonymous user hits /dashboard, gets bounced to FAS, comes back authenticated." Add the bundle's authenticator to a firewall and confirm the callback route is public:
security:
firewalls:
main:
pattern: ^/
custom_authenticators:
- Intracto\FasOpenIdBundle\Security\... # confirm exact FQCN in src/Security/
anonymous: true
Look here first when something is off: the README for the canonical snippet, src/Security/ for the authenticator (redirect creation, state handling, failure handling), src/DependencyInjection/ for config keys, and src/Service/ (or equivalent) for the client wrapper you can inject elsewhere.
The first end-to-end goal is modest: one protected route, one successful login, one dump() of the userinfo claims. Get that working before you build user provisioning on top of it.
Map claims to a local user, don't authenticate the claim. FAS tells you who the subject is; you decide what that means locally. The clean pattern is a user provider keyed on a stable claim (the sub identifier, not email — emails change, sub shouldn't):
// lookup-or-create on the stable subject identifier
$user = $this->userRepository->findOneByFasSubject($claims['sub'])
?? $this->provisioner->createFromClaims($claims);
Keep provisioning in a service, not in a security listener, so you can call it from tests and console commands.
Separate "login" from "session lifetime." The bundle handles the authorization-code exchange at login. Everything after that — how long you trust your local session, when you re-check userinfo, how you react to a revoked account — is application logic. Most teams land on: local session for N hours, refresh/revalidate against FAS on sensitive actions.
Inject the client service for non-login OIDC calls. Once the bundle is configured, the underlying client (and its discovery metadata like end_session_endpoint) is available as a service. Use it for:
id_token_hint and a post_logout_redirect_uri so users aren't left logged in upstream;Feature-flag the integration. FAS is rarely available on every developer's machine. Wrap the firewall config or the redirect initiation behind an env var so local devs can fall back to a normal form login, while staging/production run the real flow. A stub IdP (or the FAS acceptance environment) in CI is worth the setup cost.
Functional testing: replace the bundle's client/authenticator service with a test double in the container, and assert on your own behaviour — that the provider was called with the right sub, that the session was populated, that the failure path redirects to a friendly error page. Testing the OIDC wire protocol itself is the vendor's job.
Redirect URI mismatch is the #1 support ticket. FAS validates it as an exact string. https://app.example.com/callback, .../callback/, and http://... are three different URIs. Behind a load balancer, Symfony may generate http:// — set framework.trusted_proxies and trusted_headers so the absolute URL you send matches what's registered.
State and nonce need a session. The flow stores a state value (and usually a nonce) between the redirect and the callback. If your firewall is stateless: true, or you run multiple app servers without shared session storage, the callback will fail validation intermittently. Either use sticky sessions/Redis-backed sessions or move state into a signed cookie — and document which you chose.
Clock skew and JWKS caching. Token validation depends on exp/iat and on fetching the IdP's signing keys. Cache the JWKS (and the discovery document) with a TTL rather than fetching per request, but keep the TTL short enough that key rotation doesn't lock everyone out. A few seconds of allowed clock skew avoids a class of "token not yet valid" errors on freshly booted containers.
Version and lifecycle risk. Last release was October 2022, one star, zero dependents. Before adopting, verify it installs against your Symfony and PHP versions (composer why-not intracto/fas-open-id-bundle), and budget for the possibility that you fork it. Because the surface area is small — an authenticator plus a client wrapper — forking or decorating the relevant services is usually cheaper than waiting for an upstream fix. Prefer decorating the bundle's services over editing vendor code, so you can still take a patch release.
Extension points to favour: config overrides in config/packages/, service decoration for the client wrapper, and a custom user provider/loader for mapping. Avoid subclassing the authenticator unless you must; its flow is what the README documents, and forks tend to drift.
Debugging the redirect loop: if you bounce between your app and FAS forever, log the exact authorize URL you generate and diff it against the registered redirect URI and scopes. Nine times out of ten it's a URI, scope, or a state that never persisted — not the token endpoint.
How can I help you explore Laravel packages today?