Entitlements in Introspection

Entitlements in Introspection

operate

Including Entitlement Information in Introspection Results

It is very common to need some form of permissions or entitlements when determining if a user should be able to call an API. This can be done using OAuth scopes, but often additional rights need to be verified. These permissions are often needed in the API gateway, so that it can check the user's right before forwarding the call to the back-end. This is also a convenient location when the API gateway is introspecting the token to convert it from a phantom token to a JWT (i.e., from an opaque GUID used on the Internet to a corresponding JSON Web Token used internally).

To save the rights of a the user, the SCIM 2 user account model can be used. This schema defines a standard way of representing these user-specific entitlements. In that specification, it stipulates that a user may have a (possibly empty) set of rights that look like this (when encoded as JSON):

Snipped of SCIM 2 JSON that contains a user's entitlements

    {
        "entitlements": [
            {"value": "a"},
            {"value": "b"},
            {"value": "c"}
        ],
        // ...
    }

Curity supports the SCIM 2 standard and makes the user data available when the token is introspected. The following walk-through will explain how these features can be combined to solve this need. If you'd like to follow along, make sure that you have already setup:

  • An OAuth profile with a client that can get an access token (e.g., via the code flow or assisted token flow) and another that can introspect it. The first will be the one used by a Web or mobile app and the second will be the one used by the API gateway.
  • An authentication profile that allows the user to login via an HTML form
  • Other basic facilities like a Data Access Provider (DAP) and cryptographic keys

Setting Entitlements for a User

The SCIM 2 API can be used to set entitlements of users through a standard mechanism. This API supports GET, POST, PUT, and PATCH on users and groups of users. This provides a known protocol that a customer support portal, for instance, can leverage to update all user attributes, not just entitlements.

Configuring a New User Management Profile

If a user management profile has been created, then this API is available. If not, however, it can be setup by performing the following steps in the Web UI:

  1. From the User Management menu, click New Profile.
  2. Give it some name, e.g., user-management, and ensure that the Type is set to user-management.
  3. On the General page of the new profile, select the OAuth Server that will be used. This is an OAuth profile that a client, like the customer support portal, must obtain a token from in order to call the SCIM API.
  4. On the Data Sources page of the new user management profile, select the Token Data Source where tokens are stored. Also select the User Account Data Source. In a simple deployment, these will be the same.
  5. On the Endpoints page, click New Endpoint. A. In the ID field, enter some identifier, e.g., um1, and then click Done. B. Enter the URI path of the SCIM API (e.g., /um) C. In the Endpoint Type dropdown, select um-api. Then, click Done.
  6. Click the System menu, and select Deployments from the left-hand sidebar.
  7. For each server that should host the new SCIM API, do the following: A. Click the name of the server. B. In the Endpoints multi-select box, type or select the name of the new endpoint's ID (e.g., um1) C. Click the Close button and repeat for any other server nodes.
  8. From the Changes menu at the top of the UI, click Commit. You may enter a comment if you'd like. Then click OK. If the configuration is correct, it will be committed and the new API will be immediately deployed to the specified nodes.

Testing the New API

If the configuration was accepted and committed, it should be ready to use. The endpoint will be available on the server(s) that were configured. Each of those have a protocol (e.g., https), a listening address or interface (e.g., 0.0.0.0 for all interfaces on that server host), and a port (e.g., 8443). These together with the URI of the um-api endpoint form the base URL of the SCIM API. The standard itself defines the remaining part of the URL. For user management, this final component of the path will be Users. So, the endpoint will be something like::

https://localhost:8443/um/Users

To call this endpoint, a token is needed that authorizes the caller. This can be obtained in various ways. When the caller is a customer support portal, for instance, this token will usually be obtained using the client credential flow. The client ID and secret that the portal uses will be some configured values that it keeps safe. With this, it can make a request such as this to obtain an access token:

Sample HTTP request to the OAuth token endpoint using the client credential flow

    POST /dev/oauth/token HTTP/1.1
    Host: localhost:8443
    Authorization: Basic Y2xpZW50LW9uZTowbmUhU2VjcmV0
    Content-Type: application/x-www-form-urlencoded

    grant_type=client_credentials&response_type=token

