Licensed to be used in conjunction with basebox, only.
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 Actions
and 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) => {
// 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);
}
};
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 ID
at 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 Actions
and 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 beDefault
Display on consent screen
should be offInclude 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
andUser Attribute
of the new mapper aspermissions
; you can use another name of your choice (note that theUser Attribute
name will be used when creating permissions/attributes below). - Set the
Token Claim Name
tobasebox/permissions
(this name must be the same). - Set
Claim JSON Type
toString
- Set
Add to ID Token
andAdd to userinfo
toOff
- Set
Add to access token
must be on. Multivalued
andAggregate 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. permissions
or 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):
with all the operation permissions the user has.