Home Supported Standards Terms & Conditions Privacy Policy Documentation Contact About

← All protocols

SCIM 2.0 Provisioning Debugger

What this workflow is, and how the page is laid out

SCIM (RFC 7642, 7643, 7644) is how an identity provider puts somebody into a directory and takes them out again — the one protocol here whose whole purpose is to write. Two halves: one endpoint at a time for the ordinary debugging, and scenarios, which are named plans of many operations each carrying its own expectation. Hover any field label for what it does.

These endpoints create and delete accounts. Point this at a test directory.
What that means for the scenarios below

The scenarios below really do provision and deprovision the users they describe, against whatever service root the Configuration Parameters pane names, and there is no undo. Every generated userName carries the prefix in the Attribute generator, which is what makes a run findable with one filter and removable in one sweep.

Tip: click a pane’s title to collapse or expand it.

Discovery
What these three documents are

The three documents a client should read first. None of them needs a scope — a client has to be able to read a ServiceProviderConfig before it knows how to authenticate.

There is a fourth source and it is not a document: the WWW-Authenticate header of a 401. This page collects it by itself whenever the service root changes — one unauthenticated request, which is the only normative requirement RFC 7644 section 2 makes of a SCIM server — and the scheme list in the configuration table above is ordered by what came back.

The SCIM service root every path on this page is composed against — the host plus the SCIM base path, which is /scim/v2 on essentially every implementation. Do NOT put a resource path here; /Users and the rest are built onto it. Changing it makes this page go and ask the server what it accepts, which is why it sits with these three buttons. The baseUrl row in the configuration table is a mirror of this box, not a second copy of the setting.
What was read, and what it said

Nothing has been read yet.

As it came back The document verbatim, exactly as the server sent it. The Described tab is a reading of it and this is the thing being read — a disagreement between the two is a defect in this page, which is why both are here.
Configuration Parameters
What this pane is for, and where the credentials are

Every setting this workflow has, in one place, with where each one came from. That is the question this pane exists to answer: a value you typed and a value read out of a discovery document behave identically until the server changes its mind, and then only one of them is stale. Edit any of them and it takes effect at once; Save writes them to this browser. Rows marked discovered can be put back with Restore discovered values.

The table is the whole of the configuration: the service root, the call path and the authentication scheme are here and nowhere else, so there is one value of each rather than two that can drift apart.

The credentials are under the table, not in it — with one exception, and it is the access token. A password, a signing key and a minted proof are not parameters — they are not read out of a document, they are not compared against one, and two of the three must never be written down at all. So they sit below in the Credential block, which shows only what the selected scheme actually uses. The password is never stored anywhere; the HOBA private key is generated per session and never stored, because a signing key in storage is a signing key in every extension’s reach.

The access token is in the Authentication section above, directly under the scheme that decides whether it is sent at all. It is the one credential here that a reader has to go and get, and the button that gets it — which runs the OAuth2 / OIDC workflow and carries the token back into the field — is no use a screen away from the scheme that needs it. It is still stored only under its own opt-in, which ships clear.

Paste one, or get one from the OAuth2 / OIDC workflow with the button below. It is sent as a Bearer credential, or bound to a DPoP proof when that scheme is selected.

Off by default, and it ships CLEAR. Clearing the box removes whatever was already stored, on the spot. A token carried back from the OAuth2 / OIDC workflow is handed over in sessionStorage and removed as this page reads it, so it is not stored under any setting either. A password on this page is NEVER stored, and neither is the HOBA private key.
Why the call path is fixed to this browser

What the api will do with a call handed to it

Asking the api what it will do…

Credential
What this scheme is, and what it can be refused with

Specification:

What the server said it accepts

Never written to localStorage, under any setting, and never a row in the table above. It lives in this field and nowhere else. The account it belongs to is the authUsername row.
Digest is two requests

The first is answered 401 with a nonce, the second carries a hash computed over it. Both legs are in the Exchange pane, because that handshake is what this scheme is.

Which challenge is being answered

The key and the server nonce

Key: no proof has been minted yet. Server nonce: none.

Last proof A DPoP proof is a JWT signed over THIS method and URL and carrying a hash of the access token, so a captured one is not replayable against another endpoint. The key is generated for this page session and never stored.
Names the key in the signed blob and in the registration. Generated here; the private half lives in this page only and is never written to localStorage — a signing key in storage is a signing key in every extension's reach. The account it is registered FOR is the hobaUsername row in the table above.
The to-be-signed blob

Length-prefixed, RFC 7486 section 5:

Public key The half that is registered with the server. The private half never leaves this page and is never written to localStorage.
Registration response What /.well-known/hoba/register answered. RFC 7486 section 7 puts that endpoint on the SERVER'S ORIGIN and not under the SCIM base path, which is the mistake worth checking here first.
Nothing is added to the request, and no code here could add it. Why

A client certificate is chosen during the TLS handshake by whatever holds the private key, and JavaScript has no say in it. The service root has to be https and the server has to ask. The api cannot make this call either — it would present its certificate, which is a different identity and a misleading one.

