Curity user management with SCIM tutorial

User Management with SCIM

On this page

Introduction

In this tutorial, we'll setup the user management module in Curity, and with that done, proceed to create some users using the SCIM interface. We will also look at some common operations like listing users, updating an individual user and using filters to narrow down our search results to exactly what we want.

SCIM

SCIM (System for Cross-domain Identity Management) is a standardized way of representing resources, with users being the main focus. Pretty much any resource could be represented by the SCIM format and protocol though.

The SCIM standard has seen a couple of revisions since its first release, and the current version is SCIM 2, which is what we'll use in this tutorials while referring to it simply as SCIM.

As SCIM is built on top of HTTP and RESTful principles, some familiarity with these technologies will greatly ease the learning process. Luckily, if you're reading this, you've probably used a REST API or two. Great!

Configuring the Curity Identity Server

The guide assumes that you have run the Basic Setup Wizard. This ensures that a data source is set up, as well as an authentication profile and an OAuth profile. These components are needed for us to build the user management profile on top of, but if you have got all those set up by your own configuration that should work perfectly fine as well. So what do we need to add for user management? Let's have a look:

  • A user management endpoint (we'll use /user-management in this example)
  • A reference to the data source where we will store our users, aptly named user-account-data-source
  • A reference to a credential manager (if we want to be able to set and update passwords for our users)
  • An authorization manager should also be configured, to restrict access to the SCIM endpoint, as shown in the below screenshot. See the authorization manager tutorials for further details.
  • Last but not least, we need a configured client to use for sending authenticated queries to our user management endpoint. See the tutorial on client credentials for how to obtain an access token and use that for all the example requests in this guide.
Selecting Authorization Manager

Granted these requirements we could either use the CLI, the admin UI or XML configuration to set this up. Here we've used the latter, and an example of a minimal XML configuration is seen below:

xml
12345678910111213141516171819202122232425262728293031323334353637
<config xmlns="http://tail-f.com/ns/config/1.0">
<environments xmlns="https://curity.se/ns/conf/base">
<environment>
<services>
<service-role>
<id>default</id>
<endpoints>user-management-admin</endpoints>
</service-role>
</services>
</environment>
</environments>
<profiles xmlns="https://curity.se/ns/conf/base">
<profile>
<id>user-management</id>
<type xmlns:um="https://curity.se/ns/conf/profile/user-management">um:user-management-service</type>
<settings>
<user-management-service xmlns="https://curity.se/ns/conf/profile/user-management">
<api-authentication>
<oauth-service>token-service</oauth-service>
</api-authentication>
<account-manager>default-account-manager</account-manager>
<token-data-source>default-datasource</token-data-source>
<credential-management>
<credential-manager>default-credential-manager</credential-manager>
</credential-management>
</user-management-service>
</settings>
<endpoints>
<endpoint>
<id>user-management-admin</id>
<uri>/user-management</uri>
<endpoint-kind>um-api</endpoint-kind>
</endpoint>
</endpoints>
</profile>
</profiles>
</config>

To merge it with your existing configuration, save it as an XML file (for the purpose of this example, we'll call it user-management.xml), and use the idsh tool to merge it with your existing configuration:

bash
12345678910111213
user@localhost:~/identity-server/idsvr$ ./bin/idsh
user connected from 127.0.0.1 using console on localhost
user@localhost> configure
Entering configuration mode private
[ok][2022-12-14 10:46:08]
[edit]
user@localhost% load merge somewhere/user-management.xml
[ok][2022-12-14 10:46:29]
[edit]
user@localhost% commit

We should now have Curity setup with the user management endpoint found at /user-management. This is the "base path" for all user management operations and won't (currently) respond with anything unless it's followed by the resource type we are interested in. As this tutorial promised user management, we'll naturally be most concerned with users, which conveniently enough can be found at the /Users endpoint (under the base path, so /user-management/Users). To verify that it's working, send a GET request to your new endpoint and you should get a pretty sparse response:

json
123456789
{
"totalResults": 0,
"startIndex": 1,
"itemsPerPage": 50,
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"Resources": []
}

Voila! We are up and running. Now, let's add some users.

Creating new users using SCIM

Create user request

Just like we used GET to fetch our (still non-existing) users, creating a user is as simple as following the same REST conventions and sending a POST request to the /user-management/Users endpoint with the request payload being the data on the user to be created. Let's create a user, using only the bare minimum attributes:

json
123456789101112
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "teddie-the-robot-vacuum-cleaner",
"emails": [
{
"value": "teddie@example.org",
"primary": true
}
]
}

Looking at the above (quite minimal) request, we see some core components in action. Every request (and response for that matter) is defined by a schema. For a simple request like this to the /Users endpoint this could seem a little superfluous, but for more advanced scenarios involving several resource types and schemas, this becomes an absolute necessity.

Additional attributes

The above example includes a set of bare minimum attributes. It is possible to pass additional attributes in the payload as needed. The below example adds a password, externalId and tenantId when creating a user.

json
12345678910111213141515
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"userName": "teddie-the-robot-vacuum-cleaner",
"password": "Pa$$w0rd1!",
"emails": [
{
"value": "teddie@example.org",
"primary": true
}
],
"externalId": "ext-23",
"tenantId": "tenant-775"
}

