Skip to content

v0.12.0

Release Date: 2026-10-06

SystemAuth becomes a deployable, central login service for every SystemForge application: GitHub/Google social login, a hardened session and token lifecycle, OpenID Connect ID tokens and UserInfo, and a reusable relying-party package so applications federate to SystemAuth instead of each implementing social login. Sessions and login state are durable in PostgreSQL, so restarts and multi-instance deployments keep users signed in. Design of record: ADR-002 and INIT-SYSTEMFORGE-005.

Highlights

  • Social login on SystemAuth — /login, /login/{provider} and callback routes for GitHub and Google, principal upsert keyed by provider subject (verified-email linking only), a hardened __Host-sf_login session, state CSRF + PKCE, and an allowlisted post-login redirect.
  • Session and token lifecycle — refresh-token rotation with reuse detection and absolute expiry, real logout with token revocation, and a minimal consent page.
  • OpenID Connect — SystemAuth now issues ID tokens, optional RS256 JWT access tokens (features.enable_jwt_access_tokens), and serves /oauth/userinfo.
  • identity/relyingparty — OIDC client, sub → sf_principal_id principal linking, a /bff/* cookie-session surface for browser apps, and bearer middleware for JWT access tokens and API keys.
  • Durable stores — Ent-backed SystemAuth login sessions, login state and consent grants; identity/relyingparty/pgstore PostgreSQL session and login-state stores with AES-256-GCM encryption at rest and key rotation (pgstore.NewStores, pgstore.KeysFromEnv).
  • Production-deployable systemauth binary — PostgreSQL via pgx, signing key from a secret with a stable key ID, /healthz and /readyz, and a production mode that refuses unsafe configuration.

Breaking Changes

session/oauth removed

The unused duplicate social-login package is gone; identity/oauthclient is the single sanctioned package and gains everything worth keeping from it.

// Old
import "github.com/plexusone/systemforge/session/oauth"

// New
import "github.com/plexusone/systemforge/identity/oauthclient"
// oauthclient.NewGitHubConnector / NewGoogleConnector, StateStore, User.EmailVerified

systemauth production mode

Without --dev, cmd/systemauth now refuses to start unless it has a signing key (keys.private_key_file, keys.private_key_pem, --signing-key-file, or SYSTEMAUTH_SIGNING_KEY), an https issuer, and a persistent database, and it rejects social_login.insecure_cookies. Local setups pass --dev. --listen is kept as a deprecated alias of --addr.

Refresh-token reuse revokes the token family

Every refresh issues a new refresh token and retires the old one. Presenting a rotated refresh token returns invalid_grant and revokes the whole family; clients must store the newest refresh token after each refresh. Absolute lifetime is tokens.refresh_token_absolute_lifetime (default 720h).

Header-trusting session provider bypassed when social login is enabled

With social_login configured, the X-User-ID-trusting DefaultSessionProvider can no longer bypass login; the __Host-sf_login cookie session is consulted first. Custom providers set with WithSessionProvider still apply after the cookie.

Schema additions

New tables sf_external_identities, sf_login_sessions, sf_login_states, sf_consent_grants; OAuth codes and tokens gain a subject column and the auth-code user_id becomes nullable (social-login principals have no legacy user row). Run systemauth migrate (or --migrate). Relying-party applications create sf_rp_sessions / sf_rp_login_states in their own database with pgstore.EnsureSchema.

New Features

  • identity/oauthclient: provider Connector with overridable endpoints, User.EmailVerified (Google email_verified; GitHub /user/emails), pluggable single-use StateStore with a race-free MemoryStateStore.
  • identity/systemauth: PrincipalDirectory (Ent and memory) with external-login account linking; social_login configuration with ${ENV} expansion; CurrentLoginSession; options WithPrincipalDirectory, WithLoginSessionStore, WithLoginStateStore, WithSocialConnector, WithConsentStore, WithReadinessCheck; idp_hint authorization parameter.
  • identity/relyingparty: Client (Discover, AuthCodeURL, Exchange, Refresh, Revoke, VerifyIDToken, VerifyAccessToken, UserInfo), ResolvePrincipal, MembershipSource (claims-based memberships), SessionStore with DeleteBySubject / DeleteBySID for future back-channel logout, NewBFF, BearerMiddleware, PrincipalFromContext.
  • identity/relyingparty/pgstore: NewSessionStore, NewLoginStateStore, NewStores, Keys, KeysFromEnv, EnsureSchema, DecodeKey, DeleteExpired.
  • Shared store conformance suites (identity/systemauth/storetest, identity/relyingparty/storetest) run against memory and PostgreSQL implementations.

Bug Fixes

  • SystemAuth did not issue ID tokens: the Fosite OpenID Connect handlers were not registered, and discovery did not serve the advertised UserInfo endpoint.
  • Ent storage could not complete a real login: the login session was not persisted, access tokens were inserted twice, PKCE marked the code used before the exchange finished, and auth codes required a legacy user row.
  • A reused refresh token could crash the server (nil request after the inactive-token error).
  • cmd/systemauth registered no PostgreSQL driver for ent.Open("postgres").
  • Configured clients that already existed in a persistent database failed startup with a unique-constraint error; they are now updated.
  • DefaultSessionProvider concatenated returnURL into redirects without query escaping.
  • The relying-party seal nonce is generated in a dedicated buffer (sealed format unchanged).

Documentation

Upgrade Notes

  1. Replace any session/oauth import with identity/oauthclient.
  2. Run systemauth migrate against existing SystemAuth databases.
  3. For production, provide a signing key and key ID, an https issuer, and PostgreSQL; use --dev only locally.
  4. Applications federating to SystemAuth: register an OIDC client (PKCE, redirect https://<app>/bff/auth/callback), mount relyingparty.NewBFF, and use pgstore.NewStores with a 32-byte session key for durable sessions.

What's Next

INIT-SYSTEMFORGE-005 Phase 3: consumer convergence onto the relying-party contract and a CI gate against direct GitHub/Google OAuth wiring outside identity/oauthclient. Suite single sign-on follow-ups: memberships and entitlements in token claims and UserInfo, silent first-party SSO with back-channel logout, and token exchange for on-behalf-of calls between applications.