Using Firebase Auth Custom Claims for custom permissions - firebase

Initial situation:
I am developing an app (iOS, Android, Web App) that primarily uses
Firebase Auth Email & Password to authorize my users and manage permissions.
There are complex configuration options for the security and
access settings that can be individually set for each user. There
are also premade roles (e.g. admin, manager, etc.). However, these
are only templates and don't need to be used by the person who
manages the user accounts. The permissions can be customized for
each user and the whole application.
My approach:
Using Firebase Auth custom claims
Because of the very customizable permission model, I thought of
giving every permission a number like this:
2000: Calendar (Full access)
2001: Calendar (View access)
2002: Calendar (Create access)
2003: Calendar (Edit access)
2004: Calendar (Delete access)
Then I thought it would be a great idea to store the permissions
that are true as a boolean (because of the smaller size than an
Integer) in the custom claims of the user like this:
// User with permissions to view the calendar, create and edit events but not authorized to delete:
'claims': {
'2001': true,
'2002': true,
'2003': true
}
// User with full access to the calendar
'claims': {
'2000': true
}
The permissions are also stored in Firebase Cloud Firestore.
Cloud Firestore
> users
> [unique_user_id]
> permissions
Checking permissions on the clients side
When the user opens the app, on the first load the system performs two
reads to Firebase Cloud Firestore to retrieve all of the current
metadata to set the app up properly. This data is saved locally. It
contains metadata to display everything correctly and also the user's
permissions. This way the UI is already preventing the user from
accessing content that should not be visible or taking actions
(create, edit, delete).
Checking permissions on the server-side via security rules of Firebase Cloud Firestore
Because client-side operations might be manipulated by the user or the
permissions of an individual user change while the user is using the
application, there need to be some advanced security instruments that
improve the overall safety of the application. This should be done on
the server-side via Cloud Functions and the security rules of Firebase
Cloud Firestore.
A security rule could look like this:
service cloud.firestore {
match /databases/{database}/{project_id} {
match /calendar/{event} {
allow read: if request.auth.token.2000 == true || request.auth.token.2001 == true;
allow write: if request.auth.token.2000 == true || request.auth.token.2002 == true;
allow update: if request.auth.token.2000 == true || request.auth.token.2003 == true;
allow delete: if request.auth.token.2000 == true || request.auth.token.2004 == true;
}
}
I could do all of this also with a Cloud Function that checks the
user's permissions before every action to the database. However, this
would result in another read for the permission of the user for every
action that was performed. This would result in a higher amount on my
Firebase bill. I try to avoid this!
Question
What do you think about this, is there a better solution for it? I am not sure if this is really it.
I also use Microsoft as a provider to authorize the user instead of creating a new account. Will this also work with those thate sign in via Microsoft?

I was trying to do something similar in my app. The problem you will find is the limit on the size of custom claims, 1000 bytes, which can be consumed pretty quickly.
One solution is creating a helper function in Firestore rules to check requests against the information you have stored for each user in the permissions subcollection. Of course, you will also have to restrict write access to those subcollections.

Related

Firebase/Firestore users access and roles how it works?

I'm trying to figure out how the role system works with Firebase but I'm extremely confused and don't understand how it all works.
I think I understood that to give a role to a user you have to use either Firebase Admin SDK or the security rules on Firestore.
However I don't understand the difference and why one would be more efficient than the other and how it works.
For example:
I have a delivery application with only 2 roles: delivery person and admin
The deliverers have a mobile application that indicates the address where the package should be delivered.
The admin has access to the same interface as the delivery person on the mobile application but has access to a web application to manage the deliveries. He can add a delivery, a driver etc...
How does Firebase authentication work and how to use role assignment correctly?
Is making a call to the "user" database to know if he is admin or normal user as soon as he logs in a good way to do ?
The answer to this is a little large so I can't provide code snippets for it. But I can point to a example project that hase an "atomic" role system. You can check it out here.
The basics. You can save the roles of a user in the databes (RTDB or Firestore). If you need both databases sync them between both to have single system.
The main point is that each user can only read his own roles and if he is an admin.
The changes to that data should only work using the firebase cloud functions.
To check if a user is admin or has a role make a simple "exists" check for the authenticated user and if the corresponding role or admin status exists for him.
In firestore you can even have helper functions like those here:
//Checks if user is signed in
function isSignedIn() {
return request.auth.uid != null;
}
//Checks if user has admin rights
function isAdmin() {
return exists(/databases/$(database)/documents/admins/$(request.auth.uid))
}
//Checks if user has a specific grant
function hasGrant(grant) {
return get(/databases/$(database)/documents/user_grants/$(request.auth.uid)).data[grant]==true
}
//Checks if user is granted either as admin or with a grant
function isGranted(grant){
return isAdmin() || hasGrant(grant);
}
//Checks if user has specific UID
function isOwner(userUid){
return request.auth.uid == userUid
}
Some would say to use the customClaims but I would never recommend that. Maybe only for the admin status and not more. The have a to small size to have any larger roles system on them. By storing the roles into the databases you can have them changed even in realtime.
To get the roles and is admin status for a user just make a realtime listener for that data.

User conflict when using same Auth method for Admin and Normal users | Firebase Auth

I'm working on Admin module of an android application. Admin module is a web-app based on Angular. Firebase auth(Email/password) is used to sign-In as a admin. I've added a manual credential entry to firebase and admin is using these credentials to login (Since there is no registration functionality for admin)
on other side Android developer has also used the same Auth method to sign in a user. So users of android application are able to login with their credentials to Admin module.
How do I prevent android users from login to web-app. Is there any method or rule that I can use to filter the incoming login request and allow login only if email belongs to Admin ?
Firebase has no knowledge of what an "Admin" is here. That's a concept that is specific to your app, so you will have to enforce it.
There's no way to allow certain users to only sign in on a specific platform. This is because Firebase makes a clear split between authentication (the user proves who they are) and authorization (the user has access to a resource). You use Firebase Authentication for authenticating the users, but will the "who can use what app" is an authorization problem, so it is handled elsewhere.
If you're using Realtime Database, Cloud Firestore, or Cloud Storage through Firebase, you'll for example typically enforce our authorization logic in Firebase's server-side security rules. Since these are automatically enforced on the server, there's no way for a user to bypass them, and they apply equally no matter what platform the user is on.
For example, a common first security rule that I start my Firestore projects with is:
service cloud.firestore {
match /databases/{database}/documents {
match /chat/{document} {
allow read;
allow write:
if isAdmin()
}
function isAdmin() {
return false;
}
}
}
This allows anyone to read the data, and no-one to write it, since isAdmin always returns false. With these rules the only way I can write data is by using an Admin SDK, since code using this SDK runs with elevated privileges and bypasses the security rules. A perfect way to get started, and safely populate my database with initial data from Node.js scripts (in my most common case).
Then at some point I do as you did, and add an application administrator. At that point I add their UID to the security rules:
function isAdmin() {
return request.auth.uid == "KqEizsqUQ6XMkUgLUpNxFnzLm4F3"
|| request.auth.uid == "zYXmog8ySOVRrSGVt9FHFr4wJb92";
}
So the above function in my rules now gives two specific Firebase Authentication users write access to the data.
This approach works well for the first few users, but at some point adding UIDs to the rules gets tedious and error prone. At that point I have two main options:
Store the UIDs of the application administrators in the database.
Identify application administrators in another way.
For storing the UIDs in the database you'd typically either add those UIDs to the database manually, or allow administrators to identify other administrators, and write their UIDs from the app. Either way, the security rules for this are something like:
function isAdmin() {
return request.auth.uid == "KqEizsqUQ6XMkUgLUpNxFnzLm4F3"
|| request.auth.uid == "zYXmog8ySOVRrSGVt9FHFr4wJb92"
|| exists(/databases/$(database)/documents/admins/$(request.auth.uid))
;
}
So the last line now also recognizes any authentication user whose UID is store in the admins collection as an application administrator.
Finally, say that I want everyone from my company to be an application administrator; I'd do that with:
function isAdmin() {
return request.auth.uid == "KqEizsqUQ6XMkUgLUpNxFnzLm4F3"
|| request.auth.uid == "zYXmog8ySOVRrSGVt9FHFr4wJb92"
|| (request.auth.token.email_verified && request.auth.token.email.matches(".*#google.com"))
|| exists(/databases/$(database)/documents/admins/$(request.auth.uid))
;
}
So this means that any Firebase Authentication user who has a verified #google.com email address is now an also application administrator.
As you can see, I build these rules up in multiple steps, starting with simply identifying that I will have application administrators who have specific permissions and creating the isAdmin function.

Firebase Auth, only activate account created after someone accept it

I currently have an website that uses firebase auth as my authentication. My problem is that I don't want to open registration to everyone, only a selected few people that are supposed to use the website. I read about .htaccess and .htpasswd but since I'm hosting on firebase hosting I don't think it's possible.
My question is how can I secure the account creation? I don't want to create the accounts manually at firebase console but have the users create it on a page. Is it possible to have the account work only after someone "accept" it at the firebase console or add an extra step after creation, can I somehow protect the registration page if using firebase hosting?
There is no way to prevent any user from creating an account after you enable Firebase Authentication. But the fact that they can create an account, does not necessarily mean that they can then use your application.
The typical approach for your use-case is to store a list of approved users somewhere. Since you're using Firebase Authentication, this would take the form of a list of UIDs.
So to be authorized to use your application a user needs to be authenticated, and approved. Firebase Authentication takes care of them being authenticated, and your back-end functionality should take care of checking their approval status.
For example, if you're using Cloud Firestore as your database, you'd store the approved user UIDs in a collection:
approvedUsers <collection>
UID1 <document>
UID2 <document>
And then you can check in the server-side security rules of your database that the user is authorized (authenticated and approved) before allowing them to (for example) read any data.
exists(/databases/$(database)/documents/$(document.reference))
service cloud.firestore {
match /databases/{database}/documents {
match /myCollection/{document} {
function isApproved() {
return exists(/databases/$(database)/documents/approvedUsers/$(request.auth.uid))
}
allow read, write: if isApproved();
}
}
}

Firebase Web Authentication - Administrator Approval for New Accounts

I've got the Firebase Web Authentication pretty much setup and working for oAuth as well as local username/pwds (email addresses).
My question is: Does anyone have an idea as to how to introduce an additional step in there such that new accounts must be approved by a site administrator prior to being fully validated? I was thinking of tweaking/utilizing the user.emailVerified property but I'm thinking that won't work for oAuth users.
Is there an easy way to do this - to add an admin approval step? Or, is there a property in the Firebase Authentication subsystem that I could easily toggle?
Creating a user via Firebase Authentication only provides them with a unique user id. This doesn't allow them any access to your apps or "register" them in any way. That's entirely your purview. It's nothing more than a map of unique credentials (e.g. Facebook IDs or email/password hashes) to unique Firebase IDs.
You can "register" users by having any access privileges you want, and any workflow to get the user added into your Database (or any other appropriate mechanism).
Assuming database, you would write the user profile/meta data into a path, such as /users/$uid, and base your security rules on whether /users/<user id> exists.
To enforce admin approval, the simplest answer would be to maintain a separate path, such as /registered/<user id>/true that's only accessible by admins (and of course by security rules).
Now you can write rules like the following:
{
"...some path...": {
".read": "root.child("registered/" + auth.uid).val() === true"
}
}
Essentially enforcing a registration process.

Firebase Simple Login - Prevent New Users

I am using the Simple Login Email / Password Authentication functionality of Firebase.
I would like to manage users through Forge only. I don't want users to be created via the client app.
However I would still like to let them login/logout though.
Is this possible?
You can't prevent users from being created on the client using simple login. There are two options you can utilize instead:
Simple Login "accounts" are really just tokens
Simple Login is just a convenience wrapper that creates Firebase tokens. There is no limit on how many accounts can be stored and they have no affect on your Firebase usage. With this in mind, there's really no reason you need to restrict creation of accounts.
Instead, just utilize security rules to control access to data. When an admin creates an account, have them also add a profile into the data. If only an admin in Forge is allowed to create the profile, then someone could create an account, but it would be superfluous and pointless, since all it does is give them an inert token.
A security rule to enforce access to data:
".write": "root.child('valid_account/'+auth.uid).exists()"
A security rule that allows users to edit their profile but only Forge (admin: true) to create them:
"profiles": {
"$uid": {
".write": "data.exists() && auth.uid === $uid && newData.exists()"
}
}
Creating your own tokens allows complete control
If you're terribly OCD and don't like that approach, then you can cut out Simple Login. As stated previously, it just creates tokens on your behalf. So simply create your own.
In this way you have complete control over account creation and token generation.

Resources