User Management with GraphQL

User Management with GraphQL

On this page

This tutorial shows how to manage user account data within your applications by connecting to GraphQL endpoints. First ensure that you are running version 7.0 or later of the Curity Identity Server. The tutorial will explain the setup for an example scenario where a customer portal application is used by administrators to manage user accounts.

Identity Server Setup

When managing user accounts, the Curity Identity Server acts as a Web API, so you must present an access token and use scopes and claims for authorization. In order to provide a secure and flexible solution, there are a few layers, which are explained in the following sections.

Prerequisites

This guide assumes that you have run the Basic Setup Wizard and configured a data source, as well as an Authentication Profile, Token Service Profile and User Management Profile.

GraphQL in the Curity Identity Server is supported for JDBC data sources, and for other data sources you can instead use SCIM Endpoints. This tutorial will assume that user accounts are stored in a PostgreSQL database:

GraphQL Data Source

Add the GraphQL User Management Endpoint

To start using GraphQL for user accounts, first add an endpoint of type um-graphql-api to the User Management Service.

GraphQL Endpoint

Configure Scopes and Claims

This tutorial's client application will use a scope called accounts that contains a groups claim. You must either use the built-in claim name urn:se:curity:claims:admin:groups or the specific claim name groups, and in this tutorial the latter option is used. It is important to ensure that the scope is then included in access tokens under the Claims Mapper section:

GraphQL Scope and Claim

The groups claim must evaluate to an array of strings. This tutorial will use the following hard coded value, though various types of ClaimsValueProvider can be used if required, to assign values according to custom logic. See the Adding Claims tutorial for an example.

GraphQL Groups Claim Value

Create an Authorization Manager

Next navigate to System Authorization and add an Authorization Manager with the Groups type.

New Authorization Manager

Then map the manager to the accounts scope. The below scope enables full control over all user accounts, though in a real application it is recommended to only assign the permissions needed. The admin value from the groups claim will then map to these authorization rules at runtime:

GraphQL Authorization Manager

Next, use the manager in the User Management Profile. Go to Profiles -> User Management -> General. In the Authorization section choose the previously created Authorization Manager.

Add authorization manager to a profile

Get an Access Token

For the purposes of this tutorial use a simple Client Credentials based client to get an access token. A real customer portal would instead use the Code Flow, where the process would be equivalent, to just add the accounts scope to that app's configuration. The test client can be imported from the following XML, and for convenience it also has the introspection capability, to enable verifying that the access token is issued with the expected groups claim:

xml
1234567891011121314151617181920212223242526272829
<config xmlns="http://tail-f.com/ns/config/1.0">
<profiles xmlns="https://curity.se/ns/conf/base">
<profile>
<id>oauth-dev</id>
<type xmlns:as="https://curity.se/ns/conf/profile/oauth">as:oauth-service</type>
<settings>
<authorization-server xmlns="https://curity.se/ns/conf/profile/oauth">
<client-store>
<config-backed>
<client>
<id>accounts-client</id>
<client-name>accounts-client</client-name>
<secret>Password1</secret>
<scope>accounts</scope>
<capabilities>
<client-credentials/>
<introspection/>
</capabilities>
<use-pairwise-subject-identifiers>
<sector-identifier>accounts-client</sector-identifier>
</use-pairwise-subject-identifiers>
</client>
</config-backed>
</client-store>
</authorization-server>
</settings>
</profile>
</profiles>
</config>

An access token can then be retrieved and introspected using the following curl requests, or alternatively you could perform the same operations using OAuth Tools. These examples use an HTTP setup, to avoid SSL trust issues on a development computer, though of course any deployed system would instead use HTTPS. (To switch to HTTP go to System -> Deployment and change the Protocol used by the Default service role.)

bash
1234567891011
OPAQUE_ACCESS_TOKEN=$(curl -s -k -X POST http://localhost:8443/oauth/v2/oauth-token \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'grant_type=client_credentials&client_id=accounts-client&client_secret=Password1&scope=accounts' \
| jq -r '.access_token')
echo $OPAQUE_ACCESS_TOKEN
curl -s -k -X POST http://localhost:8443/oauth/v2/oauth-introspect \
-u "accounts-client:Password1" \
-H "Accept: application/json" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=$OPAQUE_ACCESS_TOKEN" | jq

The inspection result can be verified to ensure that the groups array claim has the expected value:

GraphQL Access Token

Connect With a Client

Now that an access token is available, tools can be used to connect to the GraphQL endpoint of the Curity Identity Server. First download and install the Insomnia tool. In the app, create a new GraphQL request and enter the URL of the API endpoint, which in this tutorial is http://localhost:8443/um/graphql/admin.

An access token must be provided to make requests to the GraphQL API and so that the Insomnia tool can download the API schema. Do this by selecting Bearer from the Auth dropdown and entering the opaque access token retrieved earlier:

Insomnia Token

Once you enter a valid token, Insomnia retrieves the schema silently in the background. You can view it via the schema / show documentation menu command.

Insomnia schema viewer

Working with Account Data

Use example queries

The queries and mutations from this section can be pasted into the GraphQL pane of Insomnia and sent as POST requests.

You can retrieve the fields available on the main Account object with this GraphQL introspection command, though you may prefer to just use the intellisense to see which fields are available. The response will include a name and description for each field.

