Okta SSO for AlmaForge
AlmaForge authenticates users against Okta through the standard OIDC flow. Each AlmaForge role is mapped to an Okta group. Adding a user to the group grants them the role on their next sign-in. Removal also takes effect at the next sign-in, so lock the user when access must end immediately. AlmaForge does not keep passwords for SSO users.
The integration is built into almad. Configure it with
an OIDCConnector
resource. See the generic OIDC guide for the full
field list and the shared verification, high-availability, and
break-glass content. This page covers only the Okta-specific
clicks.
This guide uses an Okta Custom Authorization Server to define the groups scope, claims, and access policy in one place. Okta's org authorization server also supports a groups claim in ID tokens. For that alternative, configure the claim on the app using Okta's groups guide and use the org issuer URL in the generic OIDC setup.
TL;DR
- Create an OIDC application in Okta and capture the client ID and secret (Step 1).
- Create a custom authorization server and note its Issuer URI (Step 2).
- Add a
groupsscope and claim to the authorization server (Step 3). - Add an access policy and rule so the auth server will issue tokens (Step 4).
- Apply the
OIDCConnectorfrom Step 5. - Verify with a real login (Step 6).
Limitations
Test recovery before changing SSO. AlmaForge has no local
accounts. If this is the only connector, a failure prevents new logins. Review
SSO-first behavior on the OIDC page,
and confirm your break-glass procedure works
before running apply on a production cluster.
This guide uses a custom authorization server. Confirm that your Okta organization has API Access Management before following the custom-server steps. The org-server alternative above supports group-based sign-in without those steps.
The access policy is a non-obvious setup gotcha. Custom
authorization servers do not ship with a default access policy.
Without an explicit policy and rule on the auth server, Okta
returns access_denied at token-exchange time even though the
user's browser sign-in appeared to succeed. See
Step 4
for the required policy shape.
Okta rate limits. Okta enforces strict per-org rate limits on
the token and userinfo endpoints. A multi-node AlmaForge auth
cluster with every node talking to Okta independently can hit
those limits during a large login spike. Monitor for
429 Too Many Requests in the auth journal and contact Okta
support for a rate-limit uplift if this becomes a real problem.
Prefer group assignment over "Allow everyone" on the app.
Assign the app to the groups that should use AlmaForge. The IdP
app assignment and the connector's claimsToRoles mapping are both
required: AlmaForge rejects a login when no mapping grants a role.
Keep credentials outside the manifest. clientSecret can load
its value from a file or environment variable. See
Secret storage.
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. - An Okta organization and an admin account that can create OIDC applications and edit authorization servers. The default Super Admin role covers this.
- Okta's API Access Management feature enabled. Required for custom authorization servers.
- One or more Okta groups that you will map to AlmaForge roles.
- The public address of your AlmaForge Proxy Service, for
example
almaforge.example.com. - Outbound HTTPS egress from
almadto your Okta domain (for exampleexample.okta.com) from every auth host. - A break-glass procedure in place and tested.
Step 1. Create an OIDC application
In the Okta admin console, open Applications → Applications → Create App Integration. Select:
- Sign-in method: OIDC - OpenID Connect.
- Application type: Web Application.
Click Next and configure:
- App integration name: something recognizable, for example
AlmaForge. - Grant type: enable Authorization Code. AlmaForge does not request refresh tokens for this sign-in flow.
- Sign-in redirect URIs:
https://almaforge.example.com/api/v1/oidc/callback, substituting your own proxy host. - Sign-out redirect URIs: optional, typically
https://almaforge.example.com/. - Controlled access: assign the app to specific Okta groups rather than leaving it as Allow everyone. See the warning below.
Click Save.
Assign the app only to the intended users or groups. AlmaForge also requires at least one matching role mapping before it creates the user session. Test both app assignment and role mapping with an account from each intended group.
On the application General tab, copy these two values:
- Client ID.
- Client secret (click the eye icon to reveal).
Store the client secret in the same secret manager you use for production credentials. Do not commit it to Git.
Step 2. Create a custom authorization server
In the Okta admin console, open Security → API → Authorization Servers → Add Authorization Server and configure:
- Name:
almaforge(or any label). - Audience:
almaforge(any string will do). Okta requires a value and echoes it back in the token'saudclaim. Thego-oidclibrary that AlmaForge uses to verify ID tokens automatically validatesaudagainst the configuredclientID, so you do not need to match the audience string anywhere in the connector. - Description: optional.
Click Save. On the new authorization server's Settings
tab, copy the Issuer URI. It will look like
https://<okta-domain>/oauth2/<auth-server-id>. This is the
value you will put into spec.issuerURL in the connector YAML.
Step 3. Add a groups scope and claim
On the authorization server, open the Scopes tab and click Add Scope. Configure:
- Name:
groups. - Display name: Groups.
- Description: Return the user's Okta groups.
- Leave the defaults for everything else.
Switch to the Claims tab and click Add Claim. Configure:
- Name:
groups. - Include in token type: ID Token, Always.
- Value type: Groups.
- Filter: Matches regex with value
.*to include every group the user is a member of. Use Starts with or Equals to limit the emitted groups to a specific prefix or name. - Include in: The following scopes: groups.
Save the claim.
Step 4. Update the authorization server access policy
On the authorization server, open the Access Policies tab. Okta creates a default policy for the built-in authorization servers but not for custom ones. You need to add one before tokens will be issued.
Click Add Policy and fill in:
- Name: Default.
- Assign to: The following clients, then pick the OIDC application you created in Step 1.
Save the policy. Inside the policy, click Add Rule and configure:
- Rule name: Default.
- IF Grant type is: check Authorization Code.
- AND User is: Any user assigned the app.
- AND Scopes requested: Any scopes.
Save the rule.
Without this policy and rule on the custom authorization
server, Okta issues an access_denied error at token-exchange
time even though the browser sign-in appears to succeed. This
is the most common first-time setup failure. If you skip this
step you will debug a confusing error whose only fix is to come
back and finish it.
Step 5. Apply the OIDCConnector
Write a YAML file with the values you collected:
# Okta connector using a custom authorization server and group claims.
apiVersion: almaforge.com/v1
kind: OIDCConnector
metadata:
name: okta
spec:
display: Okta
issuerURL: https://<okta-domain>/oauth2/<auth-server-id>
clientID: <client-id>
clientSecret: <client-secret>
scope:
- email
- profile
- groups
claimsToRoles:
- claim: groups
value: almaforge-admins
roles: [admin, access]
- claim: groups
value: almaforge-developers
roles: [access]
Replace:
<okta-domain>and<auth-server-id>with the Issuer URI from Step 2. For examplehttps://example.okta.com/oauth2/aus1abc2defGHIjk34.<client-id>and<client-secret>with the values from Step 1.- The
claimsToRolesentries with your own Okta group names and AlmaForge roles. Group matching uses the Okta group name, not a slug or a UUID. Copy it verbatim from the Okta Directory → Groups page.
The scope list must include groups explicitly. Otherwise
Okta will not emit the claim even though you defined it in
Step 3. email and
profile are listed here so AlmaForge can populate the
username from the email claim.
AlmaForge has no local accounts. If this is the only connector, a wrong claim mapping or expired client secret prevents new logins. Confirm your break-glass procedure works before applying the connector to a production cluster.
Apply the connector:
alma apply -f okta.yamlStep 6. Verify the connector
Open the cluster's audit log for your test account. See SSO verification for event fields and the distinction from service logs.
Sign out and log in through Okta:
alma logoutalma login --proxy=almaforge.example.comA browser opens the cluster's sign-in page. Choose Okta. A
successful first-time login emits a UserLogin event and the
CertificateCreate event for the certificate it mints:
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"id": "5a4b3c2d-1e0f-4958-8776-655443322110",
"index": 0,
"timestamp": "2026-03-30T15:58:58.999000Z",
"clusterName": "almaforge.example.com",
"code": "T1001I",
"type": "UserLogin",
"verb": "login",
"actor": {
"name": "alice@example.com",
"type": "user"
},
"status": {
"success": true
},
"userLogin": {
"method": "oidc",
"attributes": {
"connector_id": "okta",
"connector_type": "oidc",
"email": "alice@example.com",
"name": "Alice Example",
"request_id": "8f0c1e6a-2f7e-4a7a-9f1e-1f0b6d0f1a2b",
"username": "alice@example.com"
}
}
}
}
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"id": "b1c2d3e4-f506-4718-8929-3a4b5c6d7e8f",
"index": 0,
"timestamp": "2026-03-30T15:58:58.999000Z",
"clusterName": "almaforge.example.com",
"code": "TC000I",
"type": "CertificateCreate",
"verb": "create",
"certificateCreate": {
"certType": "user",
"identity": {
"user": "alice@example.com",
"roles": [
"admin"
],
"logins": [
"tester",
"ec2-user",
"root"
],
"traits": {
"email": [
"alice@example.com"
],
"groups": [
"almaforge-admins",
"engineering"
],
"name": [
"Alice Example"
],
"preferred_username": [
"alice@example.com"
]
}
}
}
}
}
The field to check is the groups trait on the
CertificateCreate event. It contains the group names Okta
emitted on the groups claim, filtered by the regex you
configured in
Step 3. Each value in
your claimsToRoles.value must match one of these entries
verbatim. Matching is case-sensitive. If the groups trait
is missing or empty:
- The
groupsscope is not listed inspec.scopeon the connector (Step 5). - The
groupsclaim is not configured on the custom authorization server (Step 3), or the filter excludes every group the user is in. - The user is not assigned to the OIDC application in Okta (Step 1's Controlled access).
After a successful login, you can also run
alma get user/<username> -o yaml and inspect spec.traits.groups.
Read the full audit-event breakdown on the generic OIDC verification section.
Troubleshooting
access_denied after the callback
Okta returned access_denied or invalid_grant at token
exchange, even though the browser sign-in appeared to succeed.
The custom authorization server has no access policy and rule.
Open Security → API → Authorization Servers → <your-server>
→ Access Policies and add them. See
Step 4.
No roles match the login
The login is rejected if no mapping assigns a role. Either the groups claim is missing from the token
(Step 3 was skipped, or
the groups scope was not added to the connector's spec.scope
in Step 5), or the claim
contains group names that do not match claimsToRoles.value.
Set ALMA_LOG_LEVEL=debug on the Auth host, sign in again, and
copy the group name straight out of the claims Okta sent:
journalctl -u almad -f | grep 'Extracted claims'If the login succeeds but the stored identity still looks wrong, inspect the user record as described in Claims, traits, and role templates.
Invalid issuer URL
For this guide's custom-server setup, use
https://<okta-domain>/oauth2/<auth-server-id> as issuerURL.
Copy it from the authorization server's Settings tab. The org
issuer https://<okta-domain> applies only when you configured the
alternative org-server flow.
unauthorized_client
The client ID in the connector belongs to a different OIDC application than the one assigned to the authorization server's access policy, or the user is not assigned to the app at all. Re-check both and re-apply.
429 Too Many Requests
Okta returned 429 Too Many Requests on the token or userinfo
endpoint. Okta enforces strict per-org rate limits. In a
multi-node auth cluster during a login spike, every auth node
hits Okta independently. Confirm the volume is legitimate and,
if it is, contact Okta support for a rate-limit uplift.
Operations
Lifecycle
Apply connector changes before testing the next login. No
almad restart is needed. See
When connector changes take effect
for discovery caching and IdP-side changes.
High availability
Every auth node talks to Okta independently. A multi-node auth cluster must have working egress to your Okta domain from every auth host. Each auth instance also caches the OIDC discovery document per-instance for one hour. See High availability on the generic OIDC page for the full list of per-instance assumptions.
Egress requirements
Auth hosts require outbound TCP/443 to your Okta domain (for
example example.okta.com). No inbound connectivity from Okta
is required, and the integration does not open any listening
sockets.
Client secret rotation
Okta lets you generate a new client secret on the OIDC application's General tab. The procedure is:
- On the application General tab, click Generate new client secret. Do not delete the old one yet.
- Copy the new secret value.
- Run
alma apply -f okta.yamlwith the new value. - Run a test login through the connector to confirm the new secret works.
- Delete the old secret on the Okta app.
The connector uses one client secret at a time. Keep the previous secret valid until test logins succeed from every auth host, then retire it in Okta.
Secret storage
Load clientSecret from a file or environment variable using
Secret storage, which also covers protection
of inline credentials and backups.
Break-glass access
Test break-glass access before relying
on this connector. Okta-specific recovery cases include a deleted
authorization server or revoked access policy, alongside rotated
client secrets and incorrect claimsToRoles mappings.
Monitoring
Monitor the audit log for login outcomes. For diagnostics, inspect the Auth journal for token validation and the Proxy journal for browser callbacks. On each service host, tail OIDC-related messages with:
journalctl -u almad -f | grep -Ei 'okta|oidc'Check UserLogin failures and their spec.status.error field. See
SSO verification for event codes,
certificate fields, and debug-only journal copies. Okta rate-limit
errors include the HTTP status and Okta's description in the Auth
journal. Collect records from
audit event storage for SIEM ingestion.
Collect service diagnostics from the journal separately.
Removal
Delete the connector resource:
alma delete oidcconnector/oktaExisting sessions issued through the connector remain valid until their certificates expire. No new sessions will be issued.
On the Okta side, you can also delete the OIDC application and the custom authorization server. If other AlmaForge clusters share the same authorization server, leave it in place.