In this request, the client ID and secret may also be provided in body using the form elements client_id and client_secret. The result will be a JSON document that includes an access token:

Sample JSON response from the OAuth token endpoint

    {
        "access_token": "75398416-3958-4828-a738-6c27465204eb",
        "scope": "",
        "token_type": "bearer",
        "expires_in": 300
    }

With this access token in hand, the client can now call the SCIM API to get a list of users. To do so, it would make a request similar to this one:

An HTTP request to the SCIM 2 endpoint to get a list of users

    GET /um/Users HTTP/1.1
    Host: localhost:8443
    Authorization: Bearer 75398416-3958-4828-a738-6c27465204eb
    Accept: application/scim+json

The result could be a number of users with various attributes as shown in the following elided listing:

Abbreviated SCIM results for getting all users

    {
        "totalResults": 1,
        "startIndex": 1,
        "itemsPerPage": 50,
        "schemas": [
            "urn:ietf:params:scim:api:messages:2.0:ListResponse"
        ],
        "Resources": [
        {
            "id": "VVNFUjoyOWZmODMzYWQ2NGM0MzY1YmY3Y2QzYjQ1YjM1NWI0Mw",
            "userName": "teddie",
            "active": true,
            // ...
        }]
    }

To restrict this to a particular one, a query string can be added to filter the results by email addresses. To do this, append a query string such as filter=emails eq "teddie@example.com". The quotes in the filter are required by the SCIM specification. The result should be a single user if your filter is precise enough. Another noteworthy part of the request is the Accept header; this should always be application/scim+json or */*.

Curity currently does not support CORS on the SCIM 2 API, so you may need to proxy/wrap the calls in a server-side application if you intend to use it from a JavaScript-based front-end.

In any event, the API should be clearly working by performing such requests. If it is not, check that the endpoint was assigned to a server node.

Adding Entitlements to an Existing User

To add entitlements to an existing user, the PATCH method can be used. This will update certain attributes of the user without affecting others. Using the id of the user, a PATCH request can be made to the SCIM users endpoint like this:

A SCIM 2 PATCH request to add entitlements to an existing user

    PATCH /um/Users/VVNFUjoyOWZmODMzYWQ2NGM0MzY1YmY3Y2QzYjQ1YjM1NWI0Mw HTTP/1.1
    Host: localhost:8443
    Authorization: Bearer 75398416-3958-4828-a738-6c27465204eb
    Accept: */*
    Content-Type: application/scim+json

    {
        "schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
         "Operations":[{
            "op":"add",
            "path":"entitlements",
            "value":[
                {"value": "a"},
                {"value": "b"},
                {"value": "c"}
            ]
        }]
    }

In this request, the user with an ID of VVNFUjoyOWZmODMzYWQ2NGM0MzY1YmY3Y2QzYjQ1YjM1NWI0Mw is being patched to have three new entitlements added: a, b, and c. The string values can be anything that makes sense for your application. SCIM also allows certain metadata to be added to the entitlement attribute as well.

With these new rights in place, it is now possible to include them in the introspection results, so that the API or its gateway can make an informed access control decision.

Including Entitlements in Introspection Results

To include these new entitlements in the introspection results, we have to configure a custom credential query and add a token issuance procedure that returns the user's rights when the token is introspected.

Custom Credential Query

To include the entitlements attributes in the ones that are available for a user when that user's token is introspected, a custom credential query must be configured. This should be done on the DAP that is associated with the authenticator used to login. If the authenticator does not have a DAP, a custom attribute query can be used instead; however, this will not be described in this walk-through. An HTML form authenticator is thus assumed; this kind of authenticator always uses some sort of account DAP to store and retrieve user information, including the newly added entitlements. On this DAP, the custom credential query should be added. To do this in the admin Web UI, do the following:

  1. In the Facilities view, click Edit next to the name of the DAP.
  2. In the Custom Credential Query text field, enter the following query:
    select * from accounts WHERE username = :subjectId AND active = 1

