For the complete documentation index, see llms.txt.

How to provision users into Chainguard from Okta with SCIM

Procedural tutorial on how to set up SCIM provisioning from Okta to the Chainguard platform.
  9 min read

The Chainguard platform supports SCIM 2.0 provisioning from Okta. With SCIM enabled, Okta creates, updates, and deactivates Chainguard users as you assign and unassign them, and Chainguard links each provisioned user to their single sign-on (SSO) login automatically.

This guide covers the Okta-specific setup. For provisioning behavior, token lifecycle, and limits, refer to Provision Chainguard users with SCIM.

Prerequisites

To complete this guide, you need the following:

The rest of this guide refers to your identity provider by its UIDP, stored in the IDENTITY_PROVIDER environment variable:

export IDENTITY_PROVIDER=$(chainctl iam identity-providers list -o json | jq -r '.items[0].id')

Organizations generally have one identity provider, so the list usually holds a single entry and the .items[0].id filter selects it. If the list holds more than one — because you belong to several organizations, or your organization has several providers — run chainctl iam identity-providers list on its own and set IDENTITY_PROVIDER to the UIDP of the provider you are configuring.

Step 1: Enable provisioning and generate the SCIM token

First, generate the bearer token Okta authenticates with:

chainctl iam identity-providers scim token generate $IDENTITY_PROVIDER

The token generate command prints the token to standard output and the SCIM endpoint URL to standard error. Chainguard shows the token exactly once, so keep both values for Step 2. Our guide Provision Chainguard users with SCIM outlines lifetime options and rotation.

Next, enable SCIM for your identity provider:

chainctl iam identity-providers scim enable $IDENTITY_PROVIDER

Generate the token first: scim enable fails with etag: etag is required if the identity provider has no token issued yet. If it instead fails with a message about owner-tier identities, your organization doesn’t yet have two directly assigned owners; refer to the prerequisites for SCIM provisioning.

Enabling SCIM clears the identity provider’s default role. If you set one with --default-role, the DEFAULT ROLE column in chainctl iam identity-providers list is empty afterward.

Step 2: Create the Okta provisioning app

Okta hosts SCIM provisioning on an app integration, but you cannot add SCIM to the OIDC app integration you use for login. This means you must create a second app integration that exists only to carry provisioning.

In the Okta Admin Console, navigate to the Applications landing page, click Create App Integration, and choose SAML 2.0. Chainguard doesn’t use the SAML assertion, and sign-in stays on your existing OIDC app, so the wizard’s SAML settings need only well-formed URLs. Enter placeholder values like https://example.com/sso for Single sign-on URL and https://example.com/saml for Audience URI (SP Entity ID), and leave the other settings at their defaults. On the new app’s General tab, under App Settings, set Provisioning to SCIM.

Then open the app’s Provisioning tab, select Integration, and click Edit to configure the SCIM connection:

  • SCIM connector base URL: the endpoint printed in Step 1.
  • Unique identifier field for users: userName.
  • Supported provisioning actions: select Push New Users, Push Profile Updates, and Push Groups. You need Push Groups only if you plan to grant roles from Okta groups, as described in Step 5.
  • Authentication Mode: select HTTP Header, then paste the bare cgscim_... token into the Bearer field. Do not add a Bearer prefix — Okta supplies the scheme itself, and a prefixed value fails authentication.

Use Test API Credentials to confirm the URL and token, then save.

Error authenticating: No results for users returned

Okta’s connection test reads the SCIM endpoint and expects to find a user it recognizes there. A newly enabled Chainguard endpoint has no users yet, so on a first-time setup the test fails with this message. Despite the wording, authentication succeeded; the missing user is what blocks the save.

A workaround is to create the first user directly, then run the test again. Replace <scim_token> and <scim_endpoint_url> with the bearer token and endpoint URL for your identity provider, and <okta_username> and <okta_user_id> with the details of a real user you intend to provision.

curl -X POST \
  -H "Authorization: Bearer <scim_token>" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "<okta_username>",
    "externalId": "<okta_user_id>",
    "active": true
  }' \
  "<scim_endpoint_url>/Users"

Set externalId to the user’s Okta ID — the 00u... value in the page URL when you open that user under Directory > People. Okta’s test rejects an arbitrary value even though the endpoint returns the user, so this is not a field you can fill with a placeholder. externalId is also what Chainguard matches against at login, and a wrong value leaves the user unlinked with no error, so seed a real user you intend to provision.

Step 3: Enable provisioning actions

With the integration saved, select To App in the Provisioning tab, click Edit, and enable Create Users, Update User Attributes, and Deactivate Users. These options appear only after you save the connection, and only for the actions you checked under Supported provisioning actions in Step 2.

Step 4: Assign users and verify

