Skip to content

Permissions - Operation Level Security

Introduction

Note

By default, a user cannot access any operations. They need to be given access in the IdP before they can access an operation.

Operation level security specifies what GraphQL operations a user can "call"; by default, a user is not allowed any. This is specified on the IdP side (e.g. Auth0 or KeyCloak) and sent to basebox in the access token in a Permissions list. Each user needs to be granted access per operation (this can of course be specified in a role and the user granted access to the role). You would need to add the operation name prefixed with allow:bb:operation: to the Permissions list in order for the user to have access to an operation. For example, if you have an operation called getRecord, the user would need a permission called allow:bb:operation:getRecord.

In Auth0, this entry must be added to the Permissions section (of the API and then of the User/Role).
In KeyCloak, it must be added as a claim/attribute on a user.

Unfortunately, there is no standard for this; different OpenID Connect implementations have different methods for this. We will now show you two options of setting up permissions; one in Auth0 and one in KeyCloak.

Auth0: Setting Up a Permissions List

This assumes you've created and set up your Auth0 tenant, application (this is for your client application) and API (the API is for basebox). On the left hand menu, select Applications and then APIs. Click on the API you have set up and click on the Permissions tab. You can now add permissions entries to this page. Permission will be set to the allow:bb:operation: plus the name of the operation (e.g. if you have a getUser operation, the Permission will be allow:bb:operation:getUser). Description can be anything you like, we usually just add the operation name there (i.e. getUser in our example here).
You should add all operations from the schema that you want users to have access to here. If you do not add it here, users will not have access to an operation.

Once you have added all the permissions for the API, you can now go to User Management and Roles. We will create a new role with the appropriate permissions here. You could create multiple roleshere, for example, for normal users and administrators.
Click on Create role and name your role appropriately. Once on the role screen, you will see a tab for Permissions. Click on Add Permissions. Then select the API you want to add permissions for (the basebox API entry you would have created). This will reveal all the permissions you created in the API permissions tab. Select the necessary operation permissions you want users of this role to have access to. The role is now ready to be assigned to users.

Auth0: Adding Permissions to Users

Now that permissions have been set up, you can go to a user and assign the role; this can be done through the Auth0 user interface.

You can also assign a role to a user on user creation. We will use one method of doing this using Auth0 Actions. On the left hand side menu, click on Actionsand then Library. Click on build custom and add a name e.g. Add default role after user creation. Select the Trigger to be Post User Registration. This would then come up with a screen with this snippet of code:

exports.onExecutePostUserRegistration = async (event, api) => {
};
Within the two curly brackets, you need to add code that would add the role to the user, like so:

exports.onExecutePostUserRegistration = async (event, api) => {

  // Create management API client instance
  const ManagementClient = require("auth0").ManagementClient;

  // initial management API with the domain, client id and client secret
  const management = new ManagementClient({
    domain: event.secrets.DOMAIN,
    clientId: event.secrets.CLIENT_ID,
    clientSecret: event.secrets.CLIENT_SECRET,
  });

  // set the user id parameter
  const params =  { id : event.user.user_id };
  // assign our role ids to the list of roles
  const data = { "roles" : ["rol_WPi94fULdshWzuc1", "rol_AL20ZqFnigDUs4Vc"] };

  try {
    // assign roles to the user
    await management.users.assignRoles(params, data);
    // log addition of user role
    console.log("Added user role/s for: " + event.user.user_id);
  } catch (e) {
    // log if there's any errors
    console.log(e);
  }  

};
Once you have copied this code in, you might find that require("auth0").ManagementClient is underlined (indicating an error). You need to add the Auth0 library as a dependency. You can do this by clicking on the cube on the left hand margin of the code editor and then clicking on Add dependency. Then type in the Name as Auth0, leave version as latest and click Create. This will add the Auth0 library as part of your list of dependencies for this code.

You will also notice that DOMAIN, CLIENT_ID and CLIENT_SECRET are highlighted in the above piece of code. As this is sensitive information, we need to add them in Auth0 as a Secret. You can do this by clicking on the key on the left hand margin of the code editor (labeled Secrets). You then Add Secret. You will add three secrets (DOMAIN, CLIENT_ID and CLIENT_SECRET) to this screen. These three values can be found by following the main menu (on the left hand side). Click on Applications, Applications again and and the application you have setup. You will then find Domain, Client ID and Client Secret on the Settings tab and you can use these values for your secret entries.

Lastly, you might notice that this line const data = { "roles" : ["rol_WPi94fULdshWzuc1", "rol_AL20ZqFnigDUs4Vc"] }; has two values added in rol_WPi94fULdshWzuc1 and rol_AL20ZqFnigDUs4Vc, these are role ids for two different roles (we added two to show that adding multiple roles here is possible). The Role ID can be found on the role screen. If you go back to User Management (on the main menu), then Roles and select the role you have created, you will find the Role IDat the top, under the name of the role. You need to copy this out and paste this in the code in order to assign this role to a user. Your code is now ready to run.