Here, :subjectId will be replaced with the username of the user that logged in (e.g., teddie).

Adding a Global Procedure

Because it is very common to need the entitlements, a global procedure can be added. In this walk-through, we will only use it with the introspection endpoint, so retrieving the entitlements could be done directly in the introspection procedure. Just to see how the global procedures could be used, however, this extra step will be shown. To add such a procedure, do the following in the UI:

  1. In the System section, click Procedures.
  2. Click New Procedure.
  3. Enter a Name like getEntitlements.
  4. Select global-scripts from the Procedure type dropdown menu and click Create.
  5. In the editor that opens, enter the following script:
    function getEntitlements(context) {
        var subjectAttributes = context.subjectAttributes();
        var jsonAttributes = subjectAttributes["attributes"];

        if (jsonAttributes) {
            var parsedJsonAttributes = JSON.parse(jsonAttributes);

            return parsedJsonAttributes["entitlements"].map(function(x) {
                return x.value;
            });
        }
        else {
            return null;
        }
    }
  1. Click Save

Introspection Procedure

A custom introspection procedure is needed that can call this global function and add the entitlements to the result. To do this in the UI, perform the following:

  1. Open the OAuth profile that is being used.
  2. Click Endpoints on the left-hand side.
  3. Search for the introspection endpoint that is being used.
  4. In the Procedures dropdown associated with that endpoint, select introspect-procedure. An Edit button will appear. Click this button.
  5. Another editor will open. In this, replace the existing script with the following:

Introspection procedure that calls the global getEntitlements function

    function result(context) {
        var responseData = {
            active: context.presentedToken.active
        };

        if (context.presentedToken.active) {
            appendObjectTo(context.presentedToken.data, responseData);

            responseData.entitlements = getEntitlements(context);
            responseData.token_type = context.presentedToken.type;
            responseData.client_id = context.presentedToken.delegation.clientId;
            responseData.expired_scope = context.presentedToken.expiredScopes;
        }

        return responseData;
    }
  1. Click Update, close the editor
  2. From the Changes menu, click Commit.

Checking the Introspection Results

To test the procedure shown in, a token that represents the user is required. Tokens that represent a client only, like those issued when the client-credential flow is used, will not work. A user-specific access token that runs the custom credential query and gets the entitlements will be issued after the user logs in with an HTML form authenticator that uses that DAP. (The DAP will not be used directly but through the account manager that the HTML form authenticator uses.) With this token in hand, the API or the API gateway can introspect it. A sample request is shown in the following listing:

Introspection an opaque phantom token for a user with entitlements

    POST /introspection HTTP/1.1
    Host: localhost:8443
    Authorization: Basic Y2xpZW50LW9uZTowbmUhU2VjcmV0
    Content-Type: application/x-www-form-urlencoded

    token=260a0704-e77d-4ad5-8ad7-090e9607d2a6

Here, the client ID and secret are supplied in an HTTP Authentication header using the basic authentication scheme. This credential is that of the API gateway (or if no such gateway is used, then the API itself). This client must have the introspection capability, and if it is only an API gateway, it should not have not have any other capabilities. The result will be a JSON document such as the following:

Introspection results that include the entitlements as an array of values

    {
        "sub": "teddie",
        // ...
        "scope": "read openid write",
        "entitlements": [
            "a",
            "b",
            "c"
        ]
    }

These will be the same ones set by the SCIM client at the beginning of the walk-through. With this information in hand, the gateway has not only the scopes but other permissions necessary to make a course-grained access control decision.

Conclusion

From this writeup, you have learned how to setup Curity as a SCIM 2 server by defining a user management profile. With that you saw how you could add information to a user such as entitlements. We also showed how that information can be used to enrich introspection results of tokens that are issued through Curity's OAuth server. With this, an API gateway can enforce access control. In all of this, the APIs exposed through Curity are standards-based, making it fast and easy to integrate into existing customer portals, mobile apps, and web sites. This entire use case was implemented as configuration and required no custom coding, and can be setup in just a few minutes.

Was this page helpful?