v0.26.0¶
Release Date: 2026-10-28
Two additions that let every application on SystemForge authenticate the same way, with no static API keys:
identity/appauthassembles the relying party for an application in a few lines, with the same defaults everywhere.- Service accounts give automation its own identity in an organization, with short-lived DPoP-bound tokens it gets by signing with a private key.
It also lets refresh tokens survive a SystemAuth restart, with a configured token HMAC secret.
The release adds packages and options and changes no existing API. The
JWT-bearer grant is off until configured, applications accept service
accounts only with an option, and SystemAuth starts without an HMAC secret
as before. One existing response changes: relyingparty.RequestAuthenticator
answers a cookie-authenticated cross-origin write with 403 instead of
401 (see Fixed); otherwise nothing changes for a deployment that does
nothing.
Highlights¶
- One way to authenticate an application.
appauth.Newbuilds the relying-party client, the/bff/*cookie session and one authentication chain for the API: the session or a DPoP-bound SystemAuth token, then any extra authenticators such as AAuth for agents. - The DPoP key is required. With persistent session and login-state
stores shared by every instance (
pgstore) and SystemAuth's token HMAC secret, sessions survive restarts and work on any instance. - Configuration errors name the setting in a form a host can relabel.
- Memberships are filtered to the organizations where the person holds one of the application's roles.
- Calls to SystemAuth for the signed-in person take one line with
SessionTokenandTransport. - Service accounts. An account belongs to one organization and holds
application roles there. It signs a short assertion with a private key,
and SystemAuth issues it a 15-minute DPoP-bound token through the RFC 7523
JWT-bearer grant. The token carries the
orgsclaim like a person's, so applications authorize it the same way. Removing a role takes the organization out of its memberships, and disabling the account or revoking a key ends its current tokens. Operators manage accounts with theservice-accountcommand; workloads in Go useidentity/serviceclient. -
A shared principal store.
pgstore.PrincipalStorekeeps an application's principals insf_rp_principals, created byEnsureSchema, so applications stop maintaining their own. -
Refresh tokens that survive a restart. SystemAuth signed authorization codes, refresh tokens and opaque access tokens with an HMAC secret it generated at each start, so after every restart relying parties could not refresh and sent their users through sign-in again, and a second instance refused the first one's tokens.
tokens.hmac_secretsets the secret, andtokens.hmac_verification_secretrotates it without signing anyone out. Without it, SystemAuth logs a warning.
Added¶
identity/appauth:Config,Validatewith labels,ConfigFromEnvandRedirectURL.ParseDPoPKey,ReadDPoPKeyFileandDPoPKeyCommand.New,Handler,Middleware,Authenticate,ChallengeandChallenges.SessionTokenandTransport.ResourceMetadataHandlerfor RFC 9728.pgstore.PrincipalStoreonsf_rp_principals, withSetActiveandDelete.NewStoresnow returns it asPrincipals.storetest.PrincipalStore, a conformance suite for principal stores.identity/serviceaccount: accounts, keys (generated as P-256, or supplied as ES256, RS256 with 2048 bits or more, or EdDSA), roles, and disable, enable, delete and key revocation recorded as access changes.- The JWT-bearer grant behind
oauth.jwt_bearer: enabled(default false);max_assertion_lifetime(default 5m);token_lifetime(default 15m).
Tokens are issued under the reserved client systemauth-jwt-bearer, with
the principal_type claim and no refresh token.
- identity/serviceclient: turns a private key into an http.Client
that calls APIs as the account.
- identity/serviceaccount/serviceaccountcmd: the service-account
command, registered in systemauth.
- relyingparty.BearerConfig.ServicePrincipals,
VerifiedIdentity.Service and relyingparty.ServiceAccountClientID.
appauth.Options.ServiceAccounts turns them on.
- principal.Service.CreateServiceInTx and
MemoryPrincipalStore.SetActive.
- tokens.hmac_secret and tokens.hmac_verification_secret in
SystemAuth's configuration (TokenConfig.HMACSecret and
HMACVerificationSecret, MinHMACSecretLength): at least 32 bytes each,
with environment variable expansion.
Changed¶
approleacceptsservice_accountmemberships.- Token introspection includes the
orgs,orgs_truncatedandprincipal_typevalues a token was issued with, for every principal.
Deprecated¶
appauth.Options.APIKeys. Command-line tools sign in withidentity/publicclient, and automation uses service accounts (ADR-016). The option exists so an application with API keys can adoptappauthbefore replacing them.
Fixed¶
relyingparty.RequestAuthenticatoranswers a cookie-authenticated cross-origin write with403 CSRF_REJECTEDand no challenge. It used to answer with a401, which tells a client to sign in again.
Upgrading¶
Nothing is required, with one response to check: an API behind
relyingparty.RequestAuthenticator (or requestprincipal middleware over
it) now answers a cookie-authenticated cross-origin write with
403 CSRF_REJECTED and no WWW-Authenticate challenge, where it answered
401. A client that signed in again on that 401 should treat the 403 as
a refused request instead.
To adopt the additions:
- Set SystemAuth's token HMAC secret. Generate it once per deployment
with
openssl rand -hex 32, keep it in your secret store, and settokens.hmac_secret: ${SYSTEMAUTH_TOKEN_HMAC_SECRET}. Refresh tokens issued before the first deploy with it stop working once, as they do on every restart without it. See Deploying SystemAuth. - Move an application onto
appauth. Follow Authenticating an Application. - Generate a DPoP key once per deployment and deliver it as a file.
- Keep sessions and login states in persistent stores shared by every
instance, such as
pgstore.NewStores; the in-memory stores, allowed only on a loopback URL, lose sessions at a restart. - People sign in once more after the first deploy with the key, if the application had none.
- An application with its own principals table can move it into
sf_rp_principalswith the guide's migration. - Give automation service accounts.
- Set
oauth.jwt_bearer.enabled: truein SystemAuth. - Create accounts with
service-account. - Set
appauth.Options.ServiceAccountsin the applications they call.
Service Accounts walks through it.
Documentation¶
- Authenticating an Application
- Service Accounts
- Deploying SystemAuth and Rotating Keys and Secrets: the token HMAC secret
- Relying Party: the setup example sets the DPoP key
- ADR-016: automation's grant is the service account's JWT-bearer grant
For release history, see the changelog.