Test server#

The scim2-server command runs a SCIM server for the tests or the demo of a SCIM client, whatever the language of the client. For the tests of a Python project, pytest-scim2-server provides the same server as a pytest fixture.

The command serves a WSGIApplication over an InMemoryStorage, with the WSGI server of wsgiref.simple_server. The server keeps the resources in memory. They are lost when it stops. Options lists every option of the command.

Start the server#

  1. Install scim2-server:

    $ pip install scim2-server
    
  2. Start the server. It listens on 127.0.0.1:8080 by default:

    $ scim2-server --hostname 0.0.0.0 --port 8080
    
  3. Point the client at http://<HOST>:8080/v2, where <HOST> is the address of the machine.

Serve other resource types#

By default, the server serves the users and the groups of RFC 7643, with the enterprise user extension EnterpriseUser. To serve other resources, pass JSON files in the format of the discovery endpoints:

  • --schema: a list of Schema objects;

  • --resource-type: a list of ResourceType objects;

  • --service-provider-config: a ServiceProviderConfig object, to turn a feature such as the bulk requests or the sorting on or off. The cursor pagination is off by default, and needs the cursor extra. The server encrypts its cursors with the secret of the SCIM2_SERVER_SECRET environment variable, or else with a secret drawn at start, so that its cursors do not survive a restart.

$ scim2-server --schema schemas.json --resource-type resource-types.json

Require credentials#

Without credentials, every endpoint is open. The service provider configuration still announces the bearer token scheme, because RFC 7643 §5 requires the authenticationSchemes attribute. A configuration passed with --service-provider-config keeps its own schemes.

Pass each accepted bearer token with --bearer-token, and each accepted user and password of HTTP Basic with --basic-auth. Both options can be repeated, and can be combined. Each one announces its scheme in the service provider configuration:

$ scim2-server --bearer-token s3cret --basic-auth alice:s3cret

A request without valid credentials gets a 401 response. Its WWW-Authenticate header holds a Bearer and a Basic challenge, one for each scheme in use. The /ServiceProviderConfig endpoint stays open.

Accept OAuth access tokens#

The server can act as an OAuth resource server. It then accepts the JWT access tokens of RFC 9068 that an authorization server issues. Pass the issuer of the tokens and the audience of the server. The options need the oauth extra:

$ pip install 'scim2-server[oauth]'
$ scim2-server \
    --oauth-issuer https://auth.example.com/realms/apps \
    --oauth-audience https://scim.example.com

The server finds the public keys of the issuer with the jwks_uri of its metadata. It reads the metadata of RFC 8414 §3 first, then the ones of OpenID Connect Discovery. The issuer of the metadata must be the issuer of the tokens.

When the server cannot reach the issuer URL, pass the URL of the keys with --oauth-jwks-uri. For instance, a server in a container may reach the authorization server under another host name than the one of the issuer:

$ scim2-server \
    --oauth-issuer http://localhost:8080/realms/apps \
    --oauth-audience https://scim.example.com \
    --oauth-jwks-uri http://keycloak:8080/realms/apps/protocol/openid-connect/certs

The service provider configuration announces the oauth2 scheme. An access token is accepted when:

  • its typ header is at+jwt;

  • one of the keys of the JWK set signs it;

  • its iss claim is the issuer, and its aud claim holds the audience;

  • it is not expired;

  • it has the sub, client_id, iat and jti claims.

Pass --oauth-scope to also require a scope. Several scopes separated by spaces are all required.

The server downloads the metadata and the keys with the first token, then keeps them. A token signed with an unknown key downloads the keys again, at most once a minute. The server can thus start before the authorization server.

The responses follow RFC 6750 §3:

  • A request without access token gets a 401 response, with a Bearer challenge without error code.

  • An invalid or expired access token gets a 401 response, with the invalid_token error code. The client can then request a new token. The detail of the SCIM error tells why the token is refused, such as Invalid access token: Invalid claim: 'aud'.

  • An access token without the scope gets a 403 response, with the insufficient_scope error code.

  • When the metadata or the keys cannot be downloaded, the server answers 503, with a Retry-After header of 5 seconds. The client can send the request again later, with the same token.

The OAuth options can be combined with --bearer-token and --basic-auth.

With Keycloak, set the access.token.header.type.rfc9068 attribute of the client to true. Otherwise the typ header of the tokens is JWT, and they are refused. Add an Audience mapper, so that the aud claim holds the audience of the server.

Serve several tenants#

