Curity scope authorization manager

Scope Authorization Manager

On this page

Protecting User Management Using the Scope Authorization Manager

Introduction

In the tutorial on user management and SCIM we learned how to setup the user management profile and how to actually manage our accounts through basic CRUD (create, read, update, delete) operations using SCIM. In this tutorials we'll build on the knowledge (and even more - the configuration) acquired there, so it is highly recommended to start with that, but if you already have the user management module up and running and you are familiar with SCIM already, feel free to skip that and only apply the example configuration.

Authorization

With the prerequisites out of the way, there is a glaring (albeit conscious) omission in our current setup. By default the user management module is configured without an authorization manager, allowing any client with a valid access token to read, even create or update users! While this is fine for development, or even production setups where the user management concepts are protected by other means, in most scenarios we'll definitely want to control who gets to do what. In other words, we want to configure authorization.

The Scope Authorization Manager

Authorization in the user management module is done through configured authorization managers. Currently there are two types of authorization managers: a Scope Authorization Manager and the Axiomatics Authorization Manager. This tutorials will cover the Scope Authorization Manager.

The Scope Authorization Manager uses OAuth scopes as a base "unit" for making access decisions. The base concept is rather simple and is probably familiar to most who have ever worked with OAuth scopes before. An OAuth client accesses protected resources by providing an access token, and with this access token often one or more granted scopes. The names of the scopes are chosen arbitrarily, but a good practice is to name them according to their intended use. For authorization in the user management module this could be names like admin_read or admin_write. These names clearly convey the purpose of the scopes. Let's create these scopes and assign them to the client we configured in the tutorial on client credentials:

xml
12345678910111213141516171819202122232425262728293031
<config xmlns="http://tail-f.com/ns/config/1.0">
<profiles xmlns="https://curity.se/ns/conf/base">
<profile>
<id>oauth</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">
<scopes>
<scope>
<id>admin_read</id>
<description>Admin read scope</description>
</scope>
<scope>
<id>admin_write</id>
<description>Admin write scope</description>
</scope>
</scopes>
<client-store>
<config-backed>
<client>
<id>server-client</id>
<scope>admin_read</scope>
<scope>admin_write</scope>
</client>
</config-backed>
</client-store>
</authorization-server>
</settings>
</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-scopes.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][2018-01-11 10:46:08]
[edit]
user@localhost% load merge somewhere/user-management-scopes.xml
[ok][2018-01-11 10:46:29]
[edit]
user@localhost% commit

So far we've added the necessary scopes to the OAuth profile and to the client we'll use for user management. These scopes will be checked by the authorization manager when certain actions are triggered. Actions are events like "client with scope X is trying to read all users" or "client with scope Y is trying to update data on its authenticated subject (user)". When an action is triggered, the decision on whether to allow the action is decided by an authorization policy.

Authorization Policies

How does the Scope Authorization Manager use these scopes to decide permissions for our configured clients? The answer to that is through policies. A policy consists of a "policy action", which is similar to the action described as an event above. However, while user actions like "client presenting a token with scope X is trying to read all users" is always concrete, an action specified on a policy is allowed to be more broad, like "a client is trying to do a read operation on a user management endpoint". While this might seem a little confusing at first, it allows for customizing very powerful authorization rules with very little configuration.

Configuring a Policy

Any client with scope admin_read should be allowed to read any data on the /Users endpoint.

This would translate to this concrete action, which is triggered each time a client tries to read the /Users endpoint by sending a GET request:

um:authorization-actions.user-management.users.admin.read

We could now setup a policy with an action matching the exact path, or we could define a broader policy like "any read operation on the user management profile, regardless of endpoint". This policy action would look like this:

um:authorization-actions.user-management.admin.read

Notice how we removed "users" from the action in the policy? Any requirements (such as scopes) will now be in effect for all read operations, not just on /Users. For the purpose of this example however, let's match on the exact action. Setting up the Scope Authorization Manager and policies is probably most easy to do through the Admin UI, so let's use that. After having logged in, click Authorization -> New Authorization. Name it whatever you feel like. In this example we'll name it test-authorization. In the next step we'll set its type to scopes and then we'll press the Add Policy button. Immediately we'll be asked for the name of the policy. This name is the "policy action" we talked about before, and as we are going to setup this policy to match an exact action, well chose the name um:authorization-actions.user-management.users.admin.read from the drop down menu.

Add Policy

Configuring a Rule

We now have a policy which will be considered each time a client tries to make a read operation at the user management's /Users endpoint. Without proper rules though, our authorization manager won't know what to do, and an authorization manager that doesn't know what to do will by default deny authorization. For our policy to be effective we thus need to provide our policy with a rule or two. Let's add one! We do this by pressing Add Rule, and before we proceed we give it a name. Since we want to require the admin_read scope for read operations on our policy action, I'll name my rule require-scope-admin_read. In the next step, we'll setup a very simple rule. Our rule will mandate that any client with the admin_read scope is allowed access to the action our policy has defined. This would look something like this:

Add Rule

See how simple that was? First we added our admin_read scope to the rule (Scope), then we decided what to do with it. In this case we said that the presence of that scope should allow the client access (Authorization Decision), and that the scope could be present on its own or together with any other scope (Applicability). We could have added more scopes and set applicability to all-of too, for instance if we had wanted admin and read to be separate scopes.

Configuring the User Management Profile to Use Our Authorization Manager

Last but not least, we'll need to actually use our now configured Authorization Manager. As we want to try it on the user management module, we'll simply click User Management and under the Authorization section select our configured scope authorization manager:

Selecting Authorization Manager

Testing Scope Authorization

With our Scope Authorization Manager configured, let's make sure that it actually works. What we've said is that any client presenting a token with the admin_read scope should be able to read (i.e. send a GET request to) the /Users endpoint. This would of course imply that any client presenting a token without the admin_read scope isn't allowed. Luckily, that implied rule is very easy to confirm. Just request an access token without asking for the admin_read scope and use it for trying to read /Users! The response you should see if everything is setup correctly is this:

javascript
1234567
{
"detail": "Not authorized",
"status": "403",
"schemas": [
"urn:ietf:params:scim:api:messages:2.0:Error"
]
}

Let's now ask for a new access token, and this time we'll ask for the admin_read scope while doing so (again, see the tutorial on client credentials for details on how to do this).

Providing our new new access token (with the admin_read scope) in the GET request to /Users and voila, we are in!

javascript
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": true,
"meta": {
"resourceType": "User",
"created": "2017-12-14T13:35:13Z",
"lastModified": "2017-12-14T13:35:13Z",
"location": "https://localhost:8443/user-management/Users/VVNFUjo1NmEzYWUwMWY5Zjk0MmJmYjczNmVjYzFkZGM4N2ZjYw"
}
}
]
}