socialiteproviders/franceconnect
FranceConnect OAuth/OpenID Connect provider for Laravel Socialite, letting applications offer secure login through the French government identity service (FranceConnect). Adds a franceconnect driver so users can authenticate with their existing certified identity provider and share verified data lik...
socialiteproviders/franceconnect is a Socialite driver for FranceConnect, the French government's OIDC identity federation. You don't use it like a normal social login — it's an identity broker that authenticates citizens against impots.fr, AMELI, La Poste Identité Numérique, etc., and returns a certified identity (sub, name, birthdate, gender, birthplace). Treat it as "civil-identity verification", not "get an email address".
Minimum steps:
composer require socialiteproviders/franceconnect:^4.1
config/services.php (key name is the driver name):
'franceconnect' => [
'client_id' => env('FRANCECONNECT_CLIENT_ID'),
'client_secret' => env('FRANCECONNECT_CLIENT_SECRET'),
'redirect' => env('FRANCECONNECT_REDIRECT_URI'),
],
Register the extend listener. Laravel ≤10, in EventServiceProvider:
protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\FranceConnect\FranceConnectExtendSocialite::class.'@handle',
],
];
Laravel 11+, in AppServiceProvider::boot():
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
$event->extendSocialite('franceconnect', \SocialiteProviders\FranceConnect\Provider::class);
});
Your first use case — a login route pair:
Route::get('/auth/franceconnect', fn () => Socialite::driver('franceconnect')->redirect());
Route::get('/auth/franceconnect/callback', function () {
$fc = Socialite::driver('franceconnect')->user();
// $fc->getId() === sub === the identifiant pivot
});
Where to look first: the package README (environment/endpoint switching), then your FranceConnect partner account on the DINUM portal, because nothing works until your exact redirect URI is declared there.
Persist sub, not email. The FranceConnect sub is the stable "identifiant pivot" for a user. Store it in its own indexed column and map it to your local user:
$user = User::firstOrCreate(
['franceconnect_sub' => $fc->getId()],
['name' => $fc->getName()]
);
Raw claims are on $fc->user (birthdate, gender, birthplace, birthcountry) — useful for one-time identity proofing, not for profile sync.
Environment switching. Keep integration (fcp.integrate.api.fr) and production (app.franceconnect.gouv.fr) credentials as separate env blocks and select the base URL per environment; never let a staging deploy point at prod credentials or vice-versa. If the package reads endpoints from config, override there rather than patching the provider.
Scopes and eIDAS levels. FranceConnect v2 expects the identity scope set (openid given_name family_name birthdate gender birthplace birthcountry) and an acr_values level (eidas1 by default). Set them explicitly so a package default change doesn't silently alter what you receive:
Socialite::driver('franceconnect')
->scopes(['openid', 'given_name', 'family_name', 'birthdate', 'gender', 'birthplace', 'birthcountry'])
->with(['acr_values' => 'eidas1'])
->redirect();
Access the ID token. The token response body is exposed on the user object ($fc->accessTokenResponseBody['id_token']). You need it for RP-initiated logout, and it's the only way to get auth-time metadata Socialite doesn't surface.
Multiple user types. If both agents and citizens log in, register two drivers pointing at the same provider class with different scope/acr config, rather than branching inside one callback.
Testing. Don't hit FranceConnect in CI. Http::fake() the /token and /userinfo endpoints and drive the callback route with a canned code, or bind a fake driver into the container. Assert on your own persistence layer, not on Socialite's internals.
http://localhost will not work. FranceConnect only accepts declared HTTPS redirect URIs on reachable hosts. Use a tunnel (ngrok, expose, Cloudflare Tunnel) and register that URL in the partner portal, or you'll burn hours on redirect_uri_mismatch.stateless(). FranceConnect requires state (and a nonce for OIDC). stateless() disables exactly the check you need; if the package doesn't send a nonce for you, add it via ->with(['nonce' => ...]) and verify it on callback.email and preferred_username from the available scopes. If your code does $fc->getEmail() and expects a value, it will be null — this is the single most common breakage. Redesign around sub.sub differs by eIDAS level / scope set in some configurations; don't assume a user identified at eidas1 matches a record created under an eidas2 flow. Key your table on (sub, acr_level) if you ever offer both./api/v1/logout with id_token_hint and state. Socialite's logout() doesn't apply here — check whether the 4.x driver exposes a logout helper; if not, build the URL yourself from the stored id_token and redirect. Local Auth::logout() alone leaves the FranceConnect session alive.^4.1 and read the diff only if you were bitten by a specific bug in 4.1.0.php artisan config:clear, and remember SocialiteWasCalled registration is code, not config — it won't survive a stale event cache in older skeleton apps.id_token and access token). Almost every failure is a redirect-URI, state, or scope mismatch — all visible at the URL/HTTP layer, not in Socialite exceptions.How can I help you explore Laravel packages today?