Once you have set up this user action, you need to add it to the post user creation flow:
On the left hand side menu, click on Actionsand then Flows. Here will select the Post User Registration flow. You will see a screen with a diagram than shows Start and Complete (there might be other actions added to this flow already, depending on how you have set Auth0 up). On the right hand side bar, select Custom. You will then see the action you've created. Drag this action between the Start and Complete actions and set it down in (the screen will say Drop here) in the appropriate location (i.e. between Start and Complete if you have no other actions). The action is now ready to run after a user is registered.

KeyCloak: Setting up a permissions list

This post assumes that you've already setup a Realm and Client in KeyCloak. This works for KeyCloak version 22 and may have differences for other versions of KeyCloak

In Client Scopes for the realm, create a new client scope (using the Create client scope button). You can call the scope permissions, but the name is not important.

  • Type should be Default
  • Display on consent screen should be off
  • Include in token scope should be on

Once you have saved the client scope, go to the Mappers tab on the new scope (this will appear when you save the scope), then:

  • Click on Configure a new mapper
  • Select User attribute from the list of mappers that pops up
  • Set the Name and User Attribute of the new mapper as permissions; you can use another name of your choice (note that the User Attribute name will be used when creating permissions/attributes below).
  • Set the Token Claim Name to basebox/permissions (this name must be the same).
  • Set Claim JSON Type to String
  • Set Add to ID Token and Add to userinfo to Off
  • Set Add to access token must be on.
  • Multivalued and Aggregate attribute values must be switched on

The new scope for basebox permissions has now been created.

Once this is done, go to the Client that you have created (click on Client on the left hand side and select your client application). Click on the Client scopes tab, then Add client scope and add your new scope to the client.

Now we need to setup a new group for our permissions; this can be done under Groups (on the left hand side menu). Once in Groups, create a new group. A group will hold the permissions as attributes and any user who belongs to the group will automatically inherit these attributes.
You could, for example, create groups for normal users and administrators and assign different attributes to each group.

Note

KeyCloak also has Roles that could be used for this purpose. Role attributes are however not as easily inherited by the user and some additional work would need to be performed to get the role attributes.

Once you have named the group, click on it to add attributes. Click on the Attributes tab and then add an attribute. The key of the attribute will be permissions (or, it you used a different name in the client scope User Attribute field of the mapper above, you use that name instead).
For operation level security, the value needs to be created with the prefix allow:bb:operation: and then the name of the operation that a member of this group is allowed to use. In our demo, we have an operation called getUser, to grant access to this operation would thus be allow:bb:operation:getUser.
You can add more operations, as many as required; the Key will always remain the same (i.e. permissionsor another name that you might have used).

Note

In KeyCloak version 22 there is an issue with adding multiple attributes of the same name. The attributes might disappear but have still been captured. When you look at the access token (see below), you will be able to see the attributes. The issue is logged here and will only be fixed in version 23.*

KeyCloak: Adding the Permissions to Users

Once you have set up the permissions, you need to add users to the appropriate groups.
This could also be done upon user creation. You can also assign a default group that a user belongs to. To do so, go to Realm settings, clicking on the User registration tab and then clicking on the Default groups sub-tab. Then add your default group.

Note

This means that all users will get access to the permissions defined for the group. This could be useful if all registered users get access to a default amount of operations and then administrators might get additional attributes granting them additional permissions. Users can belong to mulitple groups and attributes will then be joined together from the various groups into one list.

Testing and Checking Your Access Token Permissions

Now that you have added the permissions to the IdP, any users created will get the default attributes/permissions. You can test this by using a curl command (assuming you have curl installed) and running the following command:

curl -k -v -X POST -H 'Content-type: application/x-www-form-urlencoded' \
  -d "scope=openid%20email" \
  -d "grant_type=password" \
  -d "username=<username>" \
  -d "password=<password>" \
  -d "client_id=<client id>" \
  -d "client_secret=<client secret>" \
  https://kcdev.basebox.io:8443/realms/vue-todo/protocol/openid-connect/token

Once you have run this successfully, you will get an access token as part of the json structure returned. You can copy this to JWT decoding site (e.g jwt.io) to look at the contents of the access token.

Alternatively, if you want to decode the access token from the command line (this assumes you have jq installed), you could use the following command:

curl -k -v -X POST -H 'Content-type: application/x-www-form-urlencoded' \
  -d "scope=openid%20email" \
  -d "grant_type=password" \
  -d "username=<username>" \
  -d "password=<password>" \
  -d "client_id=<client id>" \
  -d "client_secret=<client secret>" \
  https://kcdev.basebox.io:8443/realms/vue-todo/protocol/openid-connect/token 
  | jq -R 'split(".") | .[1] | @base64d | fromjson' <<< "$JWT"

The decoded access token will show a structure that contains something like the following (this is from our demo):

"basebox/permissions": [
    "allow:bb:operation:deleteTask",
    "allow:bb:operation:createList",
    "allow:bb:operation:updateTask",
    "allow:bb:operation:getUser",
    "allow:bb:operation:updateList",
    "allow:bb:operation:createTask",
    "allow:bb:operation:createUser",
    "allow:bb:operation:deleteList"
  ],
with all the operation permissions the user has.