Skip to content

Users & Roles

Every person who accesses oikapi has a user account with an email and password. Users authenticate via session-based login or API keys.

Invitations are the normal way to add someone. The invitee sets their own password, so no administrator ever handles it, and the roles you assign are applied when they register.

Terminal window
# Email an invitation, with roles pre-assigned
oi admin user invite send user@example.com -r member
# Or generate a shareable link — useful for onboarding a group
oi admin user invite link -r member --max-uses 10
oi admin user invite list
oi admin user invite revoke <invite-id>

Invitations expire; pass --expires 48h to change the window. A link with --max-uses 0 is unlimited.

Direct creation exists for the cases invitations do not cover — seeding an instance, scripted provisioning, or a service account that will never check email. It requires you to set an initial password, which is why it is not the default path.

Terminal window
oi admin user create user@example.com -p 'Pass123@' -r member --name 'Jane Doe'

If -p is omitted, the CLI prompts for it interactively.

Users can also self-register through the API when your instance allows it:

POST /api/auth/register
{
"email": "user@example.com",
"password": "Pass123!",
"name": "Jane Doe"
}
Terminal window
oi admin user list
oi admin user get user@example.com

Grant or remove the admin role:

Terminal window
oi admin user role promote user@example.com
oi admin user role demote user@example.com

Deleting a user deactivates their account (soft delete). Their audit history is preserved and the account can be restored.

Terminal window
oi admin user delete user@example.com

Roles define what a user can do. Every user is assigned one or more roles.

RoleDescription
superuserPlatform superuser — full access including system tables, cache management, signing keys
adminDay-to-day administrator — bypasses permission checks for non-system operations
memberBaseline role for authenticated users — grants read access to essential system tables
visitorExternal user with zero base access — sees only what explicit grants allow

Every custom role must belong to an application:

POST /api/apps/system/tables/roles/records
{
"name": "sales_manager",
"display_name": "Sales Manager",
"description": "Can manage all CRM and Sales data",
"application": "crm"
}

Via CLI:

Terminal window
oi admin user role add user@example.com sales_manager
oi admin user role remove user@example.com sales_manager

Via API:

POST /api/users/{user-id}/roles
{
"role_id": "<role-uuid>"
}

Start with a few broad roles (e.g., manager, employee, viewer) and use application-level permissions to grant access. Add table-level overrides only where needed.

oikapi supports enterprise single sign-on so your users authenticate with your existing identity provider (IdP) instead of a local password. Two protocols are supported:

  • OIDC (OpenID Connect) — e.g. Okta, Microsoft Entra ID, Google Workspace, Auth0, Keycloak
  • SAML 2.0 — for IdPs that offer SAML but not OIDC

Both protocols share the same just-in-time (JIT) provisioning path, so users are created and linked identically regardless of which one you configure.

SSO is gated at two levels:

  1. Server config — the operator sets security.sso.enabled: true and supplies a 32-byte state-encryption key. In production the key must come from the environment (OIKAPI_SECURITY_SSO_STATE_ENCRYPTION_KEY), base64-encoded; the server refuses to start SSO with the built-in dev default.
  2. License — SSO login is a licensed feature (FeatureSSO); the login and SAML endpoints are feature-gated.

Once enabled, you configure one or more providers as records in the auth_providers system table.

Create an auth provider record. The key fields:

POST /api/apps/system/tables/auth_providers/records
{
"name": "okta-prod",
"display_name": "Okta",
"provider_type": "oidc",
"enabled": true,
"client_id": "<oidc-client-id>",
"client_secret": "<oidc-client-secret>",
"issuer_url": "https://your-org.okta.com",
"scopes": "openid email profile",
"allowed_domains": "example.com,example.org",
"auto_create_users": true,
"default_roles": "member",
"claim_mapping_email": "email",
"claim_mapping_name": "name",
"icon": "KeyIcon",
"button_color": "#4285F4"
}
FieldPurpose
nameUnique provider slug used in login URLs (e.g. okta-prod)
display_nameLabel shown on the login button
issuer_urlOIDC issuer; discovery (/.well-known/openid-configuration) and JWKS are fetched from here
client_id / client_secretOIDC client credentials from your IdP
scopesSpace-separated; defaults to openid email profile
allowed_domainsOptional comma-separated allowlist of email domains; logins from other domains are rejected
auto_create_usersWhen true, first-time users are provisioned automatically (JIT); when false, only pre-existing accounts may sign in
default_rolesComma-separated roles granted to auto-created users (defaults to member)
claim_mapping_email / claim_mapping_nameID-token claims to read for email/name (default email / name)
claim_mappingsOptional JSON map of extra IdP claims to user metadata fields (e.g. {"groups": "metadata.groups"})
icon / button_colorCosmetics for the login button