A tenant is an independent set of resources. The first segment of the URL path selects it, with the URL prefix method of RFC 7644 §6.1: /a/v2/Users and /b/v2/Users hold separate users. The schemas, the resource types, the configuration and the credentials are shared.

Pass each tenant with --tenant:

$ scim2-server --tenant a --tenant b

A request to another tenant gets a 404 response. v2 cannot be a tenant name.

To create the tenants on demand, pass --dynamic-tenants. The first request to an unknown tenant creates it, with no resources:

$ scim2-server --dynamic-tenants

Each test of a test suite can then pick a random tenant, and get an empty server. Any client can create tenants, even without valid credentials, and every tenant stays in memory until the server stops. Serve this option to trusted clients only.

Compare the usernames with PRECIS#

By default, the server compares the usernames without the PRECIS rules that RFC 7644 §5 requires. Pass --precis to compare the usernames and the passwords with the PRECIS profiles of RFC 8265. The server then refuses with invalidValue a username that PRECIS does not allow, such as one holding a space. The option needs the precis extra:

$ pip install 'scim2-server[precis]'
$ scim2-server --precis

Keep the resources of a run#

Pass --dump-resources with a file name. When the server stops normally, with Ctrl+C, it writes every resource to the file, as a JSON list. With tenants, the file holds a JSON object, with the list of the resources of each tenant. The server empties the file when it starts:

$ scim2-server --dump-resources resources.json

Run the server in a container#

Each release publishes an image on the GitHub container registry. The server listens on 0.0.0.0:8080 in the container, and the arguments are passed to scim2-server:

$ docker run --publish 8080:8080 ghcr.io/python-scim/scim2-server --bearer-token s3cret

To build the image, run docker build --file Containerfile . or podman build . in the repository.

Run the server behind a reverse proxy#

Pass --reverse-proxy. The server then reads the X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, X-Forwarded-Port and X-Forwarded-Prefix headers, and builds the meta.location of the resources from the URL the client used.

Debug the server#

Pass --debug to log the WSGI environment of each request, with its headers.

Warning

The logged environment holds the credentials. Never use --debug on a server reachable by others.

Options#

usage: scim2-server [-h] [--schema FILE] [--resource-type FILE]
                    [--service-provider-config FILE] [--bearer-token TOKEN]
                    [--basic-auth USER:PASSWORD] [--oauth-issuer URL]
                    [--oauth-audience VALUE] [--oauth-jwks-uri URL]
                    [--oauth-scope SCOPE] [--precis] [--hostname HOST]
                    [--port PORT] [--reverse-proxy] [--dump-resources FILE]
                    [--tenant TENANT] [--dynamic-tenants] [--debug]

Named Arguments#

--schema

Serve the schemas of a JSON file holding a list of schemas. Defaults to the schemas of RFC 7643.

--resource-type

Serve the resource types of a JSON file holding a list of resource types. Defaults to User and Group.

--service-provider-config

Announce the service provider configuration of a JSON file. Defaults to every feature supported, except cursor pagination.

--bearer-token

Accept a static bearer token, and announce the bearer token scheme. Can be repeated.

--basic-auth

Accept HTTP Basic credentials, and announce the Basic scheme. Can be repeated. Without –bearer-token, –basic-auth nor –oauth-issuer, the server accepts every request.

--oauth-issuer

Accept the OAuth JWT access tokens (RFC 9068) of this issuer, and announce the oauth2 scheme. Needs –oauth-audience and the oauth extra.

--oauth-audience

Accept only the access tokens whose aud claim holds this value.

--oauth-jwks-uri

Check the signature of the access tokens with the keys of this JWK set URL. Defaults to the jwks_uri of the metadata of the issuer.

--oauth-scope

Accept only the access tokens with this scope. Several scopes separated by spaces are all required.

--precis

Compare the usernames and the passwords with the PRECIS profiles of RFC 8265, and refuse the values they do not allow. Needs the precis extra.

--hostname

Listen on this address. Defaults to 127.0.0.1.

--port

Listen on this port. Defaults to 8080.

--reverse-proxy

Read the X-Forwarded-* headers of a reverse proxy.

--dump-resources

Write the resources to a JSON file when the server stops.

--tenant

Serve a tenant under /TENANT, with its own resources. Can be repeated.

--dynamic-tenants

Create a tenant on the first request to /TENANT. Any client can create tenants, and they stay in memory until the server stops.

--debug

Log the WSGI environment of each request. Never use it on a server reachable by others.