In this post, I will show you, how to set up a multi-tenant application with Firebase, GraphQL, and Angular. This is a common scenario for SaaS applications, as multiple tenants should be able to have their data separated from other tenants’ data and that data should be kept safe as well using proper authorization checks.
We will cover, how to design the Firebase Firestore database and setup Firebase Authentication to support multiple tenants as well as how to implement the necessary authorization checks on a GraphQL NodeJS server hosted with Firebase functions. There is going to be an Angular app, containing the course portal, which among others, are used for Angular Architect Accelerator.
Design the Firestore database
First, we are going to design our Firestore database to support multiple tenants. We are going to do this, by creating a
schools collection, containing the different schools (tenants). The tenant’s data are then nested under these schools, that way the tenant’s data is split among separate vertical slices, which makes the security and tenant management easier.
Under each school (tenant), the tenant data is users (containing user-specific data), and course collection. Each course includes sections, lessons, and resources collections (containing the necessary data for each school).
On the top level, the
schools collection contains the tenants, which contains courses and users:
Each course contains sections, resources, and lessons, where the sections contain a list of referenced lessons:
We store user data such as completed lessons and action items on each users’ entry:
Having that set up, our database now supports multiple-tenants, as well as user and course-specific data for each tenant.
Activating multi-tenant login
To support multi-tenant authentication we will use Firebase Authentication + Google Cloud Platforms’ Identity Platform, which will provide us with multi-tenant support.
You will need to go to Identity Platform in Google Cloud Platform and enable it. Here you can create tenants and add users to a specific tenant.
Integrating with the GraphQL server
We need to do these things in the GraphQL server:
- Verify id token on the requester and setting role and tenant id to each resolver, so we have it handy for all resolvers
- Use the tenants when reading and writing from/to Firestore
Verify id token and set context
For convenience, we want to get the
schoolId from an HTTP header, we are setting in our GraphQL context, so we can access it in every query/mutation without the client needing to provide it explicitly in the GrahQL payload.
Also, we want to get the user id and validate, that the requesting user has a valid token. We are using
Firebase Authentication for authentication on the Angular client.
We set this data, as well as verifies the id token in the Apollo server context middleware (on our GraphQL NodeJS server):
Use the context in the resolvers
In the queries and mutations, we want to check, that the uid in a payload, matches the current user’s userid (which we get from context) or the user is an admin of that user’s school.
Here we are using the context data to check if the requesting user is authorized to update the lesson’s completed status and is using the
schoolId (tenantId) to update
Setting up the clients for multi-tenancy
Since the clients need to tell the server on each request, which
school it belongs to, it is convenient to do that using a header in an HTTP interceptor.
In this snippet, the
schoolId and idToken is set as HTTP headers on each request and is used in the Apollo context as shown in the previous section.
Clients routing flow
For the routing, we need the route to start with the
schoolId param, so we can set the school id.
SchoolIdResolver is setting the Firebase Authentication tenant id:
After the user is logged in, it is navigated to a page for selecting the course:
What’s it! Now multiple tenants can use your application and have their data nicely separated and secured on your backend. Also, we saw how to make it simple for the clients to provide the tenant identifier (schoolId) to the server on each request, as well as having the routing set up for multi-tenancy.