In Okta, assign a user to the provisioning app integration you created in Step 2. Okta provisions them immediately. Have that user log in to Chainguard through Okta and confirm they have access. A provisioned user connects to their login by matching the SCIM externalId against the login token’s subject; with Okta, both default to the Okta user ID, so they align without configuration.

To confirm the rest of the lifecycle, deactivate that user in Okta or remove their assignment, and confirm that Chainguard refuses their next login. Then reactivate them and confirm that they can log in again.

Step 5: Push groups to grant roles

Okta can also push groups to Chainguard. Chainguard matches each pushed group against your group-to-role mappings and grants the mapped roles to the group’s members as role bindings. These bindings persist between logins, unlike the session-scoped roles that claim-based group mappings grant, and they appear in chainctl iam role-bindings list.

You manage these bindings from Okta. To change who holds a role, change the group’s membership in Okta; Chainguard rejects manual edits to a binding that SCIM manages.

Map the group to a role

Store your organization’s UIDP in the ORGANIZATION variable:

export ORGANIZATION=$(chainctl iam organizations list -o json | jq -r '.items[0].id')

This command stores the first organization in the list. If you belong to more than one organization, run echo $ORGANIZATION and confirm that it’s the organization you’re configuring.

Then map the Okta group to a Chainguard role. Replace <okta_group_name> with the group’s name exactly as it appears in Okta, and <role_name> with the role to grant, such as viewer or editor:

chainctl iam external-group-role-mappings create \
  --external-group-id "<okta_group_name>" \
  --role <role_name> \
  --scope $ORGANIZATION \
  --idp $IDENTITY_PROVIDER

Note: Okta doesn’t send a stable identifier for groups, so Chainguard matches a pushed group by its name. If you rename the group in Okta, the mapping stops matching, and Chainguard reports no error. After a rename, create a mapping for the new name.

Push the group from Okta

To push the group from Okta, follow these steps:

  1. If you set up the provisioning app before group push was available, open its Provisioning tab, select Integration, click Edit, and select Push Groups under Supported provisioning actions.
  2. On the provisioning app’s Assignments tab, click Assign > Assign to Groups, and assign the group. Okta pushes group membership only for users assigned to the app, and assigning the group also provisions its members as Chainguard users.
  3. On the Push Groups tab, click Push Groups > Find groups by name, and enter the group’s name.
  4. Leave Push group memberships immediately selected, and click Save.

The group’s Push Status changes to Active once Okta has pushed it.

Verify the role bindings

Chainguard creates a member’s role binding only after that member has logged in to Chainguard at least once. After that first login, membership changes in Okta take effect without another login.

To confirm that Okta provisioned the group’s members, list the users it has pushed. Members who haven’t logged in yet show no in the LOGGED IN column:

chainctl iam identity-providers scim users list $IDENTITY_PROVIDER

Have a member of the group log in to Chainguard through Okta, then list the role bindings in your organization:

chainctl iam role-bindings list --parent $ORGANIZATION

The output includes a binding with the mapped role and SCIM in the MANAGED BY column. The binding’s NAME / EMAIL column is empty, so identify the member by their identity ID instead. To look up that ID, replace <okta_username> with the member’s Okta username:

chainctl iam identity-providers scim users list $IDENTITY_PROVIDER -o json \
  | jq -r '.[] | select(.user_name == "<okta_username>") | .identity_id'

The binding’s IDENTITY column matches this value. If the command prints null, the member hasn’t logged in to Chainguard yet.

To confirm that removal works, remove the member from the group in Okta, then list the role bindings again. Chainguard deletes the binding shortly after Okta sends the change.

If you assigned the member to the provisioning app only through the group, removing them from the group also deactivates their Chainguard user. Okta doesn’t remove a deactivated user from the group it pushed, so the user can get the group’s role back if they’re reactivated. If that happens, open the Push Groups tab and select Push now from the group’s Push Status menu. Okta then sends the group’s current membership, and Chainguard removes the role.

Troubleshooting

A user logged in but wasn’t linked

A provisioning mismatch does not produce an error; the login proceeds as a normal, unlinked user. Confirm that you assigned the user to the provisioning app and that Okta provisioned them (check the app’s assignment and Okta’s provisioning logs), and that the app sends the default Okta user ID as externalId.

Test API Credentials fails

If the message is “No results for users returned,” refer to Error authenticating: No results for users returned in Step 2. Otherwise, confirm the base URL is the endpoint printed by token generate and that the Bearer field holds the correct cgscim_... token. The URL and token must come from the same token generate run.

Provisioning requests receive HTTP 429

Chainguard rate limits provisioning writes; Okta retries on its own schedule, so bulk assignments complete without intervention. For more information, refer to the Limits section.

Last updated: 2026-10-15 00:00