One endpoint at a time
Every operation RFC 7644 defines, including the two most implementations leave out: the root /.search, which queries both resource types at once, and /Me. Choosing one rebuilds the query editor, the body field and the preview below.
What this operation is, and what it needs

Specification:

Opaque. Use the one a create or a list gave you rather than building it — against this project's mock STS it is the entry's LDAP DN, and it is percent-encoded into the path exactly once. Encoding it twice produces a 404 that reads like a deleted user.
Remembered from the last create this page made, so a read, a PATCH and a delete do not need an id copied by hand between two fields on the same page.
The same, for Groups. The Use-the-last-one button picks whichever of the two matches the operation.
RFC 7644 section 3.4.2.2, sent EXACTLY as written. This page never quotes or escapes a filter — that grammar is the server's to parse, and a client that escaped it would make invalidFilter unreachable.
An attribute path to sort on. A server may say sort is unsupported in its ServiceProviderConfig and still accept the parameter silently — compare the two orders to find out.
ascending or descending. Left unset, the server's default applies, which RFC 7644 says is ascending.
1-BASED, not zero. A client that pages from 0 re-reads the first resource and loses the last one, silently, on every page.
How many resources to return. count=0 asks for the TOTAL with no resources, which is the cheapest way to ask how many there are.
Comma-separated in a query string; an ARRAY in a /.search body. That asymmetry is in the specification, not in this page.
The other way round, and mutually exclusive with attributes. Useful for dropping the one enormous member list off a Group read.
What shape this body has to be

Sent verbatim as JSON. The Generate button below fills it with the right shape for this operation — a full RFC 7643 User, a PatchOp, a SearchRequest or a BulkRequest.

Will send:

Result The response body. A refusal here is a RESULT and not a failure of this page — a 409 on a duplicate userName is the server answering, and is what the negative scenarios expect.
Attribute generator
Why a generated user carries every optional attribute

Generated users carry every optional attribute RFC 7643 section 4.1 defines. That is the point: what a server stores is usually narrower than what it accepts, and the difference between “accepted and stored” and “accepted and dropped” is what reading the resource back shows.

Makes it reproducible: the same seed always produces the same users, so a failure on the seventh generated user can be run again exactly rather than described. It is a mulberry32 PRNG and is NOT cryptography.
Every generated userName starts with this, which is what makes them findable with one filter and cleanable in one sweep.
Capped at 50. Fifty users is around 150 requests in a scenario, which is a long run and still a run.
The domain every generated email, IM and photo URL is built on.
userName and nothing else — the smallest legal User, and the one a create should never fail on. Useful for telling "this server rejects my attributes" from "this server rejects my credential".
Generated Nothing here has been sent. This is what WOULD be sent, which is the half a provisioning debugger has to be able to show before it writes to a directory.
Scenarios
What a scenario is, and what a green run means

A scenario is a plan, and every step in it carries its own expectation. A green run means every step did what the plan said it would, including the steps that expect a refusal: a 409 on a duplicate userName is a pass and a 201 is a failure.

Twelve named plans plus a random one composed from the seed. Changing this — or the seed, prefix or user count — throws away a plan already on screen, because a plan belongs to the inputs it was built from.
The same seed builds the same plan, users included. An unseeded harness makes every interesting failure a story rather than a test.
Namespaces everything this run creates, so a run can be found with one filter and removed in one sweep. A random scenario gives each of its phases its own prefix on top of this one.
Capped at 50 — fifty users is around 150 requests, which is a long run and still a run.
What this scenario does

This scenario needs a credential

This scenario asserts the server’s access control policy rather than the protocol, so it needs a server with authentication turned on and a read-only credential chosen in the Configuration Parameters pane. Against a server that authenticates nobody every step of it will fail, and that is the correct answer.

Runner
How the run went

Nothing planned yet.

Exchange
How this call was made, and what was added to it

There was a first leg

What this page has to say about the answer

Request

HTTP headers The headers of the request, in wire order and wire form. On a browser-direct call these are the headers THIS PAGE set — the browser adds Host, Origin, User-Agent, Referer and Content-Length of its own and will not say what they were. Through the api they are the whole set, as sent.
Body What was sent, as JSON. Empty on a GET or a DELETE, which carry none — a Content-Type on a request with no body is a header describing nothing, and some servers refuse it.
Response

HTTP headers What came back. On a browser-direct call CORS restricts this page to the seven simple response headers unless the server names more in Access-Control-Expose-Headers — Location, which every SCIM create sends, is usually missing here even though it WAS sent. The pane says so rather than presenting a partial list as a whole one.
Body The response body. A SCIM error is an answer, not a transport failure: it carries a scimType naming which rule was broken, and that is the most interesting thing a SCIM server ever says.
Operations History
Why Failure and Sent are not the same thing

Every request this page sent. Failure means the server answered and the answer was no — very often the correct outcome, and what the negative scenarios expect. A row that stays Sent means nothing came back at all, which on this workflow is usually CORS refusing a browser-direct call before it was made. Those two look identical in a status line and need completely different fixes.