JEN PERSON: Lots of apps require different levels of data access
for different types of users.
For example, you may have an app for teachers and students.
Teachers need to be able to see all student data,
add grades, and make exams; and students
need to be able to see their own grades and take those exams.
A news app needs access for readers
to view stories, editors to add their own stories,
and admins to control all content.
A movie review app will have lots of users leaving reviews,
and there may be a few moderators adding movie data
and screening reviews.
So this is a common pattern with lots of different solutions.
I'll show you one solution on today's Firecast.
When it comes to handling different user
roles in your app, you have a few options.
You could use security rules with roles
you control yourself.
For instance, if you're using Cloud Firestore,
you can have a collection listing users' roles,
and then check in security rules to see
if a user has the correct permission before allowing
them to write data.
This is a viable option and sometimes the best one
depending on the kinds of roles you'll have,
but you can see how rules will get complex
if you have to write a get request
like this for each role.
And these reads of the database do count
towards billing and quotas.
You'll also need separate rules for the documents containing
the roles.
Another option is to have separate projects and databases
for different types of users.
They can share data through a secure back end
that syncs what both apps need to be able to access.
This can be a good use case for Cloud Functions.
Since they'd be separate databases,
they could have separate security rules suitable
for each type of user.
Now this is a fine solution too.
But what if you have many different roles,
or users can have multiple roles each?
Keeping data in sync across multiple databases
is going to get complex really quickly.
So both of these options will work,
but there's another solution that may be a better fit.
Firebase Auth Custom Claims.
Let me explain what that means.
No matter how you're signing in with Firebase Auth--
whether it's using Firebase's email password Auth,
a third party provider like GitHub or Google,
or a custom Auth token that you create yourself,
at the end of the process you get back
a Firebase ID token that identifies your user
against the Firebase service.
Firebase Auth ID tokens are jots with some extra data on them
about the Firebase user, like their email address and display
name.
In addition to this data, you can add custom claims
to the token to enable different user roles.
Custom claims are key value pairs
that you can set per user at the server level.
They can be defined to give administrative privileges
to access data resources, like you
would need for apps with teachers and students,
and provide multi-level access to data,
like you have in apps with paid and unpaid subscribers.
Now that you know you can add custom claims to a user,
it might sound like a good idea to add all sorts of data
to that custom claim.
Perhaps you want to add a home address, or additional photo
URLs, or a profile description, for example.
But this is not what custom claims are for.
It wouldn't be a good experience for your users
if their ID token had lots of extra data
that had to be passed around every time you use it.
So in general, you don't add other properties
to the Firebase ID token directly.
Instead, you store the additional properties
in the Realtime Database or Cloud Firestore
and look them up as necessary.
Also, custom claims are limited to a total size of 1,000 bytes.
If you find that you're hitting that limit,
you may need to rethink how you're using claims,
as it's going to be tough to track that amount of claims
properly in security rules.
So with those cautions in mind, let's
get back into how to apply those claims to grant
limited access to data.
I'm going to use the example of an app
for sharing movie ratings.
I'm calling the app Fire Flicks.
There's a codelab that goes along with the Fire Flicks app
that you should really check out if you're
interested in custom claims.
In Fire Flicks, I want a few choice
moderators to have the ability to add
new movies to the database.
I want any user to be able to review movies,
but I don't want them to be able to add movies to the database.
I can use custom claims to distinguish
between regular users and moderators
by adding a claim to the ID token of moderators.
I definitely don't want to do that on the client.
Instead, I can send a request to my server
to make those changes for me.
Whether it's a server you build yourself
or a serverless solution like Cloud Functions,
you'll want to restrict who can interact with those services.
Fortunately, you can manage and access claims from your server
using the Firebase Admin SDK.
I'm going to show you how to get and set custom claims using
the Admin SDK for JavaScript.
Specifically, I'm going to use TypeScript.
You can also set claims using the admin SDKs
for Python, Go, and Java.
I'm not going to go through the install process in detail
right now, but if you need that information,
I've linked to a Firecast that walks you through install,
as well as to a guide for getting started with the admin
SDK.
I'm going to declare a function called
grantModeratorRole, which takes one parameter, a user's email.
This is the email of the user we want to grant
moderator privileges to.
First, I'll use a function from the Firebase Admin SDK,
getUserByEmail, to get the Firebase user who
has the given email.
I check the user's current claims using
the customClaims parameter.
If the user already has the moderated claim,
my work here is done and I return.
If the user does not, I then call
the admin SDK's setCustomUserClaims function,
which adds the claim to the user.
You can add multiple claims at once,
but remember that 1,000 byte limit.
Now I just showed you how to use the admin
SDK to add custom claims.
You can then make a call from the client
to the server to add the claims as needed.
But there's an important piece missing from the code I just
showed you.
I need a way to ensure that only authorized users can add claims
to other users.
One way I can do this is by passing along
the Firebase ID token of the user making the request.
Then, server side-- be it on a Cloud Function or whatever
protected environment I'm using--
I can check to see if the user is authorized
to promote other users.
Let's add that check to our server code.
In order to check if the user making the request
has permission, I'll need to be able to check for claims
on their Firebase ID token.
One option is to send those along
as a header in the request.
If you'd like to see what that looks like,
check out the codelab that I've linked below.
Today, I'm going to show you how to do
this using a callable function.
The reason for this is because when
a request is made to a callable function,
information about the user who made the request
can be found in the context past the function, which enables
me to do something like this.
I can check for the moderator claim using
context.auth.token.moderator.
If the moderator claim isn't set to True for that user,
I return an error.
This error will get passed back to the client.
Then if the user does have the moderator claim,
I pass the email from the request to the grant moderator
role function I showed you earlier.
So far, we've seen how to set a custom claim on a user
and how to check for the presence of that custom claim
in your own managed environment using the admin SDK.
But perhaps the most powerful use of a custom claim
is with Firebase security rules.
Since users' claims are accessible from security rules,
you can restrict data access for Cloud Firestore, the Realtime
Database, and Cloud Storage based on the presence
of those claims.
Let's look at an example.
Rules in Cloud Firestore and Cloud Storage
share the same format.
I'm going to assume you're familiar with security rules
already.
If you'd like to find out more about them,
check out the guides I've linked below.
There's a lot to security rules, and I'm not
going to cover all of it here.
I want to focus on securing a path using custom claims.
Claims in Cloud Firestore and Cloud Storage
are accessed using request.auth.token.
Any claims present can be accessed
using the key of the claim.
So for Firestore and Storage, I can
access a claim called Moderator using
request.auth.token.moderator.
In Fire Flicks, movie documents are stored
in a collection called Movies.
I want to ensure that authenticated users can
read movies, but only moderators can write to them.
Those rules look like this.
By the way, if you're using the Realtime Database,
the rules are slightly different.
They look like this.
Custom claims provide a great way
to implement role-based access to Firebase data.
But wait!
There's more.
Custom claims are accessible client side too.
This allows you to customize what
users see based on their role.
Here's some brief examples of what that looks
like on iOS, Android, and web.
Each of the Client Auth SDKs has a function
for getting the claims from the Firebase ID token.
In Swift, it's getIDtokenresult. Don't confuse this
with the getIDtoken function, which works similar, but passes
along an encoded token.
If the Moderator claim is found, I
show the UI with moderator privileges.
If it's not, I show the regular UI.
On Android, I use getIDtoken to get the ID token.
I call getClaims to get all the claims,
and get to read the claim I'm looking for, in this case,
moderator.
Just like on iOS, I can then update the UI based on
whether or not the user is a moderator.
For web, I call getIDtokenresult.
This function returns a promise which, when fulfilled,
is an ID token result object.
This object contains the ID token jot string, other helper
properties for getting different data associated with the token,
and all the decoded payload claims.
I check for the moderator claim and display a different UI
depending on the result.
There's a good chance that simply hiding or showing
content based on the presence of claims
will lead to a suitable experience for most
of your users.
But as a developer, you know this isn't
a method of securing data.
For a web app, for example, anyone
can just open up the browser's developer tools
and inspect the code to find out what's going on under the hood.
Using custom claims client side should only
be used to create a better user experience.
It's not secure.
In order to make sure that even a clever developer like
you can't access that moderator data,
you need to add security on the back end
as well, through Firebase security
rules as I showed earlier.
Think of using custom claims on the client
as a means of guiding users in your app,
not as a means of security.
It's so important that I'll say it again.
Relying solely on client side use of custom claims
is not secure.
It is the security rules that actually protect data.
When your rules are properly implemented,
even if someone is able to take apart the app
and find the moderator functionality,
they still won't be able to write to collections reserved
for moderators because their user's Firebase ID token will
not have a moderator claim.
So by now, I bet you're an expert of Firebase Auth custom
claims.
But before you go build a great custom user
experience with claims, here are a few things to keep in mind.
Remember that custom claims need to be applied
in a secure back end environment,
or users could add their own, which would defeat the purpose.
You could use whatever server or serverless solution you prefer.
You'll probably want to use some combination of claims
on the front end and back end to create the best user experience
and ensure data security.
If a user has logged in and then you add a custom claim,
they won't have it on their token
until the next time the token is issued.
The reverse is also true--
if you take away access by removing a custom claim,
access will not be immediately revoked on existing tokens.
Each client SDK has a method to refresh the Firebase token,
so you can use this where appropriate.
Cloud Firestore, the Realtime Database, and Cloud Storage
all update permissions based on changes to the user's UID, not
the refresh token.
This means that even if you refresh the token,
security rules will not reflect changes in access.
Signing out changes the UID to null,
so when the client signs back in,
Firebase products will be looking at the permissions
of the latest token.
Finally, setting a custom claim with the same key
as an existing one will always cause an overwrite operation.
This might be what you're looking for,
but can lead to undesirable consequences
if it isn't expected.
All right.
You're now ready to use Firebase Auth custom claims to customize
data access.
I'd love to hear about how you are
using claims in your own apps.
Let me know in the comments below,
or on Twitter @ThatJenPerson.
And if you found this video helpful,
be sure to subscribe to the Firebase channel
for more great content.
Thanks so much for watching.
I'll see you on a future episode of Firecasts.