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.