SCIM provisioning
CTFreak can automatically provision users and teams from your identity provider (Okta, Microsoft Entra ID, Authentik, …) through the SCIM 2.0 protocol (System for Cross-domain Identity Management).
Note: A BUSINESS Edition license or higher is required to enable SCIM provisioning.
How it works
SCIM provisioning is enabled on an OIDC auth provider at creation time.
CTFreak exposes a SCIM 2.0 endpoint at
{External URL}/scim/v2, protected by a dedicated bearer token generated when the auth provider is created (shown once, can be regenerated later).Your identity provider pushes users, groups (which become CTFreak teams) and group memberships to this endpoint: creations, profile updates, deactivations and deletions are propagated automatically.
Users and teams provisioned through SCIM are flagged as managed by SCIM (as opposed to Internal in the Managed by column): their lifecycle is owned by the identity provider and they are read-only in CTFreak. Internal users and teams remain fully editable.
At login time, the OIDC token claim selected as External ID matching claim is matched against the
externalIdpushed by the identity provider, so that the authenticated user is correlated with the provisioned account.
Enabling SCIM provisioning
Make sure the External URL of your instance is set (Settings → Global): it is used to build the SCIM base URL, and most identity providers require it to be reachable over HTTPS with a valid certificate.
Log in to the UI as an administrator and go to Settings → Authentication → New auth provider.
Fill in the OIDC configuration (client ID, client secret, discovery endpoint) as described in Auth providers.
Set the External ID matching claim (see below).
Check SCIM provisioning → Enabled, then click Create.
A pop-up reveals the SCIM bearer token. Copy it immediately: it will never be shown again.
Register your CTFreak instance in your identity provider with:
- SCIM base URL:
{External URL}/scim/v2 - Bearer token: the token copied at the previous step
- SCIM base URL:
Important: SCIM provisioning can only be enabled when creating the auth provider, and cannot be disabled afterwards.
The External ID matching claim
This setting selects which OIDC token claim is compared, at login time, with the externalId pushed by the identity provider through SCIM. It is frozen at creation time: choosing the wrong claim requires recreating the auth provider (which deletes its provisioned users and teams).
Keep
subunless your identity provider cannot expose it as the SCIM external ID.Microsoft Entra ID requires
oid: itssubclaim is unique per application and never matches the provisioned external ID.Okta works with
sub, provided the org authorization server is used: a custom authorization server (/oauth2/default) issues the user login assub, which is neither the provisioned external ID nor immutable.
Important: Choose an immutable claim. A claim such as
preferred_usernamecan be reassigned to another person, who would then inherit the account and its permissions.
SCIM bearer token lifecycle
The token is revealed once, when the auth provider is created. If it is lost or compromised:
- Go to Settings → Authentication → {your auth provider} → Edit
- Check Regenerate SCIM bearer token and save
A new token is revealed (once again, only once) and the previous one is immediately revoked: remember to update it in your identity provider.
Lifecycle of provisioned users and teams
Read-only in CTFreak: provisioned users cannot be renamed, disabled or deleted from the CTFreak UI, and provisioned teams cannot be edited or deleted. Only the identity provider drives their lifecycle.
The administrator flag stays under CTFreak’s control: granting or revoking CTFreak administrator rights on a provisioned user is still done in CTFreak, SCIM never drives it (this may change in a future update). The same goes for roles and project access rights: assign roles to the provisioned teams, and newly provisioned users get their access automatically through their team memberships, without any per-user action in CTFreak.
Team membership rules: a team provisioned through SCIM only accepts users provisioned by the same identity provider. The opposite is allowed: a provisioned user can be added to an internal team, since internal teams are an authorization construct specific to CTFreak.
Deactivation instead of deletion: most identity providers deprovision a user by pushing
active: false. The account is then marked as disabled in CTFreak (login refused), but is not deleted and keeps its team memberships, so a reactivation restores it as it was.Login requires provisioning: with a SCIM-enabled auth provider, user accounts are no longer created on first login. A user that has not been provisioned yet is refused with the message “This user account has not been provisioned yet, or the authProvider user matching claim does not match the provisioned externalId”.
Deleting the auth provider also deletes all the users and teams it provisioned.
License expiry: if your license expires, the SCIM-enabled auth provider can no longer be edited, but provisioning keeps running so that your identity provider does not silently get out of sync.
Setting up popular identity providers
The overall sequence is the same for every identity provider: configure the OIDC application on the identity provider side, create the auth provider and get the SCIM bearer token on the CTFreak side, then plug the SCIM provisioning back on the identity provider side with that token.
Authentik
Authentik supports OIDC authentication and SCIM provisioning on a single application: the OIDC provider is the application’s regular provider, and the SCIM provider is attached as a backchannel provider.
On the Authentik side:
OIDC provider: Applications → Providers → New Provider → OAuth2/OpenID Provider
- Client Type: Confidential — note the generated Client ID and Client Secret
- Redirect URIs/Origins (RegEx): Add entry, mode Strict, value
{External URL}/oidc/callback - Subject Mode (under Advanced protocol settings): Based on the User’s UUID, an immutable identifier, so that the
subclaim matches the external ID pushed below
SCIM external ID mapping: Customization → Property Mappings → New Property Mapping → SCIM Provider Mapping (not SCIM Source Property Mapping, which is the inbound one), with the expression:
return {"externalId": str(request.user.uuid)}
This mapping is only needed because the subject mode was changed. Left on its default, Based on the User’s hashed ID, Authentik emits that same value as
externalIdand no custom mapping is required. The UUID is preferred here because the hashed ID is derived from Authentik’sAUTHENTIK_SECRET_KEY: rotating that key would orphan every provisioned account.
- Application: Applications → Applications → New Application → with Existing Provider…
- Provider: the OIDC provider
- Note the Slug
- In the application’s Policy / Group / User Bindings tab, bind the groups allowed to access CTFreak: only users granted access to the application are provisioned
On the CTFreak side:
Create the auth provider with:
- Client ID and Client Secret: the ones generated on the OAuth2/OpenID provider
- Discovery endpoint:
https://{authentikHostName}/application/o/{applicationSlug}/.well-known/openid-configuration, where{applicationSlug}is the Slug of the application created above (the Authentik preset next to the field fills this template in for you) - External ID matching claim:
sub - SCIM provisioning: enabled
Copy the revealed SCIM bearer token
Back on the Authentik side:
SCIM provider: Applications → Providers → New Provider → SCIM Provider
- URL:
{External URL}/scim/v2 - Authentication Mode: Token, then Token: the SCIM bearer token revealed by CTFreak
- Filtering → Exclude service accounts: enabled
- Filtering → Group Filter: select the groups to provision. Left empty, Authentik pushes every group it holds, including its own
authentik Adminsandauthentik Read-only, which then show up as CTFreak teams. The application binding of step 3 filters users only, never groups. - Attribute mapping → User Property Mappings: keep the default user mapping and add the custom external ID mapping created above
- Attribute mapping → Group Property Mappings: keep the default group mapping
- URL:
Application: edit the application created above and add the SCIM provider to its Backchannel Providers
Push the existing users and groups: on the SCIM provider page, Schedules → Full sync for SCIM provider. → Run scheduled task now (or wait for the next scheduled sync).
Okta
To this day, Okta does not support SCIM provisioning on an OIDC application. The setup documented by Okta support uses two applications: one OIDC application for authentication, and one SAML application (whose SSO is never used) for SCIM provisioning. Access is entirely governed by the assignments of the SCIM application, since CTFreak refuses any user it has not provisioned: the OIDC application can therefore be assigned to Everyone, which spares you a second list to keep in sync.
Okta pushes the internal Okta user ID as the SCIM externalId, and the org authorization server issues that same value as the sub claim of the ID token. No custom claim is needed, provided the discovery endpoint used below is the org one and not a custom authorization server such as /oauth2/default.
On the Okta side:
- OIDC application: Applications → Create App Integration → OIDC - OpenID Connect → Web Application
- Sign-in redirect URI:
{External URL}/oidc/callback - Assignments (at the bottom of the creation form): select Allow everyone in your organization to access (see above)
- Note the Client ID and Client secret
- Sign-in redirect URI:
On the CTFreak side:
Create the auth provider with:
- Client ID and Client Secret: the ones noted on the OIDC application
- Discovery endpoint:
https://{oktaHostName}/.well-known/openid-configuration, where{oktaHostName}is your Okta org domain (e.g.dev-12345678.okta.com) - External ID matching claim:
sub - SCIM provisioning: enabled
Copy the revealed SCIM bearer token
Back on the Okta side:
SCIM application: Applications → Create App Integration → SAML 2.0. Its SSO is never used, so check Do not display application icon to users on the first screen: every provisioned user is assigned to this application, and its tile would only lead them to a broken sign-in. Fill the SAML screen that follows with any syntactically valid values, then go to General → App Settings → Edit → Provisioning → SCIM. If the Provisioning option does not appear, the provisioning feature is not enabled on your Okta org: contact Okta support.
SCIM connector: Provisioning → Integration → Edit
- SCIM connector base URL:
{External URL}/scim/v2 - Unique identifier field for users:
userName - Supported provisioning actions: Push New Users, Push Profile Updates, Push Groups
- Authentication Mode: HTTP Header, with the SCIM bearer token revealed by CTFreak
- Then in Provisioning → To App: enable Create Users, Update User Attributes, Deactivate Users
- SCIM connector base URL:
Assignments: assign users to the SCIM application, then push groups from its Push Groups tab (assigning a group provisions its members, but only Push Groups creates the group itself).
Microsoft Entra ID
Important: automatic provisioning requires an Entra ID P1 license or higher, and the enterprise application must be created as non-gallery (an application first created from App registrations may only offer manual provisioning).
On the Entra ID side:
Enterprise application: Microsoft Entra admin center → Entra ID → Enterprise apps → + New application → + Create your own application, name it, then check “Integrate any other application you don’t find in the gallery (Non-gallery)” and click Create.
OIDC authentication: Entra ID → App registrations → All applications → your application:
- Authentication → Redirect URI configuration → Add Redirect URI → Web: Redirect URI =
{External URL}/oidc/callback, then Configure - Certificates & secrets → New client secret: note the Value
- Overview: note the Application (client) ID and Directory (tenant) ID
- API permissions: make sure the delegated
openidandprofilepermissions are present (theoidclaim is only emitted withprofile)
- Authentication → Redirect URI configuration → Add Redirect URI → Web: Redirect URI =
On the CTFreak side:
Create the auth provider with:
- Client ID: the Application (client) ID noted on the app registration
- Client Secret: the client secret Value noted on the app registration
- Discovery endpoint:
https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration, where{tenant}is the Directory (tenant) ID noted on the app registration - External ID matching claim:
oid(Entra’ssubclaim is unique per application and can never match the provisioned external ID) - SCIM provisioning: enabled
Copy the revealed SCIM bearer token
Back on the Entra ID side:
Provisioning: Entra ID → Enterprise apps → your application → Provisioning → + New configuration
- Authentication Method: Bearer Authentication, when the field is offered
- Tenant URL:
{External URL}/scim/v2(the{External URL}/scim/v2?aadOptscim062020compliance variant is also supported) - Secret Token: the SCIM bearer token revealed by CTFreak
- Click Test Connection, then Save (Create in the new experience)
Attribute mappings: Provisioning → Attribute mapping
- Users: change the source of
externalIdfrommailNicknametoobjectId. Without this change, the pushed external ID never matches theoidclaim and no OIDC login will succeed. KeepuserName←userPrincipalNameas matching attribute, and make suredisplayNameandactiveare mapped. - Groups:
displayName←displayName,externalId←objectId
- Users: change the source of
Scope: Provisioning → Scoping filters
- Set Scope to Sync only assigned users and groups
Assign users and groups in Provisioning → Users and groups, then start provisioning by switching Provisioning Status to On (Overview → Start provisioning in the new experience). Entra runs a cycle roughly every 40 minutes; use Provision on demand to push a given user or group immediately and inspect the requests.
Note: Entra ID deprovisions by pushing
active: falsewhen an assignment is removed; the actual account deletion is only sent after the user has spent ~30 days in the Entra recycle bin. On repeated errors, Entra puts provisioning in quarantine: fix the cause, then use Restart provisioning.
Troubleshooting
By far the most frequent issue is a mismatch between the user identifiers used on each side: the identity provider pushes one value as the SCIM externalId, but issues another one in the OIDC token claim selected as External ID matching claim. Users are then correctly provisioned, yet every login is refused. Make sure both sides carry the same immutable identifier, as described for each identity provider above.
| Symptom | Probable cause |
|---|---|
The identity provider’s connection test fails with a 401 |
Token truncated (it contains a : separator and must be copied in full), token regenerated but not updated, auth provider disabled, or SCIM not enabled at auth provider creation. CTFreak deliberately returns the same opaque 401 in all these cases |
| The identity provider’s connection test times out | {External URL} not reachable from the identity provider, or invalid TLS certificate |
User provisioning fails with 400 invalidValue |
The pushed payload carries neither userName nor externalId, or an update leaves the user with an empty display name (CTFreak derives it from displayName, name.formatted, givenName + familyName, userName or externalId, in that order) |
| Login refused with “has not been provisioned yet” although the account exists in CTFreak | The matching claim value does not match the pushed externalId. The rejected login is logged with the received claim value, to be compared with the externalId returned by GET /scim/v2/Users. On Entra ID, check the externalId ← objectId mapping; if the configured claim itself is wrong, the auth provider must be recreated |
| Login refused with “has not been provisioned yet” and the account never appears in CTFreak (Okta) | Users assigned to the OIDC application only, so they authenticate successfully on Okta’s side but are never pushed: provisioning is triggered by assignments on the SCIM application |
| Login refused with “The authProvider did not return the configured user matching claim” | The claim is emitted neither in the ID token nor in the userinfo response. On Entra ID, check the profile permission and the v2.0 discovery endpoint. The server logs list the names of the claims actually received on both sides |