Enable Password Management

Setting a password as part of creating a user requires that the User Management profile has Enable Password Management enabled and a Credential Data Source configured.

Enable Password Management

Note that the password is passed in clear text in this example but will be hashed according to the configuration of the Credential Manager configured on the User Management profile when it is persisted in the data source.

Create user response

If everything worked as it should, the server will respond with HTTP status 201 Created, and a response body showing the just created user:

json
1234567891011121314151617181920
{
"emails": [
{
"value": "teddie@example.org",
"primary": true
}
],
"meta": {
"resourceType": "User",
"created": "2022-12-13T14:39:33Z",
"lastModified": "2022-12-13T14:39:33Z",
"location": "https://localhost:8443/user-management/Users/VVNFUjplNzA0MmQ0ZDYxNDM0ZGQ3YjdlZTg2MmZlNzJlMzhhMA"
},
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"active": false,
"id": "VVNFUjplNzA0MmQ0ZDYxNDM0ZGQ3YjdlZTg2MmZlNzJlMzhhMA",
"userName": "teddie-the-robot-vacuum-cleaner"
}

Let's have a look at what we've got here. First of all, it's obvious that the response contained a lot more information than we sent in the POST request. This is because some values (like created only is known to the server actually creating the user). Other values like "active" are just determined by the server to have a default, like "false" in this case.

The id attribute exists for any SCIM resource, and is - just like some of the other attributes - created by the server along with the resource. According to the SCIM specification, any ID must be globally unique, regardless of resource type. This is possible to enforce largely just because the client has no say in what ID the server should assign to the client.

Go ahead, change some details in the request and create a couple of users. It will make searching for them in the next section feel a little more meaningful! We'll add at least one more user whose active status will be set to true.

When using SCIM for authentication, only users that have an active attribute set to true are normally considered

Listing existing users

Remember how we previously sent a GET request to the /Users endpoint only to have it come back with an almost empty response. Not any more! Repeat the request now and you should see a listing of all the users you created above. At minimum you should be able to see both Teddie and Eddie in the response:

json
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
{
"totalResults": 2,
"startIndex": 1,
"itemsPerPage": 50,
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"Resources": [
{
"id": "VVNFUjozNzYyYzYwZTJiMjE0ZGY2YTlkNDA3ZTdlMTE5M2I3Zg",
"userName": "eddie-the-tea-cooker",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"emails": [
{
"value": "eddie@example.org",
"primary": true
}
],
"active": true,
"meta": {
"resourceType": "User",
"created": "2017-12-14T13:25:51Z",
"lastModified": "2017-12-14T13:25:51Z",
"location": "https://localhost:8443/user-management/Users/VVNFUjozNzYyYzYwZTJiMjE0ZGY2YTlkNDA3ZTdlMTE5M2I3Zg"
}
},
{
"id": "VVNFUjo1NmEzYWUwMWY5Zjk0MmJmYjczNmVjYzFkZGM4N2ZjYw",
"userName": "teddie-the-robot-vacuum-cleaner",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"emails": [
{
"value": "teddie@example.org",
"primary": true
}
],
"active": false,
"meta": {
"resourceType": "User",
"created": "2017-12-14T13:35:13Z",
"lastModified": "2017-12-14T13:35:13Z",
"location": "https://localhost:8443/user-management/Users/VVNFUjo1NmEzYWUwMWY5Zjk0MmJmYjczNmVjYzFkZGM4N2ZjYw"
}
}
]
}

