Skip to content

Okta setup for Floh

Step-by-step guide for using an Okta Developer Edition org with Floh: OIDC sign-in for the admin UI and inbound SCIM 2.0 provisioning from Okta into Floh.

Integration Direction Floh surface Okta surface
OIDC login Okta authenticates users /api/auth/* OIDC Web Application
Inbound SCIM Okta pushes users and groups /scim/v2 App with SCIM 2.0 provisioning
Outbound (Floh → Okta) Floh pushes users to Okta okta connector Okta Management API + API token

These are two separate app integrations in Okta. OIDC handles who can log in and which Floh RBAC roles they receive from Okta groups. SCIM creates and updates Floh user rows and user_group membership; it does not assign Floh system roles (admin, approver, etc.).

For Floh behavior and API details, see Inbound SCIM. To push users from Floh to Okta (create / update / deactivate), see Okta outbound user lifecycle and the okta connector (not the generic scim connector — Okta does not host /scim/v2 on the org URL).


1. Create an Okta Developer account

  1. Sign up at developer.okta.com (free Developer Edition org).
  2. Note your org URL: https://<org-name>.okta.com (also shown on the Admin Console home page).
  3. Sign in to the Admin Console (https://<org-name>-admin.okta.com or Admin from the developer dashboard).

You will use this org for both OIDC and SCIM. Production customer orgs follow the same steps with stricter change control.


2. OIDC application (Floh login)

Floh requires OIDC at startup (OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI). See also Configuring OIDC in the developer guide.

2.1 Create the OIDC app

  1. ApplicationsApplicationsCreate App Integration.
  2. Sign-in method: OIDC - OpenID Connect.
  3. Application type: Web Application.
  4. Name: e.g. Floh (local dev).
  5. Grant types: enable Authorization Code (and Refresh Token if you use long-lived API access from the same client).
  6. Sign-in redirect URIs (must match Floh exactly):
  7. HTTPS dev: https://localhost:7070/api/auth/callback
  8. HTTP-only dev: http://localhost:7070/api/auth/callback
  9. Sign-out redirect URIs (optional): your Floh web origin, e.g. https://localhost:7072.
  10. Controlled access: for dev, Allow everyone in your organization to access is simplest.
  11. Save and open the app’s General tab. Copy Client ID and Client secret.

2.2 Authorization Server (issuer)

Floh’s default Okta issuer is the org’s default custom authorization server:

https://<org-name>.okta.com/oauth2/default

Confirm via SecurityAPIAuthorization ServersdefaultMetadata URI (issuer).

2.3 Groups claim for Floh roles

Floh maps Okta groups to Floh roles using OIDC_ROLE_* env vars and expects a groups claim (see OIDC group-to-role mapping).

  1. DirectoryGroups → create groups, e.g. floh-admins, floh-requestors.
  2. Assign your test user to the appropriate groups.
  3. Ensure the access token / userinfo includes groups:
  4. SecurityAPIAuthorization ServersdefaultClaims → add or verify a groups claim (often groups with value type Groups).
  5. Under your OIDC app → Sign OnOpenID Connect ID Token → edit Groups claim filter if you need to limit which groups appear in tokens.

2.4 Floh .env (OIDC)

OIDC_ISSUER=https://<org-name>.okta.com/oauth2/default
OIDC_CLIENT_ID=<client-id-from-okta>
OIDC_CLIENT_SECRET=<client-secret-from-okta>
OIDC_REDIRECT_URI=https://localhost:7070/api/auth/callback
OIDC_SCOPE=openid profile email groups
OIDC_ROLE_ADMIN=floh-admins
OIDC_ROLE_APPROVER=floh-approvers
OIDC_ROLE_RESOURCE_MANAGER=floh-resource-managers
OIDC_ROLE_REQUESTOR=floh-requestors

Restart the Floh server after changing OIDC settings. Log in via the web UI; on first login Floh upserts the user and syncs roles from Okta groups.


3. SCIM bearer token on Floh (inbound)

Inbound SCIM calls Floh with:

Authorization: Bearer <scim-token>

Tokens are stored hashed in scim_client; Floh never stores the raw token after creation. There is not yet a first-class admin API in the UI for issuing SCIM tokens—create a row in the database for dev/test.

3.1 Generate a token and insert a client

From the repo root (Postgres running, migrations applied):

pnpm --filter @floh/server exec node --input-type=module -e "
import { createHash, randomBytes, randomUUID } from 'node:crypto';

const rawToken = 'scim_' + randomBytes(32).toString('hex');
const tokenHash = createHash('sha256').update(rawToken).digest('hex');
const tokenPrefix = rawToken.slice(0, 16);
const id = randomUUID();

console.log('Save this bearer token now (shown once):');
console.log(rawToken);
console.log();
console.log('Run in psql:');
console.log(\`INSERT INTO scim_client (id, name, token_prefix, token_hash, status)
VALUES ('\${id}', 'Okta SCIM', '\${tokenPrefix}', '\${tokenHash}', 'active');\`);
"

Apply the SQL (defaults match local Docker Compose):

docker compose -f docker/docker-compose.yml exec -T postgres \
  psql -U floh -d floh -c "<paste INSERT here>"

Use the printed scim_… value as the Bearer token in Okta (next section). To revoke access, set status = 'inactive' on that scim_client row.

3.2 Verify Floh SCIM is reachable

Base URL (no trailing slash on the host path; Okta appends resource paths):

https://<floh-host>/scim/v2

Local dev example:

https://localhost:7070/scim/v2

Okta’s cloud must reach your Floh host. For local development, expose the API with an HTTPS tunnel (e.g. ngrok, Cloudflare Tunnel) and use that public URL in Okta. Plain localhost only works if Okta runs provisioning from your machine (it does not in a real dev org).

Quick check (replace host and token):

curl -sS "https://<floh-host>/scim/v2/ServiceProviderConfig" \
  -H "Authorization: Bearer <scim-token>" | jq .

Expect 200 with patch.supported: true and bulk.supported: false. 401 means the token does not match any active scim_client row.


4. Okta SCIM application (provision into Floh)

Create a second app integration dedicated to provisioning. It does not replace the OIDC Web app from section 2.

4.1 Create the app

  1. ApplicationsApplicationsCreate App Integration.
  2. Sign-in method: OIDC - OpenID Connect (Okta’s SCIM wizard is tied to an app integration; SSO to Floh via this app is optional if you already use section 2).
  3. Application type: Web Application (or SPA only if you do not need a client secret for anything else—SCIM provisioning uses the bearer token to Floh, not this client secret).
  4. Name: e.g. Floh SCIM Provisioning.
  5. Redirect URIs: not required for SCIM-only testing; you can use a placeholder or the same Floh callback if you enable SSO on this app later.
  6. Save.

Alternatively, some teams use App IntegrationCreate New AppSAML 2.0 or SWA with SCIM enabled; the SCIM tab settings below are what matter, not the SSO protocol.

4.2 Enable SCIM provisioning

  1. Open the new app → Provisioning tab → Configure.
  2. Integration:
  3. SCIM connector or SCIM 2.0 (wording varies by Okta UI version).
  4. SCIM connector base URL: https://<floh-host>/scim/v2
  5. Unique identifier field for users: userName (Floh keys provisioning on email-style userName).
  6. Supported provisioning actions: enable at minimum:
    • Import new users and profile updates (Push New Users / Push Profile Updates)
    • Push Profile Updates
    • Deactivate users
  7. Disable Import password / password sync (Floh does not support SCIM password changes).
  8. Authentication:
  9. Mode: HTTP Header / Bearer token / OAuth Bearer Token (label varies).
  10. Token: paste the raw scim_… token from section 3.1.
  11. Test Connector (or Test API Credentials): should succeed against ServiceProviderConfig or a test GET.

4.3 Provisioning assignments

  1. ProvisioningTo AppEdit → enable:
  2. Create Users
  3. Update User Attributes
  4. Deactivate Users
  5. Assignments → assign users or groups that should be provisioned into Floh.

4.4 Attribute mapping (Users)

Under ProvisioningTo AppUser attribute mappings, align with Floh’s identity policy:

Okta / SCIM attribute Floh usage Notes
userName Primary email / login key Required; use the user’s work email.
emails[type eq "work"].value user.email Should match userName unless you have an approved custom mapping.
displayName Display name Optional but recommended.
externalId scim_user_identity.external_id Okta user id; not copied to user.sub.
active user.active false deactivates; does not hard-delete.

Do not map password fields. Floh ignores SCIM password operations.

4.5 Group push (optional)

After user provisioning works:

  1. ProvisioningTo App → enable Push Groups (or Sync groups).
  2. Push Groups tab → select Okta groups to push.
  3. Floh maps SCIM Groups to user_group and memberships—not to Floh RBAC roles or entitlements (see Groups policy).

Use Okta groups for Floh roles only through the OIDC groups claim (section 2), not through SCIM group push.

4.6 Run provisioning

  • ProvisioningRun provisioning (or wait for the next sync cycle).
  • In Okta, open the user → ProvisioningView Logs for per-request success/failure.

In Floh, provisioned users appear as unconfirmed until they complete an OIDC login with the same email (userName). SCIM does not set iss / sub; first Okta login binds the real identity.


5. End-to-end test checklist

  • [ ] OIDC: sign in to Floh web UI with an Okta user; roles match Okta group membership.
  • [ ] SCIM: assign a new user to the SCIM app in Okta; user row appears in Floh with confirmed = false until OIDC login.
  • [ ] SCIM: change displayName in Okta; Floh user updates on sync.
  • [ ] SCIM: deactivate user in Okta; Floh user.active becomes false; iss / sub unchanged.
  • [ ] SCIM (optional): push a group; Floh user_group and memberships update.
  • [ ] Audit: SCIM writes emit scim.user.* / scim.group.* actions (no bearer token in metadata).

6. Troubleshooting

Symptom Likely cause What to do
Okta Test Connector fails with connection error Floh not reachable from Okta (localhost, firewall) Use an HTTPS tunnel to your dev API; confirm URL is …/scim/v2 without a double /v2/v2.
401 / Invalid SCIM bearer token Wrong token, inactive client, or typo Re-run section 3.1; confirm status = 'active'; use Authorization: Bearer scim_… with no extra quotes.
400 invalidFilter Unsupported filter Floh supports equality filters only (userName eq "…", etc.); see Filters.
User created in Floh but cannot log in SCIM user not confirmed User must sign in via OIDC (section 2) with the same email as userName.
User has no admin permissions SCIM does not grant RBAC Add user to Okta groups that map to OIDC_ROLE_ADMIN (or other roles), not only SCIM-assigned app groups.
409 uniqueness Email or externalId conflict Another SCIM client or user already owns the identity; see integration tests / identity policy.
Okta provisioning log shows 415 or parse errors Content-Type Floh accepts application/scim+json (Okta’s default).
Profile update does not deactivate Wrong PATCH op Floh supports replace on active; unsupported ops return 400 without changing the user.

Logs: check Floh server logs for Unexpected SCIM route failure on 5xx. Do not log bearer tokens.

CSRF: SCIM routes skip CSRF when the request uses Authorization: Bearer … only; Okta does not need Floh CSRF cookies.


7. Production notes

  • Use a stable public Floh URL and TLS for the SCIM base URL.
  • Issue a dedicated scim_client per Okta org (or environment); rotate by creating a new token, updating Okta, then deactivating the old client.
  • Keep OIDC and SCIM apps in sync on email / userName conventions across environments.
  • Document Okta change windows; full reconciliations can update many users and groups at once.