How to authenticate multiple Hasura roles using Firebase
This insightful article guides you through the steps of authenticating multiple user roles in Hasura using Firebase, ensuring secure and role-specific access to your application's data. It begins by laying the groundwork with prerequisites, such as an existing Hasura and Firebase setup and a basic understanding of Hasura’s Permissions & Roles. The focus is on leveraging Firebase's JWT and custom claims to distinguish between user roles like 'admin', 'professor', and 'student' in an academic portal setting. The article walks through creating roles in Hasura, setting up the signup API in Next.js, and configuring Firebase custom claims during user signup. The final part of the tutorial delves into setting Hasura permissions based on these custom claims to control database access effectively. By the end, you'll have a clear understanding of implementing robust authentication and permission systems that are both secure and tailored to different user types.
This article assumes that:
- You’ve created a Hasura and Firebase Project.
- You’ve already setup Firebase authentication with Hasura.
- You know what Hausra’s Permissions & Roles are.
- You know that Firebase authentication with Hasura revolves around a JWT that’s created by Firebase and fed into Hasura’s API calls.
- You’ve mastered the art of decoding your cat's meows and can engage in complex conversations about existential feline matters.
What are we achieving here?
At the end of this article you will know how we can setup Firebase custom claims in order to differentiate between different user types such as ‘admin’, ‘professor’, ‘student’ user types in a university’s portal. This will allow us to setup different permissions for different roles on Hasura, which will be secured and authenticated by Firebase’s JWT having custom claim embedded in it.
Yes, another tutorial with academic portal as a example, deal with it.
Understanding Firebase Custom Claims
Before we dive into the guide, we should learn what custom claims actually are. Custom claims are like superhero capes for user authentication. Basically they are additional pieces of information that can be attached to a user's authentication token in Firebase Authentication. These claims are used to define user roles or permissions beyond what is provided by default in Firebase. By utilizing custom claims, we can add custom authorization logic to our application.
In this example we’ll be configuring Firebase to attach custom claims to the JWT before sending it to the Hasura whenever the user logs in. This configuration will be done at the time of user creation aka user signup, obviously.
Here is how to do it
STEP 1: CREATE ROLES IN HASURA CONSOLE
You need to login to the hasura console of your project and go to the permissions tab of any one of the table you’ve created. You should see something like this:
This is the default admin (the boss) role created by Hasura that you cannot remove or modify its permissions.
In the ‘Enter new role’ enter the name of your new role, duh.
Lets say we have created two new roles: ‘Student’ and ‘Professor’.
STEP 2: WRITING THE USER SIGNUP API
I’m using Next.js API to write my code for user signup, this API caters for ‘Student’ and ‘Professor’ user type.
First, we need to initialize the application.
import { NextApiRequest, NextApiResponse } from "next";
import axios from "axios";
import {
getAuth,
createUserWithEmailAndPassword,
} from "firebase/auth";
import firebaseAdmin from "firebase-admin";
import {
INSERT_PROFESSOR_MUTATION,
INSERT_STUDENT_MUTATION,
} from "./insertOperations";
const { privateKey } = JSON.parse(process.env.PRIVATE_KEY as string)
const firebaseCreds = {
type: process.env.TYPE,
project_id: process.env.PROJECT_ID,
private_key_id: process.env.PRIVATE_KEY_ID,
private_key: privateKey,
client_id: process.env.CLIENT_ID,
auth_uri: process.env.AUTH_URI,
token_uri: process.env.TOKEN_URI,
auth_provider_x509_cert_url: process.env.AUTH_PROVIDER_X509_CERT_URL,
client_x509_cert_url: process.env.CLIENT_X509_CERT_URL,
client_email: process.env.CLIENT_EMAIL,
}
if (!firebaseAdmin.apps.length) {
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(
firebaseCreds as firebaseAdmin.ServiceAccount
),
});
}
This API needs to know the email and password to signup a user. It should also know the user type that needs to be created.
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
res.status(405).json({ error: "method not allowed" });
return;
}
const { email, password, typeOfUser } = req.body;
We’ll use firebase’s createUserWithEmailAndPassword function.
const auth = getAuth();
await createUserWithEmailAndPassword(auth, email, password);
const user = auth.currentUser;
Here is how we’ll write the Firebase custom claim for Hasura. It’s a JSON object that is dependent on the type of user we’re signing up. The ‘x-hasura-default-role’ is the role assigned to this custom claim. The ‘x-hasura-allowed-roles’ is the list of roles that can be assumed by the role we are setting. Since ‘Student’ and ‘Professor’ have independent permissions, we’ll only keep the current role itself in the list. If for example we need an ‘Admin’ role which could do whatever ‘Professor’ and ‘Student’ combined could do, we’ll set the
const customClaim = {
"<https://hasura.io/jwt/claims>": {
"x-hasura-default-role": typeOfUser, //typeOfUser could be 'Student' or 'Professor'
"x-hasura-allowed-roles": [
typeOfUser === typeOfUser,
],
"x-hasura-user-id": user.uid,
},
};
await firebaseAdmin.auth().setCustomUserClaims(user.uid, customClaim);
If for example, we were assigning an ‘Administrator’ role which could do whatever ‘Professor’ and ‘Student’ combined could do, we’ll set the ‘x-hasura-allowed-roles’ like this:
"x-hasura-allowed-roles": [
"Student", "Professor"
],
The user has been created on Firebase, but we also need to store the Firebase uid in Hasura database. This is because, we’ll setup the Hasura role-based permissions using this uid. We can store it in ‘student’ or ‘professor’ table according to what type of user is signing up.
await axios({
method: "POST",
url: process.env.NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT,
headers: {
"content-type": "application/json",
"x-hasura-admin-secret": process.env.HASURA_GRAPHQL_ADMIN_SECRET,
};
data: {
query:
typeOfUser === "Professor"
? INSERT_PROFESSOR_MUTATION //Ezpz query
: INSERT_STUDENT_MUTATION, //just drag n drop in the hasura console. You know it.
variables: {
id: user.uid,
},
},
});
res.status(200).json({ data: "success" });
}
Congratulations! Now ‘Student’ and ‘Professors’ can signup on your platform having their own set of permissions to different tables in your application’s database schema. Now let’s have a sneak peak of how would you configure permissions:
STEP 3: CONFIGURING ROLE-BASED (BASED) PERMISSIONS IN HASURA
Your role-based permissions would be based on the type of user that is trying to access your database. Thanks to Firebase custom roles, Hasura now knows what type of user it is through its JWT.
Here are the two permissions we are setting :
- We don’t want ‘Professor’ to update the student table and vice versa. But we want ‘Professor’ to be able to update their own table and vice versa.
- We don’t want one student updating data of another student. Same goes for professors.
The first part is fairly easy.
Go the permission tab for the professor table. Yeah the same place we created roles:
Here we already created ‘Professor’ and ‘Student’ roles. By default all of the permissions will be marked as a red ‘X’, which means neither of the roles have any permission for this table. Let’s change that. Click on the update permission of ‘Professor’ role. You should see something like this:
Expand the ‘Row update permissions’ and select ‘With custom check’. The check will be based on the ‘X-Hasura-User-Id’ aka the firebase uid extracted from the JWT. If there is a row whose ‘id’ (Firebase uid) property equals to the id from the JWT and the JWT also has the ‘Professor’ in the list of ‘x-hasura-allowed-roles’, the user can update that particular role.
Your custom check should look something like this:
Finally go to the ‘Column update permission’ tab and toggle the table property you want to allow to be updated under this custom check and click on ‘save permission’. The red ‘X’ mark should now change into this weird looking funnel thingy mark:
But wait, we don’t want to let the ‘Student’ role to update the professor table. Don’t worry, the red ‘X’ mark on the ‘Student’ update permission indicates the ‘Student’ role has no power here.
You can setup permissions for the student table in the same way.
This article was written by Muhammad Hassan Bilal, Co-Founder & CFO at Antematter.io