Searching users with a provided filter

Most of the times we don't want to simply list all users. In a production system there are likely several thousands, so we're likely to want to list only those according to a given search criteria. In the SCIM protocol, this is called a filter. If you are familiar with SQL databases you'll rightly recognize the similarities to the syntax of the query language used for filtering in SCIM. See the SCIM specification on filter for a complete document on the possible options. Note however, that not all options for filtering may be supported.

For the most common use cases, adding a filter is a simple as adding the filter parameter to the GET query:

url
1
https://localhost:8443/user-management/Users?filter=active eq true

In the example above, we've narrowed the search to only include users whose active attribute is set to true. Now only Eddie shows up in the response:

json
123456789101112131415161718192021222324252627282930
{
"totalResults": 1,
"startIndex": 1,
"itemsPerPage": 50,
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
],
"Resources": [
{
"id": "VVNFUjozNzYyYzYwZTJiMjE0ZGY2YTlkNDA3ZTdlMTE5M2I3Zg",
"userName": "eddie-the-tea-cooker",
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"emails": [
{
"value": "eddie@example.org",
"primary": true
}
],
"active": true,
"meta": {
"resourceType": "User",
"created": "2017-12-14T13:25:51Z",
"lastModified": "2017-12-14T13:25:51Z",
"location": "https://localhost:8443/user-management/Users/VVNFUjozNzYyYzYwZTJiMjE0ZGY2YTlkNDA3ZTdlMTE5M2I3Zg"
}
}
]
}

Use and, or and not and other operators to build more complex queries.

With filtering out of the way, let's see how we can make Teddie active as well.

Updating a user

When it comes to updating a user SCIM provides us with two options. One is to use the HTTP PUT method to replace all values on an existing resources, and the other way is to use the HTTP PATCH method to update only what is needed. Since all we want is to make Teddie active, the PATCH method seems like the most appropriate. Both methods are used directly on the URL of the actual user, which you might remember being found in the meta.location attribute in the response we saw previously.

Sending a PATCH request

An example PATCH request to update user Teddie, found at

url
1
https://localhost:8443/user-management/Users/VVNFUjo1NmEzYWUwMWY5Zjk0MmJmYjczNmVjYzFkZGM4N2ZjYw

could look something like this:

json
123456789101112
{
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
],
"Operations": [
{
"op": "replace",
"path": "active",
"value": true
}
]
}

Looking at the attributes sent in the query, we can see that we first specify the type of operation we want to perform. In this case we want to update an existing value, so the replace operation is what we want (add and remove is also available). Next we see the path to the attribute we want to operate on. In this case the active attribute is on the top level of the resource, but specifying a nested path is also possible using dots in the attribute path (like name.lastName). Last we provide the new value that we want set, in our case true.

Again we get the user back in the response. This time we can see that the value of active now is set to true!

json
1234567891011121314151617181920
{
"emails": [
{
"value": "teddie@example.org",
"primary": true
}
],
"meta": {
"resourceType": "User",
"created": "2017-12-14T13:35:13Z",
"lastModified": "2017-12-14T14:46:55Z",
"location": "https://localhost:8443/user-management/Users/VVNFUjo1NmEzYWUwMWY5Zjk0MmJmYjczNmVjYzFkZGM4N2ZjYw"
},
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User"
],
"active": true,
"id": "VVNFUjo1NmEzYWUwMWY5Zjk0MmJmYjczNmVjYzFkZGM4N2ZjYw",
"userName": "teddie-the-robot-vacuum-cleaner"
}

Repeating our GET search query with the filter should now return both users.

Conclusion

By now we've seen how to setup user management in Curity and with that up and running how to create, search and modify users. While this covers the most common needs in most user management systems, more advanced features are certainly available. Have a look at the official specifications for both the SCIM schema as well as the SCIM protocol for further reading.

Join our Newsletter

Get the latest on identity management, API Security and authentication straight to your inbox.

Start Free Trial

Try the Curity Identity Server for Free. Get up and running in 10 minutes.

Start Free Trial