Explicit identity federation.
Optional Authorization Code flow with S256 PKCE, verified RS256 tokens and local MFA.
Opt in after registering a client
SSO is disabled on framework.icom.la until a real provider is configured. Register one OIDC client at your provider with the exact callback https://framework.icom.la/auth/oidc/callback (or your own APP_URL). Require Authorization Code, query response mode, S256 PKCE and RS256 ID tokens. Set the exact issuer and documented authorization/token/JWKS endpoints privately; dynamic discovery and user-supplied endpoints are not supported. PHP needs ext-curl and OpenSSL. A non-empty secret uses client_secret_basic; an empty secret uses a public PKCE client. client_secret_post, private_key_jwt, encrypted ID tokens and algorithms other than RS256 are not implemented.
# Set these in private deployment configuration, never in public source.
OIDC_ENABLED=true
OIDC_ISSUER=https://identity.example.com/your-issuer
OIDC_AUTHORIZATION_ENDPOINT=https://identity.example.com/authorize
OIDC_TOKEN_ENDPOINT=https://identity.example.com/token
OIDC_JWKS_URI=https://identity.example.com/keys
OIDC_CLIENT_ID=your-registered-client-id
OIDC_CLIENT_SECRET=your-private-client-secret
# callback is derived exclusively from APP_URL:
# https://your-app.example/auth/oidc/callbackLink a verified subject explicitly
Provision the local account first, independently establish the exact provider subject for that person, and use the operator CLI. The mapping is the hash of the exact issuer+subject pair; database collation cannot merge differently cased subjects. Email claims, even email_verified=true, never create or link accounts. Linking an already mapped identity fails; unlink deliberately before reassignment. Both operations revoke the affected account’s sessions and pending challenges.
php bin/icom user:create owner@example.com member --password-stdin < /private/password
php bin/icom oidc:link ACCOUNT_ID --subject-stdin < /private/provider-subject
php bin/icom oidc:unlink ACCOUNT_ID --subject-stdin < /private/provider-subjectSign-in protocol
The login screen shows Continue with SSO only when enabled. A CSRF-protected POST /auth/oidc/start creates a random state, nonce, browser binding and verifier. Flow material is encrypted, indexes contain hashes, and each flow expires in five minutes. A dedicated host-only HttpOnly SameSite=Lax cookie binds the top-level callback to its browser. The callback atomically consumes the flow before code exchange. It validates signature, issuer, exact subject, audience/azp, nonce, integer timestamps and any returned c_hash/at_hash. Config changes invalidate pending flows. Query data and provider error descriptions are never reflected into the HTML error message.
Local MFA still applies
Verified OIDC establishes only the primary identity. Active-account and mapping checks run under the account lock; local roles and policies remain authoritative. Enrolled accounts receive a five-minute HttpOnly SameSite=Strict challenge cookie and must POST a factor to /auth/oidc/challenge before getting a session. Provider email, group and role claims do not grant local permissions. Password recovery does not remove the SSO mapping or MFA. Disabling an account or unlinking its subject revokes sessions and factor challenges.
Transport and operating limits
Back-channel HTTP uses only configured endpoints, verifies TLS, refuses redirects, caps each response at 128 KiB, and uses a two-second connect/six-second total timeout per request. JWKS is fetched per sign-in, so rotation does not wait on a local cache. The operator trusts the configured provider/endpoints and must control network egress; no claim-selected URL is fetched. Login attempts share a 20-per-five-minute connected-IP limit for SSO routes. Reverse proxy addresses are not trusted automatically. Schedule security:prune. Review infrastructure access-log retention/redaction for the callback query; the framework’s own request log omits the URI and raw tokens.
What is verified and what is next
The tests exercise signed tokens, claim/key rejection, SQLite/MariaDB races, bounded HTTP transport, a real disposable loopback issuer, browser redirects/cookies, local MFA and both worker adapters. The loopback HTTP flag is forbidden in production. No Google/Microsoft/Keycloak tenant has been onboarded or certified. Exact provider configuration, real HTTPS login/logout testing and account-owner verification remain required before enabling production SSO. SAML, automatic provisioning, refresh-token storage, provider logout/back-channel logout, identity-provider role sync and account recovery delivery are not included. Local sign-out revokes the local session; it does not sign out the provider.