Configuration & Guides

Copy Markdown View Source

Configuration Reference

All options are set under config :ex_scim.

Core

KeyDefaultDescription
:base_url"http://localhost:4000"Base URL for SCIM endpoints. Falls back to SCIM_BASE_URL env var.
:storage_strategyExScim.Storage.EtsStorageModule implementing ExScim.Storage.Adapter
:auth_provider_adapterrequiredModule implementing ExScim.Auth.AuthProvider.Adapter

Resource Mapping

KeyDefaultDescription
:user_resource_mapperExScim.Users.Mapper.DefaultMapperModule implementing ExScim.Users.Mapper.Adapter
:group_resource_mapperExScim.Groups.Mapper.DefaultMapperModule implementing ExScim.Groups.Mapper.Adapter

Ecto Storage (ex_scim_ecto)

KeyDefaultDescription
:storage_reporequiredYour Ecto Repo module
:user_modelrequiredEcto schema module or {Schema, opts} tuple
:group_modelrequiredSame format as :user_model

When using the {Schema, opts} tuple form, available sub-options are:

  • :preload - list of associations to preload (default [])
  • :lookup_key - primary key field (default :id)
  • :filter_mapping - map of SCIM attribute paths to DB columns (default %{})
  • :tenant_key - column used for multi-tenant scoping (default nil)
  • :field_mapping - map of domain fields to {db_field, to_storage_fn, from_storage_fn} tuples for value transformation (default %{})
config :ex_scim,
  storage_strategy: ExScimEcto.StorageAdapter,
  storage_repo: MyApp.Repo,
  user_model:
    {MyApp.Accounts.User,
     preload: [:roles],
     lookup_key: :uuid,
     filter_mapping: %{"emails.value" => :email},
     tenant_key: :organization_id,
     field_mapping: %{
       active: {:status,
         fn true -> "active"; false -> "inactive" end,
         fn "active" -> true; _ -> false end}
     }},
  group_model:
    {MyApp.Groups.Group,
     preload: [:members],
     tenant_key: :organization_id}

Capabilities

Reported in the ServiceProviderConfig discovery endpoint.

KeyDefault
:patch_supportedfalse
:bulk_supportedtrue
:bulk_max_operations1000
:bulk_max_payload_size1_048_576
:filter_supportedfalse
:filter_max_results200
:sort_supportedfalse
:change_password_supportedfalse
:etag_supportedfalse

Discovery

KeyDefaultDescription
:documentation_urinilURI included in ServiceProviderConfig
:authentication_schemes[]List of scheme maps per RFC 7643
:resource_typesUser + GroupList of resource type maps
:schema_modulesBuilt-in User, EnterpriseUser, GroupSchema definition modules

Lifecycle & Tenancy

KeyDefaultDescription
:lifecycle_adapternilModule implementing ExScim.Lifecycle.Adapter
:tenant_resolvernilModule implementing ExScim.Tenant.Resolver

Authorization Scopes

Scopes are strings in the ExScim.Scope struct's :scopes list, populated by your AuthProvider.Adapter when validating a token or credentials.

Standard scopes

ScopeEndpointsActions
scim:read/Users, /Groups, /Schemas, /ResourceTypes, /ServiceProviderConfigGET (list, show, search)
scim:create/Users, /Groups, /Bulk (POST operations)POST
scim:update/Users, /Groups, /Bulk (PUT/PATCH operations)PUT, PATCH
scim:delete/Users, /Groups, /Bulk (DELETE operations)DELETE

/Me scopes

ScopeAction
scim:me:readGET /Me
scim:me:createPOST /Me
scim:me:updatePUT /Me, PATCH /Me
scim:me:deleteDELETE /Me

Bulk operations

For /Bulk, scope is enforced per individual operation rather than on the request as a whole. A caller with only scim:create may submit a bulk request containing POST operations; any PUT, PATCH, or DELETE operations in the same payload will return a 403 operation result (and count toward failOnErrors).

Example scope lists

# Read-only client
scopes: ["scim:read"]

# Provisioning client - can create and update users, but not delete them
scopes: ["scim:read", "scim:create", "scim:update"]

# Full-access admin client
scopes: ["scim:read", "scim:create", "scim:update", "scim:delete"]

# Self-service user (Me endpoint only)
scopes: ["scim:me:read", "scim:me:update"]

Multi-Tenancy

Multi-tenancy is opt-in. When no tenant_resolver is configured (or scope.tenant_id is nil), the system operates in single-tenant mode with no isolation applied.

To enable it, wire up three pieces:

  1. Tenant resolver - implement ExScim.Tenant.Resolver to extract a tenant identifier from the request (header, subdomain, path, etc.).
  2. Phoenix plug - add ExScimPhoenix.Plugs.ScimTenant to your SCIM pipeline after ScimAuth.
  3. Ecto tenant key - set :tenant_key on your model tuples so queries are scoped and creates inject the tenant ID.
# 1. Resolver
defmodule MyApp.TenantResolver do
  @behaviour ExScim.Tenant.Resolver

  @impl true
  def resolve_tenant(conn, _scope) do
    case Plug.Conn.get_req_header(conn, "x-tenant-id") do
      [tenant_id] -> {:ok, tenant_id}
      _ -> {:error, :missing_tenant}
    end
  end
end

# 2. Config
config :ex_scim,
  tenant_resolver: MyApp.TenantResolver,
  user_model: {MyApp.Accounts.User, tenant_key: :organization_id},
  group_model: {MyApp.Groups.Group, tenant_key: :organization_id}

# 3. Pipeline
#    Add: plug ExScimPhoenix.Plugs.ScimTenant

The resolver can optionally implement tenant_scim_base_url/1 to generate tenant-specific resource location URLs (e.g., https://acme.example.com/scim/v2).

Custom Adapters

Storage Adapter

Implement the ExScim.Storage.Adapter behaviour to use a custom data store:

defmodule MyApp.CustomStorage do
  @behaviour ExScim.Storage.Adapter

  def get_user(id), do: # your implementation
  def create_user(user_data), do: # your implementation
  # ... other callbacks
end

Resource Mapper

Implement ExScim.Users.Mapper.Adapter or ExScim.Groups.Mapper.Adapter to control how domain structs map to SCIM JSON:

defmodule MyApp.UserMapper do
  @behaviour ExScim.Users.Mapper.Adapter

  def from_scim(scim_data) do
    %MyApp.User{
      username: scim_data["userName"],
      email: get_primary_email(scim_data["emails"])
    }
  end

  def to_scim(%MyApp.User{} = user, _opts) do
    %{
      "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"],
      "id" => user.id,
      "userName" => user.username,
      "emails" => format_emails(user.email)
    }
  end
end

Endpoints

All endpoints are served under the scope you configure (typically /scim/v2).

Users

MethodPathDescriptionRFC
GET/UsersList with filtering, sorting, pagination§3.4.2
POST/UsersCreate§3.3
GET/Users/{id}Fetch by ID§3.4.1
PUT/Users/{id}Replace§3.5.1
PATCH/Users/{id}Partial update (JSON Patch)§3.5.2
DELETE/Users/{id}Delete§3.6

Groups and Me follow the same pattern. See RFC 7644 §3.11 for Me endpoint details.

Other

MethodPathDescriptionRFC
POST/.searchCross-resource search§3.4.3
POST/BulkBulk operations§3.7
GET/ServiceProviderConfigServer capabilities§4
GET/ResourceTypesSupported resource types§4
GET/SchemasSchema definitions§4