Skip to main content
Private preview. fremforge is in private preview — invited customers only. Content is still subject to change. Request access →

SCIM 2.0 user provisioning

fremforge implements SCIM 2.0 (RFC 7643/7644) so your IdP can provision Forgejo users into your org automatically: create on hire, update on attribute change, deactivate on termination. No more “John just left, did anyone remember to revoke their fremforge access?” Your IdP’s deactivation flips the SCIM active flag and fremforge deactivates the Forgejo user the same minute.

SCIM pairs with OIDC or SAML; it doesn’t replace either. SCIM is the provisioning protocol. It pushes user records into fremforge. Users still log in via your separately-configured login flow (OIDC, recommended or SAML). Most small teams don’t need SCIM at all. Manual user management via Forgejo’s UI is fine. Set up SCIM only when “we just hired five people, did anyone remember to add them to fremforge?” becomes a real problem.

This page is the field guide for connecting Okta / Entra / Authentik / any SCIM 2.0 IdP. Plug-and-play for the major IdPs; works with any SCIM 2.0 client.

Capabilities

CapabilitySupported
POST /Users (provision)
GET /Users/:id (lookup)
GET /Users (list, with userName eq filter)
PATCH /Users/:id (RFC 7644 §3.5.2 ops)
PUT /Users/:id (full replace)
DELETE /Users/:id (deactivate, not hard-delete)
GET /ServiceProviderConfig
GET /ResourceTypes, GET /Schemas (discovery)
POST /Groups / GET /Groups / GET /Groups/:id
PATCH /Groups/:id (add/remove members)
PUT /Groups/:id (full replace)
DELETE /Groups/:id (delete Forgejo team + soft-delete mapping)
Group filter displayName eq
Bulk endpoints✗ Not yet
Filter operators beyond eq✗ Use multiple requests
Nested groups✗ Forgejo doesn’t model them

Setup

Step 1, enable SCIM in fremforge

  1. Org admin → SSO → SCIM 2.0 user provisioning → click Enable SCIM & mint token.
  2. The dashboard surfaces the bearer token once. Copy it; we’ll need it for your IdP. fremforge does NOT show the token again after this redirect (only its SHA-256 hash is stored). If you lose it, click Rotate token to mint a new one.
  3. Note the Base URL shown on the page, it looks like:
    https://frem.sh/<your-org>/scim/v2

Step 2, connect your IdP

Okta

  1. Applications → Create App Integration → SCIM 2.0 Test App (Header Auth).
  2. Provisioning tab → Configure API Integration:
    • Base URL: https://frem.sh/<your-org>/scim/v2
    • Authentication Mode: HTTP Header
    • HTTP Header Name: Authorization
    • HTTP Header Value: Bearer <token from step 1>
  3. Test API Credentials, should return success.
  4. To App tab → enable Create, Update, Deactivate.
  5. Assign users to the app. Okta will POST them to fremforge.

Microsoft Entra (Azure AD)

  1. Enterprise Applications → New application → Non-gallery → name it “fremforge”.
  2. ProvisioningGet started → mode = Automatic:
    • Tenant URL: https://frem.sh/<your-org>/scim/v2
    • Secret Token: <token from step 1> (paste with no Bearer prefix; Entra adds it)
  3. Test Connection → green check.
  4. Mappings → use the default User mapping; the only field that often needs adjustment is userName, set it to userPrincipalName (the user’s email-shaped login).
  5. Save & Start provisioning.

Authentik

  1. Applications → Providers → Create → SCIM Provider:
    • URL: https://frem.sh/<your-org>/scim/v2
    • Token: <token from step 1>
  2. Bind the provider to your Application; assign a group; users in that group will be provisioned.

Generic SCIM 2.0 IdP

Any client that supports the standard Authorization: Bearer <token> shape against a SCIM 2.0 base URL works. fremforge advertises capabilities at:

GET /<your-org>/scim/v2/ServiceProviderConfig
  • inspect that response to verify what’s supported before integrating.

What happens during provisioning

When your IdP POSTs a user:

  1. fremforge creates a Forgejo user (username derived from the SCIM userName; email-shaped names are sanitized to Forgejo’s [a-z0-9-] rules, e.g. alice@acme.comalice-acme-com).
  2. The user is added to the org’s default Members team. They are NOT promoted to Owner. Admin promotion stays operator-side.
  3. The user signs in via your existing OIDC or SAML auth source (set up separately on the SSO page). SCIM creates the user record; the IdP-side login flow logs them in.
  4. fremforge logs an audit_events row with action scim.user.create. Visible in the Audit log admin tab.

