kleegroup/franceconnect-bundle
Symfony bundle for integrating FranceConnect authentication into PHP applications. It simplifies the OAuth2 authorization flow, callback handling, and retrieval of authenticated user data, making it easier to add the French government identity provider to your Symfony app.
This is a Symfony bundle, not a Laravel package — but release 0.8 changes which Symfony it targets. The bundle now supports Symfony 6 (previously Symfony 3, which is EOL). That removes the worst of the old "dead dependency" objection, but it still doesn't make it installable in a Laravel app: it pulls in symfony/security-bundle / framework-bundle, which Laravel does not ship and does not want. Treat it as a maintained reference implementation of the FranceConnect OAuth2/OIDC flow, not a dependency.
Where to look first in the repo:
README.md — the example service config shows the exact knobs: client_id, client_secret, redirect_uri, scopes, the integration vs. production environment switch, and (new in 0.8) a proxy_port setting.composer.json — check the actual Symfony constraint. The 0.8 line jumps to Symfony 6; if you are pinned to Symfony 4.4/5 anywhere, confirm the version matrix before assuming compatibility.DependencyInjection/Security/Factory/* and the resource-owner class) — these show the authorize → callback → token → userinfo sequence and the claim mapping.Minimal Laravel path:
composer require league/oauth2-client
# or build a custom Laravel Socialite driver
// config/services.php
'franceconnect' => [
'client_id' => env('FC_CLIENT_ID'),
'client_secret' => env('FC_CLIENT_SECRET'),
'redirect' => env('FC_REDIRECT_URI'), // must match exactly
'base_url' => env('FC_BASE_URL'), // FI sandbox vs production
'scopes' => ['openid','given_name','family_name','birthdate','gender','birthplace','email'],
// mirror the bundle's 0.8 proxy knob if your egress is filtered
'proxy' => env('FC_PROXY'), // e.g. http://proxy.internal:3128
],
Routes: GET /auth/franceconnect (generate state/nonce, redirect) and GET /auth/franceconnect/callback (validate state, exchange code, fetch userinfo, map sub).
First use case: a "Se connecter avec FranceConnect" button so French users can log in with a verified civil identity instead of creating yet another password — returning given_name, family_name, birthdate, gender, birthplace, and email.
Two integration styles
league/oauth2-client GenericProvider pointed at the FC authorize/token/userinfo URLs. Fastest, but you hand-roll id_token validation.Laravel\Socialite\Two\AbstractProvider, implement getAuthUrl(), getTokenUrl(), getUserByToken(), and override mapUserToObject(). This is the pattern the bundle's resource-owner class translates to most cleanly — port the claim mapping, not the Symfony wiring.Login flow (v2/OIDC)
state and nonce; store both in the session (or a short-lived cache keyed by state)./api/v2/authorize with client_id, redirect_uri, response_type=code, scope, state, nonce, and optionally acr_values=eidas1.state, reject on error/error_description, exchange code at /api/v2/token (client_secret_post), validate the id_token (JWKS signature, iss, aud, exp, nonce), then call /api/v2/userinfo.sub to a local user.Token fetching (what the 0.8 fix tells you)
PR #4 ("Fix for symfony 4.3+ token") corrected token handling that broke when Symfony changed its HTTP client behaviour. If you copy token-exchange code from an older fork, article, or vendored snapshot, assume it carries the pre-fix bug — re-derive it from the 0.8 resource-owner class or, better, from your own Http::post() with explicit form params.
Corporate proxy / filtered egress
0.8 added proxy_port configuration (PR #5). If you're integrating from inside a French public-sector or enterprise network, outbound calls to FranceConnect will need a proxy. In Laravel this is Http::withOptions(['proxy' => ...])->asForm()->post(...) — apply the same option to every outbound call (token, userinfo, JWKS), not just the first.
User mapping
Schema::create('identities', function (Blueprint $t) {
$t->id();
$t->foreignId('user_id')->constrained();
$t->string('provider'); // 'franceconnect'
$t->string('provider_user_id'); // the FC `sub`
$t->json('claims'); // minimal, see below
$t->timestamps();
$t->unique(['provider', 'provider_user_id']);
});
Key on sub, never on email. Keep claim storage minimal (data minimization — birthdate/birthplace are sensitive and CNIL will ask why you keep them).
Logout
Local Auth::logout() is not enough. FranceConnect requires an RP-initiated logout: redirect to /api/v2/logout with id_token_hint, post_logout_redirect_uri, and state, then handle the return route. Store the id_token in session at login to supply the hint.
Testing
Http::fake() the token and userinfo responses; assert user creation, identity linkage, and session state. Do not hit the network in tests.If you read the bundle source: follow the security factory and resource-owner class; extract the claim-name table (given_name, family_name, birthdate, gender, birthplace, birthcountry, preferred_username, email), the environment URL table, and the proxy settings into your own config.
illuminate integration, no service provider, no facade, no Socialite driver. Porting the flow is a day of work; wiring the bundle is still wrong.@FlorianKlee) and merged community PRs, so the "abandoned" framing is stale — verify the current repo state yourself rather than trusting either description.composer.json constraints — the 4.3 token fix landed on the way to the 6.x jump, so intermediate branches may be unmaintained.nonce, signed id_token, JWKS. The single most common (and dangerous) shortcut is accepting the code exchange and calling userinfo without validating the id_token — that defeats the point of OIDC.sub is pseudonymous and per-client (derived from your sector identifier). Rotate client_id or sector and every existing user link breaks. Never merge users across providers based on email — FranceConnect emails may be shared or unverified.redirect_uri match. HTTPS only, registered in the partner portal, case- and trailing-slash-sensitive. Mismatch yields a bare invalid_request with no useful detail.state/nonce hygiene. Single-use, session-bound, invalidated on callback. Don't let them survive into an authenticated session where replay is possible.id_token_hint under v2.proxy_port knob (0.8) only helps if every outbound call honours it — token, userinfo, and JWKS fetch. A proxy that works for login and silently fails on key retrieval produces confusing signature errors.exp/iat checks on the id_token; a one-minute leeway is standard, but check your server's NTP.error_description from the callback query string to the log — FranceConnect is verbose there and silent elsewhere.getUserByToken() for claim mapping, mapUserToObject() for the user shape, and a custom getLogoutUrl() for RP-initiated logout. Everything else is standard AbstractProvider.league/oauth2-client FranceConnect provider, a SocialiteProviders entry if one exists, or a generic OIDC client like jumbojett/openid-connect-php. Verify maintenance status and current endpoints yourself — the ecosystem around FC is small and moves with government spec changes.How can I help you explore Laravel packages today?