graphql
123456789
query getAccountSchema
{
__type(name:"Account") {
fields {
name
description
}
}
}

Find a particular user account using one of the lookup methods:

Only active users by default

The default behavior when creating accounts using the GraphQL API is to set active=false (or 0). The default behavior unless explicitly defined is also to only list accounts where active=true (or 1). The result is that an account could be created in one query and if the accounts are listed in a subsequent query, it will not be available.

Either explicitly set active=true when creating the account or include active=false accounts in the query listing users.

graphql
1234567891011121314151617
query findAccount
{
accountByEmail(email: "jane.doe@mycompany.com") {
id
name {
givenName
middleName
familyName
formatted
}
preferredLanguage
displayName
linkedAccounts {
value
}
}
}

The results are then returned in the standard GraphQL JSON response format, containing the fields requested within a data section:

json
12345678910111213141516
{
"data": {
"accountByEmail": {
"id": "4d807914-ab51-11ec-b593-0242ac170002",
"name": {
"givenName": "Jane",
"middleName": null,
"familyName": "Doe",
"formatted": "Jane Doe"
},
"preferredLanguage": null,
"displayName": null,
"linkedAccounts": []
}
}
}

Some companies will have a large volume of user accounts, so pagination is used when working with lists, to ensure that queries are efficient. Select a list of the first 10 user accounts like this:

graphql
1234567891011121314151617181920
query getAccounts
{
accounts(first: 10) {
edges {
node {
id
name {
givenName
middleName
familyName
}
userName
emails {
value
primary
}
}
}
}
}

During querying, intellisense features are available to explain the meaning of each object and field:

Insomnia Intellisense

To make data changing commands you can use GraphQL mutations. When creating a user account you can optionally also set a password:

graphql
1234567891011121314151617181920
mutation createAccount
{
createAccount(input: {
fields: {
userName: "john.doe",
password: "Password1",
emails: {
value: "john.doe@mycompany.com",
primary: true
}
}
}) {
account {
userName
emails {
value
}
}
}
}

The following example shows an update operation for a user account whose name has changed, so that the userName, displayName and email fields are all set to new values:

graphql
123456789101112131415161718192021
mutation updateAccount
{
updateAccountById(input: {
accountId: "fbea947a-aabc-11ec-adcb-0242ac170002",
fields: {
userName: "jane.smith"
displayName: "jane.smith"
emails: {
value: "jane.smith@mycompany.com"
primary: true
}
}
}) {
account {
displayName
emails {
value
}
}
}
}

Finally, user accounts can be deleted with the following syntax:

graphql
12345678
mutation deleteAccount
{
deleteAccountById(input: {
accountId: "fbea947a-aabc-11ec-adcb-0242ac170002"
}) {
deleted
}
}

There are potentially many other GraphQL features you can use, to put you in better control of your identity data, though the above examples will enable you to get connected.

Custom User Fields

Starting in version 7.2 of the Curity Identity Server, GraphQL can be used to save custom fields against user accounts. First you need to define custom fields in the Admin UI under User Management Service / General. The following example extends the GraphQL schema to add a string field and a numeric field:

Graphql Custom Fields

You can then run a mutation of the following form to populate the custom fields for a user and save them to the user account data. This example query also echoes back the values in the mutation response.

graphql
123456789101112131415
mutation updateAccount
{
updateAccountById(input: {
accountId: "4d807914-ab51-11ec-b593-0242ac170002",
fields: {
Custom1: "A string value"
Custom2: 123
}
}) {
account {
Custom1
Custom2
}
}
}

The custom values are then saved within the attributes column of the data row for the user account, and can then be retrieved in the same way as any other GraphQL field. The values can then be used during authentication and token issuing. The Working with Claims tutorial describes how to include custom values from user account data in access tokens.

Users and Dynamic Clients

In some security use cases, Dynamic Client Registration (DCR) is used to register one or more distinct OAuth clients per user. This technique is described in Mobile Best Practices, where a separate dynamic client is registered per user and device. The Authenticated DCR mobile code example provides a working end-to-end solution.

From version 7.2 of the Curity Identity Server, dynamic clients mapped to users can also be accessed using GraphQL. First enable access to the additional data under User Management Service / General:

Graphql DCR Data

You can then use GraphQL to read details from the dynamically_registered_clients database table. The result set could potentially contain multiple dynamic clients per user, and the DCR fields are described in the Using Dynamic Client Registration article.

graphql
1234567891011121314151617
query findAccount
{
accountByEmail(email: "demo@user.com") {
id
name {
givenName
familyName
}
emails {
value
}
dynamicClients {
client_id
scope
}
}
}

All of this data can then be accessed for any user with an account record. As for other areas of the schema, GraphQL tooling will enable you to easily see which DCR fields are available:

GraphQL Advanced Query

Programmatic Clients

Once the above data access techniques are understood, you can begin GraphQL coding against user accounts in your own apps. There are many client libraries available to choose from, in all the main technology stacks. These provide similar features to the Insomnia tool, to enable productive development.

Conclusion

User accounts in the Curity Identity Server are easy to manage in your own apps. GraphQL endpoints provide a friendly API with modern tooling for developers, though SCIM endpoints are also available, and may be a better choice for some types of operation. To use GraphQL you must connect securely, with an access token that has the expected groups scope, and which maps to an Authorization Manager, where the level of access is defined.