Connecting your identity provider
People sign in to TinyConductor with an identity provider over OIDC. This page says what the provider must give TinyConductor, then shows the setup for Keycloak, Microsoft Entra ID, Okta and Auth0: what to do on the provider’s side, and the TinyConductor settings that match. How sign-in, mapping rules and permissions work is described in Identity and access.
There are two ways to connect:
- Through the TinyBlox identity provider. TinyConductor trusts only the TinyBlox identity provider, which in turn signs people in with your directory. Every component then sees the same issuer and the same claims, and the provider differences below are handled once, in the TinyBlox identity provider. This is the recommended setup on the TinyBlox platform.
- Directly. TinyConductor trusts your own provider. There is one hop less, and TinyConductor’s mapping rules read your provider’s claims as they are. The sections below are for this setup.
Either way, TinyConductor is registered as one client of the provider it trusts, with the values in the next section.
What your identity provider must provide
Section titled “What your identity provider must provide”TinyConductor publishes its requirements as a client requirements document, a short machine-readable description that a provisioning tool can register at any OIDC provider, and that you can use as a checklist when you register it by hand. A running engine serves it, filled in with its own settings:
curl -s https://bpm.example.com/.well-known/tinyblox-client-requirementsBefore the engine runs, use the template. {publicUrl} is the engine’s public
host, such as bpm.example.com, and {clientId} the client id your provider
gives TinyConductor:
apiVersion: tinyblox.ai/v1alpha1kind: ClientRequirementscomponent: tinyconductorclients: - name: console type: confidential redirectUris: ["https://{publicUrl}/auth/callback"] postLogoutRedirectUris: ["https://{publicUrl}/"] grantTypes: [authorization_code] responseTypes: [code] tokenEndpointAuthMethod: client_secret_basic scopes: [openid, profile, email, groups] audience: "{clientId}" idTokenSigningAlg: RS256 acceptedSigningAlgs: [RS256, ES256] pkce: required pkceMethod: S256 requiredClaims: [sub, groups, roles] claims: subject: sub username: [preferred_username, email, sub] displayName: name mapped: [groups, roles] tokenLifetimes: clockSkewSeconds: 60 refreshTokens: false sessionIdleSeconds: 1800 sessionAbsoluteSeconds: 43200 logout: rpInitiated: true idTokenHint: true frontChannel: false backChannel: false secretRef: {secret: tinyconductor-oidc, key: client-secret}roles: [admin, operator, task-worker, tenant-operator, viewer]In plain words:
| What | Value |
|---|---|
| Kind of client | A web application with a client secret (a confidential client). A client without a secret (a public client) also works: leave the secret unset |
| Sign-in flow | Authorization Code with PKCE (S256). No implicit flow, no password grant |
| Redirect URI | https://bpm.example.com/auth/callback, exactly |
| After sign-out | https://bpm.example.com/, where the provider sends the browser back |
| Client authentication | The client id and secret as HTTP Basic credentials (client_secret_basic) |
| Scopes | openid, profile, email, and groups where your provider sends group membership only when that scope is asked for |
| Token signatures | RS256 or ES256. Tokens signed any other way are refused |
| Audience | Access tokens sent to the API carry TinyConductor’s audience in aud: the client id unless you choose another |
| Who a person is | The provider’s issuer and the person’s sub. The username comes from preferred_username, then email, then sub, unless you name another claim |
| Groups and roles | Whichever claims your mapping rules read, usually groups and roles |
| Token lifetimes | The ID token is checked once, at sign-in (allowing 60 seconds of clock difference); after that TinyConductor keeps its own session, which ends after 30 minutes without activity or after 12 hours. Refresh tokens are not used |
| Sign-out | TinyConductor sends the browser to the provider’s end-session endpoint with the ID token as a hint. Front-channel and back-channel logout are not used |
roles lists the role names a mapping rule can give: the built-in ones and any
you add.
Keycloak
Section titled “Keycloak”In Keycloak (in the realm your people sign in to):
- Clients → Create client. Client type OpenID Connect, client ID
tinyconductor. - Capability config: turn Client authentication on, and leave only Standard flow checked.
- Login settings: valid redirect URI
https://bpm.example.com/auth/callback, valid post-logout redirect URIhttps://bpm.example.com/. - Advanced → Proof Key for Code Exchange Code Challenge Method:
S256. - Credentials: copy the client secret.
- Client scopes → tinyconductor-dedicated → Add mapper → By configuration:
- Group Membership, token claim name
groups, Full group path off, added to the ID token and the access token; - Audience, included client audience
tinyconductor, added to the access token. Programs that call the API with a Keycloak token need it.
- Group Membership, token claim name
Keycloak sends realm roles in realm_access.roles and client roles in
resource_access.tinyconductor.roles; a mapping rule reads either by that
dotted path. The issuer is https://keycloak.example.com/realms/<realm>.
TinyConductor:
docker run -d --name tinyconductor \ -p 8080:8080 \ -v tinyconductor-pgdata:/var/lib/postgresql/data \ -v /srv/secrets/keycloak-client-secret:/run/secrets/oidc-client-secret:ro \ -e CONDUCTOR_OIDC_ISSUER=https://keycloak.example.com/realms/acme \ -e CONDUCTOR_OIDC_CLIENT_ID=tinyconductor \ -e CONDUCTOR_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc-client-secret \ -e CONDUCTOR_OIDC_REDIRECT_URI=https://bpm.example.com/auth/callback \ -e CONDUCTOR_OIDC_DISPLAY_NAME=Keycloak \ -e 'CONDUCTOR_AUTH_MAPPING_RULES_JSON=[{"claim":"groups","equals":"bpm-admins","roles":["admin"]},{"claim":"realm_access.roles","equals":"bpm-operator","roles":["operator"]}]' \ registry.tinyfactory.ai/tinyblox/tinyconductor-bundled:0.1.0Keycloak sends groups through the mapper, so the default scopes
(openid,profile,email) are enough, and the username is Keycloak’s own
(preferred_username). A local Keycloak, end to end
walks through the same setup on one machine with Keycloak’s command-line tool.
Microsoft Entra ID
Section titled “Microsoft Entra ID”In the Microsoft Entra admin center:
- App registrations → New registration. Name
TinyConductor, Accounts in this organizational directory only, redirect URI of platform Web:https://bpm.example.com/auth/callback. - Authentication: add a second Web redirect URI,
https://bpm.example.com/. Entra sends the browser back after sign-out only to a registered redirect URI. Leave Access tokens and ID tokens under implicit grant unchecked. - Certificates & secrets → New client secret: copy its Value.
- Token configuration → Add groups claim: Groups assigned to the application, emitted as Group ID in the ID token. Then Enterprise applications → TinyConductor → Users and groups: assign the groups that may use TinyConductor.
- Or, instead of groups, App roles → Create app role (for example value
TinyConductor.Admin, allowed for users and groups) and assign people or groups to it. App roles arrive in therolesclaim. - Token configuration → Add optional claim → ID:
email, if you want the console to show it. - Manifest: set
"requestedAccessTokenVersion": 2("accessTokenAcceptedVersion": 2in the older manifest format). Programs that call the API with an Entra access token need it: version 1 tokens name another issuer (https://sts.windows.net/…), which TinyConductor refuses.
Watch for:
- Many groups. When a person is in more than 200 groups, Entra leaves the
groupsclaim out and sends a link to look them up instead. TinyConductor does not follow that link, so such a person matches no group rule. Emitting only groups assigned to the application (step 4), or using app roles (step 5), avoids it. - Group ids.
groupsholds object ids, so a mapping rule names a group by its id, such as7c5a…-…. - Do not ask for the
groupsscope. Entra has no such scope and refuses the sign-in; keep the default scopes.
TinyConductor:
-e CONDUCTOR_OIDC_ISSUER=https://login.microsoftonline.com/<directory-id>/v2.0 \-e CONDUCTOR_OIDC_CLIENT_ID=<application-id> \-e CONDUCTOR_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc-client-secret \-e CONDUCTOR_OIDC_REDIRECT_URI=https://bpm.example.com/auth/callback \-e CONDUCTOR_OIDC_DISPLAY_NAME='Microsoft' \-e 'CONDUCTOR_AUTH_MAPPING_RULES_JSON=[{"claim":"roles","equals":"TinyConductor.Admin","roles":["admin"]},{"claim":"groups","equals":"<group-object-id>","roles":["operator"]}]'The username is the sign-in name in preferred_username, such as
wanda@contoso.com; Entra’s sub and oid are opaque ids and make poor
usernames. With requestedAccessTokenVersion 2, access tokens for the API
carry the application id as audience, which is the default
CONDUCTOR_OIDC_AUDIENCE. If you expose an API with its own application ID URI
instead, set CONDUCTOR_OIDC_AUDIENCE to what its tokens carry.
In the Okta Admin Console:
- Applications → Create App Integration: OIDC - OpenID Connect, Web Application.
- Grant type: Authorization Code only. Sign-in redirect URI
https://bpm.example.com/auth/callback, sign-out redirect URIhttps://bpm.example.com/. - Client Credentials: Client secret, and check Require PKCE as additional verification. Copy the client ID and the secret.
- Assignments: assign the groups that may use TinyConductor.
- Security → API → Authorization Servers → default → Claims → Add Claim:
name
groups, include in the ID token (Always) and in the access token, value type Groups, filter Starts withbpm-(or Matches regex.*for every group). Okta sends nogroupsclaim until such a filter exists. - On the same authorization server, Settings → Audience is the audience
of its access tokens (
api://defaultunless you change it).
TinyConductor:
-e CONDUCTOR_OIDC_ISSUER=https://acme.okta.com/oauth2/default \-e CONDUCTOR_OIDC_CLIENT_ID=<client-id> \-e CONDUCTOR_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc-client-secret \-e CONDUCTOR_OIDC_AUDIENCE=api://default \-e CONDUCTOR_OIDC_REDIRECT_URI=https://bpm.example.com/auth/callback \-e CONDUCTOR_OIDC_DISPLAY_NAME=Okta \-e 'CONDUCTOR_AUTH_MAPPING_RULES_JSON=[{"claim":"groups","equals":"bpm-admins","roles":["admin"]},{"claim":"groups","equals":"bpm-operators","roles":["operator"]}]'The issuer is the authorization server’s (…/oauth2/default), not the bare
Okta address: only that server issues access tokens other applications can
check. The groups claim configured in step 5 comes with the default scopes;
do not add a groups scope, which this server does not define. The username
is the Okta login in preferred_username, usually an email address.
In the Auth0 Dashboard:
-
Applications → Create Application: Regular Web Applications.
-
Settings: Allowed Callback URLs
https://bpm.example.com/auth/callback, Allowed Logout URLshttps://bpm.example.com/. Copy the domain, the client ID and the client secret. -
Credentials → Authentication Method: Client Secret (Basic).
-
Advanced Settings → OAuth: JSON Web Token signature algorithm
RS256. -
User Management → Roles: create roles such as
bpm-adminand assign them to people. -
Actions → Triggers → post-login: add an action that puts the roles in the tokens. Auth0 requires custom claims to be named by a URL you own:
exports.onExecutePostLogin = async (event, api) => {const roles = event.authorization?.roles ?? [];api.idToken.setCustomClaim('https://bpm.example.com/roles', roles);api.accessToken.setCustomClaim('https://bpm.example.com/roles', roles);}; -
For programs that call the API with an Auth0 token, Applications → APIs → Create API with an identifier such as
https://bpm.example.com/api: that identifier is the audience of its access tokens.
TinyConductor:
-e CONDUCTOR_OIDC_ISSUER=https://acme.eu.auth0.com/ \-e CONDUCTOR_OIDC_CLIENT_ID=<client-id> \-e CONDUCTOR_OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc-client-secret \-e CONDUCTOR_OIDC_AUDIENCE=https://bpm.example.com/api \-e CONDUCTOR_OIDC_REDIRECT_URI=https://bpm.example.com/auth/callback \-e CONDUCTOR_OIDC_DISPLAY_NAME=Auth0 \-e 'CONDUCTOR_AUTH_MAPPING_RULES_JSON=[{"claim":"https://bpm.example.com/roles","equals":"bpm-admin","roles":["admin"]}]'A mapping rule names the custom claim by its whole URL; the dots in it are not
read as a path. Auth0 sends no preferred_username, so the username is the
email address; set CONDUCTOR_OIDC_USERNAME_CLAIM to another claim if you prefer.
With the Helm chart
Section titled “With the Helm chart”The chart sets the same values under auth.oidc (issuer, clientId,
audience, redirectUri, scopes, displayName, usernameClaim, alias)
and reads the client secret from a Secret you create
(auth.oidc.clientSecret.existingSecret, key client-secret). Mapping rules go
in auth.mappingRules.
Several identity providers
Section titled “Several identity providers”TinyConductor can trust more than one provider, for example your staff directory and a partner’s. Each is known by its issuer and has a short alias; see Several identity providers for the settings.