Controlling Data Access Using Firebase Auth Custom Claims (Firecasts)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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.
Info
Channel: Firebase
Views: 141,648
Rating: undefined out of 5
Keywords: Firebase, Firebase Auth, Firebase Auth Custom Claims, Cloud Functions Firebase, Firebase Admin SDK, Admin SDK, app developers, mobile apps, mobile app sdk, app backend sdk, custom attributes, role based access control, firebase apps, app security, firecasts, jen person, custom auth claims, firebase auth claims, app UX, app security updates, Flutter, web apps, Firebase auth admin sdk, auth admin, tokens, firebase custom claims, realtime db, firebase realtime, GDS: Yes;
Id: 3hj_r_N0qMs
Channel Id: undefined
Length: 13min 11sec (791 seconds)
Published: Tue Sep 25 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.