Skip to main content

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:

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​

  1. Register AlmaForge as an OAuth2 client at your IdP and capture the client ID and secret (Step 1).
  2. Note the IdP's issuer URL (Step 2).
  3. Write the OIDCConnector YAML from Step 3.
  4. 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 alma CLI 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 almad to 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 OIDCConnector resources. The preset admin role 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: openid is required. Without it the IdP returns an OAuth 2.0 access token but no ID token, and AlmaForge has no identity claims to map. Add email and profile for the standard OIDC user identity claims. groups is not a standard OIDC scope. Some providers (Keycloak, Authentik, Okta custom auth servers) expose group membership through a groups scope. 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 in claimsToRoles.

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 URLissuerURL
https://accounts.google.com/.well-known/openid-configurationhttps://accounts.google.com
https://keycloak.example.com/realms/main/.well-known/openid-configurationhttps://keycloak.example.com/realms/main
https://auth.example.com/application/o/alma/.well-known/openid-configurationhttps://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:

oidc.yaml:

examples/sso/oidc.yaml
# 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, and clientSecret with the values from Step 1 and Step 2.
  • The claimsToRoles entries with your own claims and AlmaForge roles. Literal values match case-sensitively. Claims whose value is an array (for example groups) are matched per element. A user whose groups claim contains [/admins, /developers] matches both a value: /admins entry and a value: /developers entry, 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​

Test break-glass before applying

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:

Terminal
alma apply -f oidc.yaml

alma apply is idempotent. Re-running it updates the existing connector in place. Inspect the result with:

Terminal
alma get oidcconnector/keycloak -o yaml

The 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:

Terminal
alma logoutalma login --proxy=almaforge.example.com

A 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:

JSON
[
{
"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:

EventCodeWhat it proves
UserLoginT1001IThe SSO login succeeded. Failed SSO logins use T1001W, spec.status.success set to false, and a spec.status.error field.
UserCreateT1002IFirst-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.
CertificateCreateTC000IThe 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.traits using their claim names, such as groups, preferred_username, and name. Compare these values against the value fields in your claimsToRoles mapping. 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 the logins array has what they need.
  • The client address, when present, under spec.certificateCreate.identity.clientIp. Correlate records by user and time. The UserLogin event carries the SSO request ID under spec.userLogin.attributes.request_id. CertificateCreate does 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 when pkceMode is unset). AlmaForge fetches the IdP's discovery document and enables PKCE only if code_challenge_methods_supported contains S256. This is the right setting for standards-compliant IdPs that advertise PKCE correctly.
  • enabled. AlmaForge always sends a code_challenge and code_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:

  1. email: must parse as a valid email address.
  2. preferred_username
  3. upn

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:

  1. alma opens 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.
  2. 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/callback with an authorization code.
  3. AlmaForge exchanges the code at the IdP's token endpoint, authenticating the exchange with clientID and clientSecret. The IdP returns an ID token plus an access token.
  4. 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, and nonce claims. A login with an invalid token is rejected here. If email_verified is false on the token, AlmaForge also rejects the login unless spec.allowUnverifiedEmail is set.
  5. AlmaForge reads the claims on the verified ID token, runs them through the claimsToRoles mapping 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 to alma.

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:

Terminal
journalctl -u almad -f | grep 'Extracted claims'

Typical causes:

  • Missing groups scope 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:

Terminal
alma get user/<username> -o yaml

This 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 sub in the failed login's audit event against spec.identities[].subject on 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:

Terminal
sudo alma get oidcconnectorsudo alma delete oidcconnector/<name>

Typical SSO failure modes that need break-glass:

  • Expired clientSecret on the connector.
  • Misconfigured claimsToRoles that 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:

Terminal
alma get user/alice@example.com -o yaml
YAML
kind: 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.identities lists 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.traits is a flat map keyed by claim name. Role templates read these values through external. 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-dot.yaml:

examples/sso/role-template-dot.yaml
# 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-bracket.yaml:

examples/sso/role-template-bracket.yaml
# 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:

Terminal
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.

References​