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
| Capability | Supported |
|---|---|
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
- Org admin → SSO → SCIM 2.0 user provisioning → click Enable SCIM & mint token.
- 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.
- Note the Base URL shown on the page, it looks like:
https://frem.sh/<your-org>/scim/v2
Step 2, connect your IdP
Okta
- Applications → Create App Integration → SCIM 2.0 Test App (Header Auth).
- 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>
- Base URL:
- Test API Credentials, should return success.
- To App tab → enable Create, Update, Deactivate.
- Assign users to the app. Okta will POST them to fremforge.
Microsoft Entra (Azure AD)
- Enterprise Applications → New application → Non-gallery → name it “fremforge”.
- Provisioning → Get started → mode = Automatic:
- Tenant URL:
https://frem.sh/<your-org>/scim/v2 - Secret Token:
<token from step 1>(paste with noBearerprefix; Entra adds it)
- Tenant URL:
- Test Connection → green check.
- Mappings → use the default User mapping; the only field that often needs adjustment is
userName, set it touserPrincipalName(the user’s email-shaped login). - Save & Start provisioning.
Authentik
- Applications → Providers → Create → SCIM Provider:
- URL:
https://frem.sh/<your-org>/scim/v2 - Token:
<token from step 1>
- URL:
- 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:
- 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.com→alice-acme-com). - The user is added to the org’s default
Membersteam. They are NOT promoted to Owner. Admin promotion stays operator-side. - 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.
- fremforge logs an
audit_eventsrow with actionscim.user.create. Visible in the Audit log admin tab.
When your IdP deactivates a user (PATCH active=false or DELETE):
- fremforge sets
prohibit_login=trueon the Forgejo user. Sessions are revoked and future logins refused. - The user’s SSH keys remain in Forgejo (they can’t be used to push because the user is locked).
- 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:
- 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). - Records the mapping in
scim_groupsso future PATCH/PUT/DELETE operations resolve back to the same Forgejo team. - Permits
membersPATCH ops withop=add/op=remove(RFC 7644 §3.5.2 +members[value eq "<id>"]short-form). DELETE /Groups/:iddeletes 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 OIDCusername_claimsetting. - 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 → APIbecomes 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:
- Org admin → SSO → SCIM → click Rotate token.
- New token shown once; copy it.
- 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.
- Update your IdP with the new token.
- 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_usersrows 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
Bearerprefix, 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:
- The user has an OIDC / SAML auth source set up at org admin → SSO → Auth sources.
- The user’s IdP-side identity (email, principal name) matches the
userNameyou SCIM-provisioned. - 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 actionsscim.token.rotated/scim.token.previous-dropped, operator UI actionsscim.user.create, IdP POSTscim.user.update/scim.user.replace, IdP PATCH/PUTscim.user.deactivate/scim.user.reactivate, IdP active-flag changescim.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
- Org admin → SSO, where you enable + manage SCIM
- OIDC SSO and SAML 2.0 SSO, IdP-side login flows that pair with SCIM provisioning
- Audit log, see every SCIM event on the same UI
- Public REST API, programmatic equivalent if you build your own connector