OIDC SSO for AlmaForge
AlmaForge authenticates users through an external identity provider and maps their claims to roles. Users do not need a separate AlmaForge password. AlmaForge supports any identity provider that speaks OpenID Connect.
After a user signs in at the IdP, AlmaForge receives an ID
token, maps the claims on that token to AlmaForge roles, and
issues short-lived certificates for the session. The
integration is built into almad and is configured
through an
OIDCConnector
resource. For the protocol details, see
How AlmaForge uses OIDC.
This page describes the generic OIDC flow. If you use one of the providers below, start with the provider-specific guide instead. The setup clicks are different, and the generic page only shows the shape of the final YAML:
- Microsoft Entra ID
- Google Workspace
- Okta
- GitHub SSO (uses a plain OAuth2 flow, not OIDC)
The generic flow on this page works for Keycloak, Authentik, Auth0,
Zitadel, Dex, Ory Hydra, Keycloak-compatible Red Hat SSO, and
anything else that exposes a standards-compliant
/.well-known/openid-configuration document.
TL;DR
- Register AlmaForge as an OAuth2 client at your IdP and capture the client ID and secret (Step 1).
- Note the IdP's issuer URL (Step 2).
- Write the
OIDCConnectorYAML from Step 3. - Apply and verify with a real login (Step 4).
Prerequisites
Before you start, make sure you have:
- A running AlmaForge cluster with an Auth Service that you
can reach over the network and a working
almaCLI pointed at it. - Administrative access to an OIDC-compliant identity provider and the ability to register a new OAuth2 client in it.
- Outbound HTTPS egress from
almadto the IdP from every auth host. The integration makes no inbound connections beyond the browser redirect that the user's own browser performs. - The public address of your AlmaForge Proxy Service, for
example
almaforge.example.com. You will register this host with the IdP as the OIDC callback URL. - A AlmaForge role with permission to read and write
OIDCConnectorresources. The presetadminrole already has this. - Recommended: read Break-glass access and SSO-first behavior before you apply this connector on a production cluster. If it is the cluster's only connector, a configuration error prevents new SSO logins.
Configuration
Step 1. Register as an OAuth2 client
Sign in to your identity provider's admin interface and register a new OAuth2 / OIDC client. You will need to provide:
- Client type: confidential (web application). AlmaForge holds a client secret. Public client types and PKCE-only flows are not supported.
- Grant types:
authorization_code. AlmaForge does not use refresh tokens. Each new AlmaForge session starts with a fresh browser login to the IdP. - Redirect URI: exactly
https://<proxy-host>/api/v1/oidc/callback, substituting your own proxy host. If your IdP insists on exact matching including trailing slashes, include or omit the slash to match what AlmaForge actually redirects to. - Scopes:
openidis required. Without it the IdP returns an OAuth 2.0 access token but no ID token, and AlmaForge has no identity claims to map. Addemailandprofilefor the standard OIDC user identity claims.groupsis not a standard OIDC scope. Some providers (Keycloak, Authentik, Okta custom auth servers) expose group membership through agroupsscope. Microsoft Entra ID carries groups once you add the groups claim to the token, and a tenant setting decides whether it sends object IDs or names. See Microsoft Entra ID for why to pick object IDs. Google does not put groups in the ID token at all and needs a separate API call. List whatever scopes your IdP documents for the claims you reference inclaimsToRoles.
After the client is created, capture two values:
- The client ID.
- The client secret. Most IdPs only show it once. Save it immediately and store it in the same secret manager you use for production credentials. Restrict access to the secret and rotate it if it is exposed.
Step 2. Locate the issuer URL
The issuerURL is the base path of your IdP's OIDC discovery
document. Take the URL of the discovery document and strip
/.well-known/openid-configuration from the end.
For example:
| Discovery URL | issuerURL |
|---|---|
https://accounts.google.com/.well-known/openid-configuration | https://accounts.google.com |
https://keycloak.example.com/realms/main/.well-known/openid-configuration | https://keycloak.example.com/realms/main |
https://auth.example.com/application/o/alma/.well-known/openid-configuration | https://auth.example.com/application/o/alma |
AlmaForge fetches and caches this document when a login first uses the connector. If the IdP is not reachable from the auth hosts, the login fails with a discovery error. See Troubleshooting for the failure shape and When connector changes take effect for the cache behavior.
Step 3. Write the OIDCConnector
Write a YAML file describing the connector:
# Generic Keycloak connector with group-to-role mappings.
apiVersion: almaforge.com/v1
kind: OIDCConnector
metadata:
name: keycloak
spec:
display: Keycloak
issuerURL: https://keycloak.example.com/realms/main
clientID: alma
clientSecret: <client-secret>
scope:
- email
- profile
- groups
claimsToRoles:
- claim: groups
value: /admins
roles: [admin, access]
- claim: groups
value: /developers
roles: [access]
Replace:
- The
issuerURL,clientID, andclientSecretwith the values from Step 1 and Step 2. - The
claimsToRolesentries with your own claims and AlmaForge roles. Literal values match case-sensitively. Claims whose value is an array (for examplegroups) are matched per element. A user whosegroupsclaim contains[/admins, /developers]matches both avalue: /adminsentry and avalue: /developersentry, and receives the union of the assigned roles.
The connector metadata.name labels its button on the sign-in
page when spec.display is unset, and identifies the connector in sign-in audit records. Pick something
short and memorable. For the full field list, see the
OIDCConnector reference.
Continue to Apply and verify. For provider-specific sign-in requirements, see Advanced configuration.
Step 4. Apply and verify
AlmaForge has no local accounts. If this is the only connector, a wrong claim mapping or expired client secret prevents new logins to the Web UI and CLI. Confirm your break-glass procedure works before running the next command on a production cluster.
Apply the connector:
alma apply -f oidc.yamlalma apply is idempotent. Re-running it updates the
existing connector in place. Inspect the result with:
alma get oidcconnector/keycloak -o yamlThe clientSecret field is redacted in the output unless you
pass --with-secrets.
Verify the connector by completing a real login and checking the resulting roles, target logins, and audit events. Test an account from each mapped group before relying on the connector for production access.
Open the cluster's audit log to check the events for your test account. Audit events are stored separately from the service journal. Journal copies are emitted only at debug level.
Sign out and attempt an SSO login:
alma logoutalma login --proxy=almaforge.example.comA browser opens the cluster's sign-in page. Pick the connector,
authenticate at the IdP, and alma prints the roles,
logins, and expiration on the session. A successful first-time login
produces user-login, user-creation, and certificate-creation audit
records. These illustrative JSON excerpts show selected fields for a
Microsoft Entra login:
[
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"code": "T1001I",
"type": "UserLogin",
"verb": "login",
"actor": {
"name": "alice@example.com",
"type": "user"
},
"status": {
"success": true
},
"userLogin": {
"method": "oidc",
"attributes": {
"connector_id": "microsoft",
"username": "alice@example.com"
}
}
}
},
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"code": "T1002I",
"type": "UserCreate",
"verb": "create",
"actor": {
"name": "system",
"type": "system"
},
"objectRef": {
"kind": "User",
"name": "alice@example.com"
},
"userCreate": {
"connector": "microsoft",
"roles": [
"admin"
]
}
}
},
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"code": "TC000I",
"type": "CertificateCreate",
"verb": "create",
"certificateCreate": {
"certType": "user",
"identity": {
"user": "alice@example.com",
"roles": [
"admin"
],
"routeToCluster": "almaforge.example.com",
"logins": [
"tester",
"ec2-user",
"root"
],
"kubernetesCluster": "production-k8s",
"kubernetesGroups": [
"system:masters"
],
"traits": {
"groups": [
"44d56e07-85fc-4000-8000-000000000001",
"7907db2a-2e6b-4000-8000-000000000002"
],
"name": [
"Alice Example"
],
"preferred_username": [
"alice@example.com"
]
},
"clientIp": "203.0.113.42",
"expires": "2026-09-09T11:02:03.000000Z"
}
}
}
}
]
Each event tells you something different:
| Event | Code | What it proves |
|---|---|---|
UserLogin | T1001I | The SSO login succeeded. Failed SSO logins use T1001W, spec.status.success set to false, and a spec.status.error field. |
UserCreate | T1002I | First-time SSO logins materialize a AlmaForge user on the fly. The spec.userCreate.connector field names the connector that provisioned the user. The spec.userCreate.roles field shows which roles were assigned by claimsToRoles. Subsequent logins emit UserUpdate (T1003I) instead. |
CertificateCreate | TC000I | The short-lived certificates were issued. This is the event with the full diagnostic payload. |
Read the CertificateCreate event carefully. It contains:
- The IdP claims, stored under
spec.certificateCreate.identity.traitsusing their claim names, such asgroups,preferred_username, andname. Compare these values against thevaluefields in yourclaimsToRolesmapping. Matching is exact and case-sensitive. - The derived cluster identity, nested under
spec.certificateCreate.identity:roles,logins,kubernetesGroups,kubernetesUsers,databaseNames,databaseUsers,routeToCluster,expires. If a user signed in but cannot SSH into anything, this block shows whether theloginsarray has what they need. - The client address, when present, under
spec.certificateCreate.identity.clientIp. Correlate records by user and time. TheUserLoginevent carries the SSO request ID underspec.userLogin.attributes.request_id.CertificateCreatedoes not carry that field.
To inspect the claims stored by a successful login, use
alma get user/<username> -o yaml and read spec.traits.
For an SSO authentication failure, look for UserLogin with
spec.status.success set to false and a spec.status.error field. Do not use the absence of a
certificate event alone to diagnose a failed login. See
Troubleshooting for common errors.
If UserLogin shows spec.status.success set to true and CertificateCreate lists the
expected roles and claims, the integration is working. For
running it in production, see Operations.
Advanced configuration
The default settings cover a standard setup. Adjust these options when your identity provider or your organization's sign-in policy requires them.
ACR values
Set spec.acrValues to ask the IdP to assert a specific
Authentication Context Class Reference. AlmaForge adds the
value as an acr_values query parameter on the authorization
request and then verifies that the returned ID token's acr
claim matches. If the claim is missing or different, the
callback is rejected with a Failed to calculate user attributes error. Use this when your IdP requires an explicit
assurance level for a login.
For example, set spec.acrValues: urn:mace:incommon:iap:silver.
Unset by default. When unset, no acr_values parameter is
sent and no acr claim check runs.
Max age
Set spec.maxAge to require that the IdP authenticated the user
within the given duration. AlmaForge adds the value as a
max_age query parameter on the authorization request, which
the OIDC spec (section 3.1.2.1) requires the IdP to honor by
prompting for re-authentication if the session exceeds the
limit. The same section requires the IdP to report when it
authenticated the user, in an auth_time claim, and
AlmaForge checks that claim before accepting the login.
For example, set spec.maxAge: 24h.
Unset by default, which means the upstream session lifetime is
controlled entirely by the IdP. 0s is the same as unset. The
value must not be negative.
An IdP that ignores max_age does not leave you quietly holding
a weaker guarantee than you asked for. Its response arrives with
no auth_time claim, or with one outside the window, and the
login is refused rather than accepted. A 30-second allowance
absorbs clock difference between the IdP and the cluster in
either direction.
Setting maxAge against an IdP that does not implement it means
nobody using that connector can log in, so test it on one
connector before you rely on it. Google does not implement
max_age and does not return auth_time, so do not set this
field on a Google Workspace connector.
PKCE
Set spec.pkceMode to control Proof Key for Code Exchange on
the authorization code flow. PKCE binds the authorization code
to the same client that started the request, which closes a
specific class of authorization-code interception attacks
between the browser and almad.
Three modes:
auto(the default whenpkceModeis unset). AlmaForge fetches the IdP's discovery document and enables PKCE only ifcode_challenge_methods_supportedcontainsS256. This is the right setting for standards-compliant IdPs that advertise PKCE correctly.enabled. AlmaForge always sends acode_challengeandcode_verifier. Use this when an IdP supports PKCE but does not advertise it in the discovery document. If the IdP actually does not support PKCE, the authorization request will fail.disabled. AlmaForge never uses PKCE for this connector. Use this only if an IdP claims to support PKCE in discovery but rejects the actual parameters.
The challenge method is always S256. plain is not
supported.
Prompt
Set spec.prompt to override how the IdP reauthenticates or
re-consents the user when alma login opens the
authorization URL. AlmaForge passes the value through as the
standard OIDC prompt query parameter (section 3.1.2.1).
For example, set spec.prompt: login.
The four values the OIDC spec defines:
none: the IdP must not display any interactive UI. If the user is not already signed in, the authorization request fails. Useful for automation flows where a human prompt would break the path.login: the IdP must prompt for reauthentication.consent: the IdP must prompt for explicit consent before returning claims.select_account: the IdP must let the user pick between accounts.
The default is select_account, which asks the IdP to show an
account picker. Set another supported value explicitly when your
sign-in policy requires different behavior.
Username claim
Set spec.usernameClaim to pick which OIDC claim AlmaForge
uses as the user's AlmaForge username on first login.
metadata.name on the resulting User resource is set to the
value of this claim.
For example, set spec.usernameClaim: preferred_username.
When usernameClaim is unset, AlmaForge falls back through
a fixed sequence of claims and uses the first one that is
present and non-empty:
email: must parse as a valid email address.preferred_usernameupn
The fallback is why running OIDC against an IdP that emits
email as the first-class user identifier usually "just
works" without any usernameClaim on the connector. Set
usernameClaim explicitly when your IdP uses a different
identifier for humans (for example, a numeric Active Directory
SID or an internal username) and you want that identifier to
show up as the AlmaForge username instead.
A login where both usernameClaim is set and the claim it
names is missing from the ID token fails at the callback with
usernameClaim missing from claims. A login where the named
claim is present but its value is empty or not a string fails
with usernameClaim has an invalid value.
How AlmaForge uses OIDC
AlmaForge uses OpenID Connect on top of the
OAuth 2.0 authorization code flow.
AlmaForge registers at the IdP as a confidential client,
meaning it holds a clientSecret that the user's browser never
sees. When a user signs in through an OIDC connector, the full
flow is:
almaopens the user's browser to the cluster's sign-in page. The user picks the connector there, and the cluster redirects the browser to the IdP's authorization endpoint with the registered callback URL.- The IdP authenticates the user under its own policy (MFA,
session rules, conditional access, and anything else the IdP
enforces) and redirects the browser back to
https://<proxy-host>/api/v1/oidc/callbackwith an authorization code. - AlmaForge exchanges the code at the IdP's token endpoint,
authenticating the exchange with
clientIDandclientSecret. The IdP returns an ID token plus an access token. - AlmaForge verifies the ID token. It fetches the IdP's
signing keys through the discovery document, checks the
token signature, and validates the
iss,aud,exp, andnonceclaims. A login with an invalid token is rejected here. Ifemail_verifiedis false on the token, AlmaForge also rejects the login unlessspec.allowUnverifiedEmailis set. - AlmaForge reads the claims on the verified ID token, runs
them through the
claimsToRolesmapping on the connector, and assigns the matching AlmaForge roles to the session. It then issues short-lived X.509 and SSH certificates and hands them back toalma.
The redirect URL is always
https://<proxy_public_addr>/api/v1/oidc/callback. AlmaForge
computes it at request time from the proxy's advertised endpoint,
ALMA_ADVERTISE, and the fixed
callback path. The connector itself has no redirectUrl
field. Every AlmaForge proxy serves the same callback path. If the
IdP rejects the callback as unregistered redirect URI, the URL you
registered in the IdP does not match the URL served by your proxy,
typically a scheme or port mismatch.
The IdP session and the AlmaForge session are decoupled.
Running alma logout ends the AlmaForge session but
does not log the user out of the IdP. The next AlmaForge login
through the same browser may complete silently if the IdP still
has a valid session cookie. To force a fresh IdP login, log out
of the IdP directly.
Troubleshooting
Callback rejected as unregistered redirect URI
The URL registered in the IdP differs from the URL AlmaForge
sends as redirect_uri, usually by scheme (http vs https),
port (explicit :443 vs none), or a trailing slash. AlmaForge
sends https://<proxy_public_addr>/api/v1/oidc/callback.
Take that exact string (default port 443 is omitted) and
register it verbatim in the IdP.
Discovery document unreachable
The auth service cannot reach
<issuerURL>/.well-known/openid-configuration. Check egress
firewall rules from the auth hosts. For air-gapped clusters, the
IdP must be inside the same network. Every auth instance must be
able to reach the IdP independently. See
High availability for why.
Login fails with "OIDC provider did not verify email"
The IdP returned email_verified:false. AlmaForge refuses
logins with an unverified email by default because AlmaForge
uses the email claim as a user identifier. Mark the user as
verified in the IdP.
Setting spec.allowUnverifiedEmail: true on the connector
disables the check, but only do this if the IdP is trusted to
verify user identity by means other than email ownership. With
the check disabled, any user who can edit their own profile at
the IdP can sign in to AlmaForge as the holder of an
arbitrary email, including the email of another AlmaForge
user. Treat this flag the same way you would treat disabling
email verification on a password manager.
Login fails with "failed to calculate user attributes"
The ID token did not match any claimsToRoles entry. Set
ALMA_LOG_LEVEL=debug on the Auth host, log in again, and read
every claim the token contained:
journalctl -u almad -f | grep 'Extracted claims'Typical causes:
- Missing
groupsscope on the connector. - The IdP emits groups as a separate lookup, not as a token claim.
- The claim name in your mapping is wrong (
roles,realm_access.roles, etc.).
Login fails with "invalid_client" on token exchange
The clientSecret on the connector does not match what the IdP
has on file. The secret was mistyped, rotated, or belongs to a
different OAuth app. Re-copy it from the IdP and re-apply the
connector.
No roles match the login
The login is rejected if no mapping assigns a role. Literal claim
values are case-sensitive. Paths in
claims (for example Keycloak's /admins) include the leading
slash. Okta group names do not. For a rejected login, inspect the
debug claims described above. After a successful test login, copy
the value from spec.certificateCreate.identity.traits in its CertificateCreate audit record
or from spec.traits in the stored user record.
If the login succeeds but access still looks wrong, inspect the stored user record directly:
alma get user/<username> -o yamlThis only helps after a successful login. A failed SSO login does not create or update the user record.
Check spec.createdBy.connector, spec.identities, and the
keys under spec.traits to confirm which connector issued the identity
and which claim values AlmaForge actually stored for the
session.
Login fails with "already bound to a different subject"
The cluster username already belongs to a different person at the identity provider, so the login was refused rather than allowed to take the account over.
An account is bound to the sub claim of whoever first signed in
under that name. sub is the identifier a provider guarantees is
unique and never changes. The username is not, because several
providers let a user edit the claim it comes from.
Two things reach this legitimately:
- The person genuinely changed at the IdP. An account was
deleted and recreated, or a contractor's mailbox was reassigned
to someone new. The old AlmaForge user is stale. Delete it
with
alma delete user/<username>and let the next login recreate it, which also drops the previous holder's access requests and locks referencing that name. - You are looking at an impersonation attempt. Compare the
subin the failed login's audit event againstspec.identities[].subjecton the stored user before deleting anything.
alma get user/<username> -o yaml shows the bound
subject. Do not edit it by hand to make a login succeed.
Operations
Break-glass access
Use the auth host's local admin identity to recover when the SSO
connector is broken, the IdP is unreachable, or AuthConfig is
misconfigured. Follow the break-glass procedure
to establish that identity before inspecting or changing connectors.
Once authenticated as the recovery admin, inspect the connector and remove it only when a replacement is ready:
sudo alma get oidcconnectorsudo alma delete oidcconnector/<name>Typical SSO failure modes that need break-glass:
- Expired
clientSecreton the connector. - Misconfigured
claimsToRolesthat assigns zero roles to real users. - Rotated IdP signing key that the per-instance discovery cache has not picked up yet.
- IdP itself unreachable.
- A connector deleted or renamed while it was the only one.
Test the break-glass workflow in staging before a real incident. See the Break-glass access guide for full command examples and hardening advice.
Access changes
Claims, group membership, and team membership are resolved at
login time. Changing a group, team, or connector role mapping affects
the next login. It does not rewrite an issued certificate. Certificate
expiry prevents new connections. Ending active sessions at expiry
also requires disconnectExpiredCert.
The cluster default session TTL on
AuthConfig falls back
to 12 hours. A role-specific max session TTL can shorten that
window. To cut off access sooner, lock the user
and remove the corresponding access at the IdP. Locks take effect as they reach
the services handling the user's access, typically within seconds over a
healthy Auth connection.
Identity provider availability
SSO connectors only contact the identity provider during login and callback handling. If the IdP is unreachable, new logins through that connector fail until it recovers. Existing sessions are not terminated by the IdP outage and remain subject to the session policy. New connections using an existing certificate remain subject to its expiry and cluster access policy. This applies to OIDC, Google, Microsoft, Okta, and GitHub connectors.
Secret storage
clientID and clientSecret on
OIDCConnector and
GithubConnector
accept either literal values or expansions. For production, keep
them outside the manifest:
Set spec.clientID to ${env:OIDC_CLIENT_ID} and
spec.clientSecret to ${env:OIDC_CLIENT_SECRET}.
${env:...} reads from the auth service environment. This fits
systemd EnvironmentFile= setups such as
/etc/almaforge/almad.env, container --env-file, and similar
deployments. ${file:...} reads a file on the auth host at
login time. If you use ${file:...}, every auth host needs the
same file at the same path.
Inline secrets still live in the cluster state store. A backup
of that store is a backup of every inline SSO credential. Protect
backups accordingly, and restrict alma get --with-secrets
to a narrow set of identities. Re-apply the connector only if
you change the expansion expression itself.
SSO-first behavior
AlmaForge authenticates people only through an identity provider.
There are no local accounts or password login. Applying an
OIDCConnector
therefore enables sign-in directly, with no separate activation step
or setting that restores a local login path.
The connector is a single point of failure for human access. A wrong claim mapping, expired client secret, rotated signing key, or unreachable identity provider prevents every sign-in. Use break-glass access to recover.
Until a connector exists, a new cluster has no sign-in method at all. The Web UI says so on its sign-in page and prints the break-glass command to use for first-run setup. See the Standalone guide.
Run one SSO connector per cluster. For a dev cluster, migration window, or disaster-recovery scenario that needs more than one, see Multi-SSO.
Claims, traits, and role templates
When a user signs in through an OIDC connector, AlmaForge
converts supported ID-token claims into traits on the user
record. Trait keys retain the claim names, such as email,
groups, and preferred_username. The user record's
spec.identities and the login audit event identify the connector.
On the live demo stand, the user record for a Microsoft login looks like this:
alma get user/alice@example.com -o yamlkind: User
apiVersion: almaforge.com/v1
metadata:
name: alice@example.com
labels:
internal/expires: "1775921955"
spec:
createdBy:
connector:
id: microsoft
identity: alice@example.com
subject: 8f14e45f-ea6a-4c1d-9b2e-7d3a0c5b1f92
type: OIDC
createdAt: "2026-04-10T15:39:15Z"
user:
name: system
identities:
- id: microsoft
identity: alice@example.com
subject: 8f14e45f-ea6a-4c1d-9b2e-7d3a0c5b1f92
type: OIDC
roles:
- admin
traits:
groups:
- 44d56e07-85fc-4ab8-9146-738c259a401c
- 7907db2a-2e6b-46c6-ae9b-91dbaddb156c
name:
- Alice Example
preferred_username:
- alice@example.com
The record shows three important details:
metadata.labels["internal/expires"]records the expiry as Unix seconds. SSO user records are temporary and refreshed on each login. Their lifetime follows the requested certificate lifetime, capped by the assigned roles and the cluster's certificate limit. An expired record is removed, and the next sign-in creates a fresh one. Manage durable assignments in the IdP and connector rather than editing the user record.spec.identitieslists the external identities this user has signed in through. On a cluster with a single connector (the recommended shape), this list has exactly one entry. A second connector is refused by default. See Multi-SSO for the flag that turns that behavior off and for how merging works when it is on.spec.traitsis a flat map keyed by claim name. Role templates read these values throughexternal. The whole trait map is replaced in full on every login. There is no cross-login trait merging.
Role templates against claims
AlmaForge role templates read traits through the external
variable. The bare dot form works for plain identifier names:
# Role template that maps a plain OIDC claim to the SSH login.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: dev
spec:
allow:
serverLogins:
- '{{external.preferred_username}}'
Bracket syntax also works and supports claim names that contain
characters such as /:
# Role template for OIDC claim names that require bracket syntax.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: dev
spec:
allow:
serverLogins:
- '{{external["preferred_username"]}}'
serverLabels:
team: '{{external["groups"]}}'
Use the exact claim name your provider returns. With multiple connectors, the same name can carry different values depending on the provider used at login. See Multi-SSO for migration and recovery workflows.
When connector changes take effect
alma apply -f oidc.yaml updates the
resource in the cluster state store immediately. The next login
attempt uses the new value. There is no polling interval, no
plugin reload, and no almad restart required. A new
clientSecret or a new claimsToRoles entry takes effect on
the next login.
The cache worth knowing about is the OIDC discovery document.
When an auth instance first resolves a connector, it fetches
<issuerURL>/.well-known/openid-configuration and caches the
result in memory for up to one hour.
Editing the connector refreshes it on the next login, so a repointed issuer or a rotated secret is live immediately.
What can still lag is a change made at the IdP with the
connector untouched: new endpoint URLs, a new jwks_uri, or a
different supported-algorithms list. To force an immediate
refresh, restart almad on the affected hosts.
Signing key rotation is handled separately through the IdP's
JWKS endpoint. If a login starts failing immediately after you
rotate keys at the IdP, check that the IdP published the new
key before removing the old one, then restart
almad on the affected hosts to force a fresh JWKS
fetch if the failures persist.
High availability
In a multi-instance almad deployment, each auth node
runs its own copy of the OIDC connector code and maintains its
own in-memory caches. Two assumptions are worth making explicit
before sharding traffic across auth nodes:
- Every auth instance must reach the IdP independently. The OIDC discovery document, the token endpoint, and (for Google) the Cloud Identity API are all called per-instance, not proxied through a leader. If firewall rules allow auth node A to reach the IdP but not auth node B, logins succeed against A and fail against B depending on which instance the load balancer sent the callback to. Test egress from every auth host.
- The OIDC discovery cache is per-instance. Changing the connector's provider settings or resolved credentials refreshes the client on its next use. Changes made only at the IdP can remain cached until the TTL expires. See When connector changes take effect before restarting auth instances to refresh discovery.
- Files behind a
${file:PATH}reference must exist on every auth host. If a connector field holds${file:/etc/almaforge/google-sa.json}, the file must be present, readable by root, and identical on every auth host. The connector resolves the reference on each login, so a missing or stale file on one auth node produces login flaps. The user succeeds or fails depending on which auth instance catches the callback.
Removal
Delete the connector resource:
alma delete oidcconnector/<name>Existing sessions issued through the connector remain valid until their certificates expire. No new sessions will be issued.
If the connector was the only one on the cluster, removing it leaves no way for anyone to sign in. Recover with break-glass access and apply a replacement.