You have unlimited access as a PRO member
You are receiving a free preview of 3 lessons
Your free preview as expired - please upgrade to PRO
Contents
Recent Posts
- Object Oriented Programming With TypeScript
- Angular Elements Advanced Techniques
- TypeScript - the Basics
- The Real State of JavaScript 2018
- Cloud Scheduler for Firebase Functions
- Testing Firestore Security Rules With the Emulator
- How to Use Git and Github
- Infinite Virtual Scroll With the Angular CDK
- Build a Group Chat With Firestore
- Async Await Pro Tips
Role Based Authorization With Firestore and Angular
Episode 75 written by Jeff DelaneyFull source code for the Angular Firestore role-based auth project.
In this lesson, I will show you how to assign roles to users with the Firestore NoSQL database, then secure them on both the frontend and backend. This feature is known as role-based access control and is commonly used in forums, social media, etc - Stack Overflow Privileges is an excellent specimen of this feature in the wild (minus the ranking system). Keep in mind, there are many different ways to implement role-based authorization - I am sharing an approach that offers a high degree of flexibility, but may need to be customized to fit the needs of your application.
Roles vs. Abilities
This tutorial manages access control with roles and abilities. For example, the admin
role canDelete
a post.
- Roles are assigned to a user.
- Abilities are assigned to a role.
Do not skip the section that covers backend Firestore database rules, as this is the only way to truly protect your data. Frontend security is great for the user experience, but does not provide real security.
Firebase Authentication Setup
If you’re brand new to Firebase authentication, I recommend checking out Episode 55 OAuth with Firestore first. We are going to build directly on that lesson with some additional logic to assign user’s roles.
Core Module
For this demo I have created a core module. It contains all of the user authentication and authorization code.
ng g module core |
Don’t forget to add the CoreModule
to the imports section of app.module.ts.
User and Roles Interfaces
We have an interface for the User
and another for Roles
. Every role is represented with a boolean value and a user can have multiple roles. Maintaining multiple roles makes it easy to customize their abilities in complex ways.
export interface Roles { |
Auth Service for Firestore
The auth service keeps track of the logged-in user’s document in Firestore. I’m not going to explain this code, but go here if you want a detailed breakdown of the Firestore auth service.
import { Injectable } from '@angular/core'; |
Backend Security
Backend security is the single most important part of this tutorial. Firestore security rules provide the only guaranteed mechanism to ensure your data cannot be accessed by unauthorized users.
You will likely need to repeat your role-based security rules frequently, which will get out-of-control in a hurry. Thankfully, Firestore allows us to write reusable rule functions to keep our code expressive.
Rules for Any Document
The rules outlined below will fetch the user document and check the value of a given role. This function makes it easy to write rules by saying hasRole('admin') == true
, as opposed to writing out the entire path for each rule.
match /posts/{document} { |
Rules for the User Document
It’s also important that we define rules on the user document to prevent a user from giving themselves an unauthorized role. We still want the user to be able to customize data on their document, unless it involves changing a role. The following rule will allow the user to write the subscriber
role on create, but only admins can update the document with the editor
or admin
role.
The request.resource.data
allows you to check the incoming data and enforce rules against it.
match /users/{document} { |
Defining Authorization Rules
Now let’s jump back to the auth.service.ts
file and write some authorization rules. In my opinion, it is best to write activity-based rules, then assign roles to them. For example, we have three rules below canRead
, canEdit
, and canDelete
- pretty self-explanatory.
Each method has a static array of authorized roles, then the checkAuthorization
helper will see if any of those roles exist on the user document. You might consider extracting this logic to it’s own class if the complexity becomes hard to maintain.
///// Role-based Authorization ////// |
Enforcing Roles in a Component
The authorization rules expect a plain user object, which allows you to use them in the component’s template or typescript. The easiest way to enforce an ability is to remove it’s corresponding UI element from the DOM.
<button *ngIf="auth.canEdit(user)" |
But you can also subscribe to the current user and prevent certain actions from being executed.
user: User; |
Securing Routes
ng g guard core/can-read -m core |
An easier approach is to block unauthorized users at the router level. In this case, we are going to create two different router guards in Angular - one for a specific role and one for a specific ability. The idea is to…
- Activate routes only for users with the
admin
role. - Activate routes only for users roles assigned to the
canRead
ability.
Admin Guard
The guard works by taking the user Observable and mapping it to a boolean. If the user is not logged in or does not have the admin role, it will evaluate to false and block the route.
import { Injectable } from '@angular/core'; |
Can Read Guard
The cool thing about this system is that we can activate routes for multiple roles in a single guard. Using the exact same code from the AdminGuard
, we just use the canRead
helper from the auth service, which will capture all roles with this ability.
// ...omitted |
The End
Role-based security often requires a specialized implementation molded to the needs of the underlying product. My goal today was to provide a flexible solution that gives you a foundation for building complex access control features with Angular and Firebase. Let me know if you have questions in the comments or on Slack.