schaefersoft/laravel-swiss-eid
Laravel package for integrating Swiss E-ID (Swiss electronic identity) authentication into PHP applications. It simplifies identity flows, user login, and verification so you can add Swiss digital identity support quickly and securely.
Requirements & install
composer.json, so a project on 8.1 will hard-fail on composer require rather than degrade gracefully. Verify your CI matrix (and any platform.php override in config.platform) before bumping.composer require schaefersoft/laravel-swiss-eid, then publish the config: php artisan vendor:publish --tag=swiss-eid-config (confirm the exact tag in the README — it's the first thing to check, since the package is pre-1.0 and tags have moved between releases).Where to look first, in order
config/swiss-eid.php — this is the contract. Client ID/secret, redirect URI, discovery/issuer URL, scopes, and any route/guard toggles all live here. Read it before writing a single line of integration code.src/Http/Controllers (or equivalent) — the package almost certainly ships a redirect + callback controller pair. Knowing whether it registers routes for you or expects you to wire them determines your whole integration shape.boot() — reveals the registered routes, facade/singleton bindings, and any published migrations.First use case: add a "Login with Swiss EID" button
The minimal happy path is a redirect out, a callback in, and a local user:
Route::get('/auth/swiss-eid', [SwissEidController::class, 'redirect'])->name('swiss-eid.redirect');
Route::get('/auth/swiss-eid/callback', [SwissEidController::class, 'callback'])->name('swiss-eid.callback');
Register those two URIs at the identity provider, put the callback URL in the config, and you have a working login. Everything else (account linking, guard selection, step-up auth) is layered on top of this.
Provisioning users on first login
Treat the sub claim as the primary key for the identity and store it on your users table:
// migration
$table->string('swiss_eid_sub')->nullable()->unique();
public function callback(Request $request)
{
$identity = SwissEid::handleCallback($request); // per README naming
$user = User::firstOrCreate(
['swiss_eid_sub' => $identity->sub()],
[
'name' => $identity->name(),
'email' => $identity->email(),
]
);
Auth::login($user, remember: true);
return redirect()->intended(route('dashboard'));
}
Do not key on email — it can change upstream, and keying on it silently merges two people if the IdP ever recycles an address.
Account linking for existing users
If you already have password-based accounts, an explicit "link your Swiss EID" flow inside an authenticated session is safer than auto-merging by email on callback. Pattern: authenticated user hits a link route → redirect out with a marker in the session/state → callback attaches swiss_eid_sub to Auth::user() instead of logging in a different account. Verify the email matches before attaching, and log the link event.
Guard separation
If you want eID login to be an additional guard rather than the default, configure a dedicated guard in config/auth.php pointing at the package's user provider (if it exposes one) or wrap it in your own. Avoid swapping the default guard globally — it tends to break existing session-based auth in subtle ways.
Requiring a verified identity on sensitive routes
Wrap the routes that need it in a middleware that checks the swiss_eid_sub column is present, rather than relying on "logged in at all." This gives you a clean step-up story: ordinary browsing on a normal session, high-assurance actions behind eID.
Sensitive-action re-authentication
For signatures or data disclosure, re-run the redirect flow with prompt=login (or the IdP's equivalent) instead of trusting an hours-old session. Keep the flow short-lived: stash the intended action in the session, complete the round trip, then resume.
Testing
The callback is an HTTP boundary — fake the token endpoint rather than faking the package:
Http::fake([
'*/token' => Http::response(['access_token' => 'x', 'id_token' => $jwt]),
]);
Build a helper that mints a signed id_token with your test claims so you can exercise claim-mapping, missing-email, and malformed-token branches. Keep at least one test that asserts the redirect URI in the outgoing request matches config exactly.
Logout
Standard session logout only clears your side. If the package supports RP-initiated logout, also hit the IdP's end-session endpoint with id_token_hint and post_logout_redirect_uri; otherwise users stay signed in upstream and get silently re-authenticated on the next login attempt, which looks like a bug.
Version and platform pitfalls
^0.7 and read changelogs — pre-1.0 releases here carry no backwards-compatibility promise.composer why-not php 8.2 if the update refuses to resolve; a transitive dev dependency is the usual culprit.Redirect URI mismatches
The single most common failure. The URI must match byte-for-byte what's registered at the IdP — scheme, host, port, trailing slash, and www.. http vs https and localhost vs 127.0.0.1 both count as mismatches.
Session state across the round trip
The callback is a fresh request that depends on values stashed before the redirect (state, nonce, PKCE verifier). Symptoms and causes:
SESSION_LIFETIME and keep the flow snappy.SESSION_DOMAIN misconfigured → the cookie set on the redirect request isn't sent back on the callback. In local dev with a tunnel (ngrok/Expose), make sure you're using one consistent hostname throughout.Config caching
php artisan config:cache will freeze the config file's values. Changing credentials and seeing no effect is almost always a stale cache — php artisan config:clear after any .env or config edit in a cached environment.
HTTPS and local development
Assume HTTPS is required in production and strongly preferred by the IdP. For local work you either need a tunnel, or the IdP's sandbox/test environment with its own client credentials. Keep those credentials in a separate SWISS_EID_* env block rather than toggling a sandbox boolean, so you can't accidentally point staging at production.
Don't log tokens or raw claims
Debugging by Log::info($response->json()) is the fastest way to leak an id_token and full PII into your log storage. Log the sub and error codes; use debug level and strip the payload if you must.
Clock skew and token validation
JWT exp/nbf/iat checks are unforgiving. Container clocks drift; if you see intermittent "token expired" failures that vanish on retry, sync ntp or give the validator a small leeway in config if the package exposes one.
Extending rather than editing vendor code
If the claim mapping isn't right for you (unusual name formats, missing email_verified, extra AGOV attributes), look for a configurable claim map or a bindable interface in the container before reaching for a fork. Since this is a 0.x package, an override published into your own app is safer than vendor/ edits that vanish on the next composer update.
Upgrade hygiene
Keep a test that exercises the callback end-to-end. With a package moving through 0.x, that test is what tells you a patch bump quietly changed a claim name or a config key.
How can I help you explore Laravel packages today?