When your IdP deactivates a user (PATCH active=false or DELETE):

  1. fremforge sets prohibit_login=true on the Forgejo user. Sessions are revoked and future logins refused.
  2. The user’s SSH keys remain in Forgejo (they can’t be used to push because the user is locked).
  3. The org membership is kept for audit trail. To hard-remove, an org admin clicks Remove from org in the Forgejo UI.

Group provisioning

When your IdP creates a SCIM Group, fremforge:

  1. Creates a Forgejo team inside your tenant org. Team name is derived from the group’s displayName (sanitised to Forgejo’s [a-z0-9_-] rules, 30 chars max).
  2. Records the mapping in scim_groups so future PATCH/PUT/DELETE operations resolve back to the same Forgejo team.
  3. Permits members PATCH ops with op=add / op=remove (RFC 7644 §3.5.2 + members[value eq "<id>"] short-form).
  4. DELETE /Groups/:id deletes the underlying Forgejo team. The DB row is soft-deleted (deleted_at) so the externalId↔team mapping survives re-creation of the same IdP group.

Each Forgejo team grants its members write access to all repos in the org by default (so a “Backend” group ends up with write access to every repo). Finer-grained per-repo access is configured operator-side in the Forgejo team settings, fremforge SCIM does not push repo-scoped permissions, since SCIM 2.0 has no standard for repo-permission attributes.

Limitations at launch:

  • Email is the user-key. SCIM identifies users by externalId, which we expect to match the user’s primary email. This aligns with our existing OIDC username_claim setting.
  • No SAML-only Just-in-Time provisioning. If you’re a SAML-only customer, set up SCIM as a second connector for the lifecycle side, SAML handles login, SCIM handles user lifecycle.
  • No nested groups. Forgejo’s team model is flat (a user is either in a team or not). Nested-group hierarchies from your IdP are flattened: a user in Engineering → Backend → API becomes a member of all three corresponding Forgejo teams.

Token rotation

SCIM bearer tokens should rotate periodically. fremforge supports zero-downtime rotation with a dual-token window:

  1. Org admin → SSO → SCIM → click Rotate token.
  2. New token shown once; copy it.
  3. The previous token stays valid until you click Drop previous token. This window lets your IdP swap to the new token without dropping any in-flight provisioning calls.
  4. Update your IdP with the new token.
  5. After confirming the new token works (check the IdP-side provisioning health for a few cycles), click Drop previous token.

Recommended cadence: every 90 days, or on personnel changes that warrant credential rotation.

Disabling SCIM

Org admin → SSO → SCIM → click Disable SCIM. fremforge:

  • Clears both current and previous token hashes (any pending IdP request gets 401)
  • Marks the row enabled=false (audit-visible)
  • Leaves scim_users rows intact (re-enabling re-provisions from your IdP)

The Forgejo users themselves are NOT deactivated. If you want to lock them all out, do that from your IdP first, then disable SCIM.

Troubleshooting

“Authorization: Bearer required” (401) on every request

The bearer token isn’t reaching fremforge. Common causes:

  • Wrong header name, must be exactly Authorization.
  • Missing Bearer prefix, Okta adds it automatically; some clients don’t. Check what your IdP actually sends with a tcpdump or the IdP’s verbose logs.
  • Token revoked, if you’ve rotated and dropped the previous, the old token is dead. Re-paste the current.

“userName is required” (400) on POST

Your IdP isn’t mapping userName to a meaningful value. In Entra this often means the user’s UPN isn’t set; map userName to mail or a similar non-empty attribute.

Users provisioned but can’t log in

SCIM only creates the user record. The login flow uses your separately-configured OIDC or SAML auth source. Verify:

  1. The user has an OIDC / SAML auth source set up at org admin → SSO → Auth sources.
  2. The user’s IdP-side identity (email, principal name) matches the userName you SCIM-provisioned.
  3. The IdP login screen at frem.sh/user/login?redirect_to=/<your-org> shows the SSO button.

Filter doesn’t work for email eq or other operators

We support only userName eq "<value>". Anything else returns an empty list. Most IdPs only need userName eq for de-duplication; verify in their provisioning logs which filter shapes they emit and let us know if you need additional operators (file at support@frem.sh).

Audit trail

Every SCIM-driven action is recorded in the org’s audit log with actor=system (since the actor is “your IdP,” not a logged-in user) and one of:

  • scim.enabled / scim.disabled, operator UI actions
  • scim.token.rotated / scim.token.previous-dropped, operator UI actions
  • scim.user.create, IdP POST
  • scim.user.update / scim.user.replace, IdP PATCH/PUT
  • scim.user.deactivate / scim.user.reactivate, IdP active-flag change
  • scim.group.create / scim.group.update / scim.group.replace / scim.group.delete, IdP group lifecycle
  • the same actions land in the data-export bundle’s audit slice

Cross-references