Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.
Connect your identity provider
Subact ID does not authenticate people. Your identity provider does, and Subact ID takes the token it issued as the subject of an exchange. The connection has two jobs: trusting the provider’s tokens, and learning when a person should no longer be acted for.
Trusting the provider’s tokens
Section titled “Trusting the provider’s tokens”Any OpenID Connect provider can be the upstream. Subact ID reads its discovery document and JWKS, and
accepts a subject token signed by one of those keys, issued by that issuer and carrying
SubactId:UpstreamIdp:Audience in aud.
# The realm or tenant URL; discovery is derived from it.export SubactId__UpstreamIdp__Issuer=https://sso.example.com/realms/corp# Or, for a provider whose discovery document is not at <issuer>/.well-known/openid-configuration:# export SubactId__UpstreamIdp__MetadataUrl=https://sso.example.com/.well-known/openid-configurationexport SubactId__UpstreamIdp__Audience=subactidTwo mistakes are common. SubactId.Server doctor reports both:
- The issuer must equal the URL Subact ID fetches it from. The discovery document’s
issuermust be exactly the realm URL Subact ID uses. A provider behind a proxy that advertises a public name while Subact ID reaches it on an internal one fails here. Make the two URLs the same. In Keycloak the advertised issuer comes from the realm’s frontend URL orKC_HOSTNAME. - The token must carry Subact ID’s audience. Most providers put only the client itself in
audby default. Add a mapper or claim rule that emits Subact ID’s audience, or every exchange fails with an audience mismatch.
A token must also carry sub. In Keycloak, keep the built-in basic client scope when you set a
client’s default scopes.
Learning a person may no longer be acted for
Section titled “Learning a person may no longer be acted for”At every exchange and refresh, the control plane checks its own block list. A person on it is
refused. SubactId:UpstreamIdp:SponsorCheck:Mode decides what else it does.
poll, the default
Section titled “poll, the default”The control plane also asks the provider’s admin API whether the person is still active. It
speaks Keycloak’s admin API. A person disabled or deleted at the provider fails their next refresh
within one expires_in, even if nothing told the control plane. A provider that cannot answer
counts as no: the request gets temporarily_unavailable.
On a refresh, the answer is reused for at most SponsorCheck:CacheTtl, which may not exceed the
token lifetime. On an exchange it is always asked fresh.
poll needs three more settings:
export SubactId__UpstreamIdp__SponsorCheck__UsersUrl=https://sso.example.com/admin/realms/corp/usersexport SubactId__UpstreamIdp__SponsorCheck__TokenUrl=https://sso.example.com/realms/corp/protocol/openid-connect/tokenexport SubactId__UpstreamIdp__SponsorCheck__ClientId=subactidThe control plane authenticates to the provider with a signed assertion (private_key_jwt),
checked against the control plane’s own /.well-known/jwks.json, so there is no client secret.
Its service account needs only to read users (view-users in Keycloak).
signals
Section titled “signals”The provider is not asked. The control plane acts only on what it is told:
- An operator block,
PUT /admin/sponsors/{sponsor_key}/block. It revokes the person’s live tasks and refuses them until the block is lifted. It works in both modes. - Back-channel logout. Set
SubactId:UpstreamIdp:BackchannelLogout:Audienceto the client people sign in to, the one the logout URI is registered on.POST /backchannel-logoutthen exists. A logout revokes the session’s tasks but does not block the person. - SCIM 2.0 provisioning, turned on by
SubactId:Scim:BearerToken. A deactivated or deleted user is blocked and their tasks revoked. - Shared Signals (CAEP and RISC events), turned on by
SubactId:Ssf:Issuer. A disabled account is blocked; a revoked session ends the person’s tasks.
Without a signal, a task runs until its own expiry, which max_task_ttl bounds. Use signals
when your provider has no Keycloak-shaped admin API.
export SubactId__UpstreamIdp__SponsorCheck__Mode=signals# The three SponsorCheck URLs above are refused in this mode: a leftover one usually means# somebody believes the provider is still being asked.Section 6 of the spec documents each receiver.
Keycloak
Section titled “Keycloak”Keycloak is the provider this project tests against, in poll mode. The quickstart runs one, and
CI applies the Terraform module for it. The control plane repository ships two tools that
configure an existing realm: a Terraform module and a kcadm script. Both create the audience
mapper, the client the control plane authenticates as, one client scope per delegable permission,
and, when given the Subact ID URL, the back-channel logout URL on the human-facing client.
For back-channel logout, also turn on Backchannel logout session required on the human-facing
client, so a logout names the session. Without a sid, a logout ends all of the person’s tasks.
The settings together
Section titled “The settings together”| Setting | poll |
signals |
|---|---|---|
SubactId:UpstreamIdp:Issuer or MetadataUrl |
one of | one of |
SubactId:UpstreamIdp:Audience |
required | required |
SubactId:UpstreamIdp:SponsorCheck:Mode |
poll |
signals |
SubactId:UpstreamIdp:SponsorCheck:UsersUrl |
required | refused |
SubactId:UpstreamIdp:SponsorCheck:TokenUrl |
required | refused |
SubactId:UpstreamIdp:SponsorCheck:ClientId |
required | refused |
SubactId:UpstreamIdp:SponsorCheck:CacheTtl |
optional | refused |
SubactId:UpstreamIdp:SponsorKeyClaim |
optional | optional |
SubactId:UpstreamIdp:BackchannelLogout:Audience |
optional | optional |
SponsorKeyClaim is the claim each task records the person under, and what a block and a signal
name them by. Leave it at sub unless the provider’s sub differs from the identifier its SCIM
or signals use. It does not change the token’s sub, and poll still asks the provider by sub.
Tasks started before a change stay keyed by the old claim until they expire.
Every setting is in configuration.
© 2026 Nikola Živković PR Agencija za programerske usluge Novi Sad. Subact ID is its product.