In the previous Tutorial we learned the basics of obtaining an Access token to use in Hasura to make simple permissions rules. This is good when you only have one role (logged in), but when users can have many roles, and those roles can change, the flow evolves to something more complex.
Here we will learn to make an advanced flow between Auth0 and Hasura to handle multiple roles using Auth0 rules and JWT tokens. This flow consists of first saving new users from Auth0 to Hasura with different roles, and then when the user logs in in Auth0, extracting the roles from Hasura and placing them in the token.
You should first read the article From Easy to Hard: The security in Hasura with Auth0 because this article is a continuation. We assume that you already know and have the basic configuration between Hasura and Auth0.
All code shown in this tutorial are in this Github Repository.
Here is the Hasura server with which this tutorial was made. Pass: topcoder.
In this section, rules are configured so new users that are created in Auth0 are saved in Hasura so that only the user and the password is in Auth0 and everything else associated with that user is in Hasura. But first, we have to create relationships in the employee table.
We go to the Hasura console, the Data tab, the employee table, and the Relationships tab. Here, we add the two suggested relationships.
We do the same with the role_employee table. These relationships will allow us to perform nested object queries between the employee and role_employee tables, to insert and read user roles faster.
Now, we go to the Auth0 dashboard, to the Rules menu, and add a new blank rule. With this rule we will verify that it is the first time the user is logging in. If so, we save it in Hasura with a mutation. The code is the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function (user, context, callback) {
const userId = user.user_id;
const email = user.email;
const mutation = `mutation($userId: String!, $email: String) {
insert_employee(objects: [{
auth0_id: $userId,
email: $email,
role_employees:{
data:[{
id_role: 1
},{
id_role: 2
}]
}
}],) {
affected_rows
}
}`;
if (context.stats.loginsCount === 1) {
request.post({
headers: {
"content-type": "application/json",
"x-hasura-admin-secret": configuration.ACCESS_KEY
},
url: "https://<YOUR-HASURA-DOMAIN>.herokuapp.com/v1/graphql",
body: JSON.stringify({
query: mutation,
variables: {
userId,
email
}
})
},
function (error, response, body) {
callback(error, user, context);
});
} else {
callback(null, user, context);
}
}
Lines 2-3: We obtain the Auth0 id and the user’s email.
Lines 4-18: This is the mutation that we will use to insert the new user with the trial (id: 1) and user (id: 2) roles.
Line 19: We check if it is the first time the user logs in.
Line 20: We made a POST request.
Lines 21-24: We create the header of the request. Here, we pass the Hasura password from the ACCESS_KEY
configuration variable. We will create this variable later.
Line 25: The entry point of our Hasura server.
Line 26: We pass the mutation and the variables that will be used.
Lines 28-30: We return the variables without change to Auth0. If there was an error in the query, it would be saved in the error
variable and sent to Auth0, otherwise the error
variable would have the value null.
Lines 32-34: If it is not the first time the user is logging in, then we simply return the function without any changes.
We save and return to the Rules menu. Here we drag the new rule to the top of all the rules so that it runs first. Then, in the Setting section we add the ACCESS_KEY
variable with the Hasura password. If you neglect this, there is no way to display it once added.
Let’s test the configuration. We go to the menu User and Roles > Users and create a new user. Then, we use the request to obtain the new user’s token.
1 2 3 4 5 6 7 8 9 10
POST https: //YOUR_AUTH0_DOMAIN/oauth/token Content - Type: application / x - www - form - urlencoded client_id = YOUR_CLIENT_ID & grant_type = http: //auth0.com/oauth/grant-type/password-realm& responseType = token id_token & scope = openid profile & realm = Username - Password - Authentication & username = EMAIL_OF_YOUR_USER & password = PASSWORD_OF_YOUR_USER
Performing this request is like logging in. If we consult the entire employee table, our new user will appear.
Up to this point, new users that are created and logged in will be saved in Hasura, but the token with that information still needs to be made. We go to the Rules menu in Auth0 and edit the rule that adds the Hasura variables to the token. The previous rule was as follows:
1
2
3
4
5
6
7
8
9
10
function (user, context, callback) {
const namespace = "https://hasura.io/jwt/claims";
const userId = user.user_id;
context.idToken[namespace] = {
'x-hasura-default-role': 'trial',
'x-hasura-allowed-roles': ['trial', 'user'],
'x-hasura-user-id': userId,
};
callback(null, user, context);
}
In lines 5 and 6 we hard-code the default role and allowed roles, when these must be obtained for each user. We will consult users’ roles from Hasura. The code is the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function (user, context, callback) {
const namespace = "https://hasura.io/jwt/claims";
const userId = user.user_id;
const mutation = `query ($userId: String!) {
employee(where: {auth0_id: {_eq: $userId}}){
role_employees {
role {
text_id
}
}
}
}`;
request.post({
headers: {
"content-type": "application/json",
"x-hasura-admin-secret": configuration.ACCESS_KEY
},
url: "https://<YOUR-HASURA-DOMAIN>.herokuapp.com/v1/graphql",
body: JSON.stringify({
query: mutation,
variables: {
userId
}
})
}, function (error, response, body) {
if (!error) {
var data = JSON.parse(body);
let roles_employees = data.data.employee[0].role_employees;
var roles = [];
for (let role of roles_employees) {
roles.push(role.role.text_id);
}
context.idToken[namespace] = {
'x-hasura-default-role': roles[0],
'x-hasura-allowed-roles': roles,
'x-hasura-user-id': userId,
};
}
callback(error, user, context);
});
}
Lines 4-12: This is the query we will use to bring the roles that correspond to the user who has the current auth0_id.
Lines 13-19: Like the previous rule, we make a POST query to our entry point in Hasura and pass the query with the user id.
Lines 22-27: The result is obtained, unpacked and roles are prepared.
Line 29: As a default, the first user role is placed.
Line 30: The list of roles is passed as allowed roles.
We save the rule. We execute the token request of any user to prove our rule. We copy the token and decode it in Hasura.
If it is a valid token with the roles of that user, then the rule has worked correctly.
One of the most common restrictions you want for an application is that the user can only obtain information that belongs to him. This is what we are going to do in Hasura using the token.
In the Hasura console we go to the Data tab, the employee table and the Permissions tab. We add a select permission to the user role with the restriction that the auth_id
is equal to the variable X-Hasura-User-Id
that is inside the token.
We return to the GraphiQL tab to test the new permission. It is recommended to add more users for better tests.
We place the Header x-hasura-role
with the user role and make a simple query to the employee table.
As we can see, a query to the whole table employee only brings us the information of the user to whom the token belongs, thanks to the restriction in the permission. We can play with these restrictions, we can place more variables in the token using the Auth0 rule, and then use them in Hasura’s restrictions.
We have completed an advanced configuration between Auth0 and Hasura. With this, we can start to develop our application for web, mobile, whatever. Auth0 provides us with the interface and logic of Authorization and Authentication, and in Hasura we secure the database with permissions. In our application we will not have to worry much about those things, and can just do what our application has to do.
Auth0 Rules
Relationships between tables/views