Configure the redirect/callback URI in your IdP to:

https://<your-host>/api/auth/sso/<name>/callback

Create the provider with "provider_type": "saml". oikapi acts as the SAML Service Provider (SP). The SP endpoints for a provider named okta-saml are:

EndpointPurpose
GET /api/auth/saml/okta-saml/loginStart SP-initiated login (redirect to IdP)
GET /api/auth/saml/okta-saml/metadataSP metadata document to import into your IdP
POST /api/auth/saml/okta-saml/acsAssertion Consumer Service — where the IdP posts the signed assertion

Register the ACS URL and SP entity ID (the metadata URL) in your IdP, and provide the IdP’s signing certificate (PEM) and SSO URL in the provider record. Assertion signatures are validated fail-closed — an unsigned or improperly signed assertion is rejected.

By default only SP-initiated login is accepted: an assertion that does not correspond to a login request oikapi issued is rejected (defends against login-CSRF). IdP-initiated (unsolicited) login can be opted into per provider only when your IdP requires it.

Consumed assertion IDs are recorded so a captured, still-in-window assertion cannot be replayed to mint a duplicate session. In multi-pod deployments this single-use guard is backed by ValKey so it holds across pods.

EndpointAuthPurpose
GET /api/auth/providersPublicList enabled providers (name, display name, icon, color) for the login page
GET /api/auth/sso/{provider}/loginPublic (rate-limited)Begin OIDC login
GET /api/auth/sso/{provider}/callbackPublicOIDC redirect callback
POST /api/auth/sso/{provider}/backchannel-logoutPublicOIDC back-channel logout
POST /api/auth/test-connectionAuthenticatedValidate an OIDC issuer’s discovery document

Login and SAML endpoints are rate-limited (10 requests/minute per client IP).

On a successful login, oikapi resolves the user in this order:

  1. Existing SSO user — matched by provider + IdP subject/NameID. Disabled accounts (is_active = false) are refused; SSO cannot silently re-enable a user your IdP deactivated.
  2. Existing account, same provider, verified email — an account already belonging to this provider is re-linked by email.
  3. Auto-create — if auto_create_users is enabled and the IdP asserts a verified email, a new account is created with the provider’s default_roles.

To protect against account takeover, oikapi will not auto-link an SSO identity to a pre-existing local or different-provider account matched only by email. Those users must sign in normally and link the provider from account settings. Unverified IdP emails are never auto-linked or auto-provisioned.

oikapi implements SCIM 2.0 so your IdP can provision, update, and deprovision users and groups automatically — no manual account management as people join, move, or leave.

The base URL is:

https://<your-host>/api/scim/v2

Point your IdP at the SCIM base URL and authenticate with a bearer token minted in oikapi. Tokens are created and managed by a superuser (via session or API key — a SCIM bearer token itself cannot mint or list further tokens):

POST /api/scim/v2/Tokens
{
"name": "okta-provisioning",
"expires_in_days": 365
}

The response returns the plaintext token once — store it in your IdP immediately. Tokens always expire: expires_in_days defaults to 365 and is capped at 730 (2 years). Manage tokens with:

GET /api/scim/v2/Tokens
DELETE /api/scim/v2/Tokens/{id}

The bearer path is rate-limited (30 requests/minute per client IP).

Standard SCIM 2.0 User operations are supported:

MethodPathPurpose
GET/api/scim/v2/UsersList/filter users
GET/api/scim/v2/Users/{id}Fetch one user
POST/api/scim/v2/UsersProvision a user
PUT/api/scim/v2/Users/{id}Replace a user
PATCH/api/scim/v2/Users/{id}Partial update (e.g. flip active)
DELETE/api/scim/v2/Users/{id}Deprovision

userName maps to the account email and externalId to the IdP’s stable user ID. Filtering supports userName eq "..." and externalId eq "..." for IdP reconciliation (the filters Okta and Entra use).

When a user is disabled (active: false) or deleted, oikapi immediately revokes access — cached auth profile, live sessions, and API keys are invalidated. Deactivation and deletion are guarded by the last-admin recoverability invariant: a request that would remove the final recoverable admin is rejected with 409 Conflict.

MethodPathPurpose
GET/api/scim/v2/GroupsList/filter groups
GET/api/scim/v2/Groups/{id}Fetch one group with members
POST/api/scim/v2/GroupsCreate a group
PATCH/api/scim/v2/Groups/{id}Add/remove members or rename
DELETE/api/scim/v2/Groups/{id}Delete a group

SCIM groups are provisioned as oikapi teams (tagged as SCIM-managed). PATCH handles membership changes; group filtering supports displayName eq "...". Membership changes invalidate affected users’ cached auth profiles so effective access updates immediately.

All SCIM user and group mutations are recorded as attributable security audit events.