Firebase/Firestore users access and roles how it works? - firebase

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.

Related

Setting Firebase Firestore to only allow read/write from a single Unity App

I've been looking for this answer for a while and honestly there isn't a lot of Firebase using Unity tutorials out that that gives a definitive answer.
I'm using Firestore as a database for an Unity app. My goal is to only allow that app to write to/read from that database. There is no user login, as the app has no use for it. Everything is working already as far as the reading and writing.
My first question is, do I even need Firebase Authentication for this? Or can the app only read/write if it has the associated GoogleServices.json (android) and PList (iOS) files in when it was built? I don't want unauthorized users to access my databse, and i'm unsure how secure just having the json and plist files are.
My second question is this. I have 2 data nodes in the database, one for reading and the other for writing to the general user of the app. I know how to set up the rules so that's the case. But I do want special execution, say I have a personal app that should be able to read the otherwise write-only node, and write to the otherwise read-only node.
Would I need Firebase Authentication then? I assume I have to make a read exception to use Authentication in the security rules.
Thanks in advance for your help.
Question1:
If you do not want unauthorized access to your database, you do need Firebase Auth. Luckily, Firebase Auth has a feature for people like you who don't need to have people log in to your app. It's called Anonymous Authentication, and it essentially works by you signing in users in the back ground without your app's user knowing it. With this, you can restrict database access so that a certain user would not be able to override someone else's data. You can read more about Anonymous Authentication with Unity here.
Question2:
I don't know if the situation you are describing like a admin case, where you as an admin of the app should be able to read/write to the database where normal users should not be allowed, but if that is the case you may use "Custom Claims". With custom claims, you are adding a special key-value pair to your auth token to then use it in security rules. You can read more about custom claims here.
thank you for the quick reply. I got the anonymous sign-in to work, and I think I set up the rules correctly.
I have a document in Firestore called 'organizations', and I want the any user that is signed-in to be able to read it and everything below it, but only write if he's user id is administrator. From the Firestore docs, I think I should set the rules like this:
match /databases/{database}/documents
{
match /organizations/{organization=**}
{
allow read: if request.auth != null;
allow write: if request.auth.uid == "administrator";
}
}
I have a function that runs at the beginning of the app that signs the user in anonymously, which I can confirm works since I can read the user when the task is completed.
However, something isn't working right in the rules.
The app can read the Firestore node whether or not I'm signed in or not. I can comment out the sign-in function, and it will still read the "organization" node. I'm not sure if signing in anonymously does anything at all. Maybe I'm signing in wrong?
Here is my sign-in function:
public async Task SignInAnon()
{
await FirestoreController.firebaseAuth.SignInAnonymouslyAsync().ContinueWith(task =>
{
if (task.IsCanceled)
{
Debug.LogError("SignInAnonymouslyAsync was canceled.");
return;
}
if (task.IsFaulted)
{
Debug.LogError("SignInAnonymouslyAsync encountered an error: " + task.Exception);
return;
}
GameManager.anonUser = task.Result;
Debug.Log("Firebase Signed-In as: " + GameManager.anonUser.UserId);
});
}
anonUser is a static variable initiated elsewhere in the code. Or can I not doing it this way?
EDIT: I'm an idiot. I signed in once and I didn't bother to sign the user out, so I'm treated as signed in even if I comment out the code. :P

Using Firebase Auth Custom Claims for custom permissions

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.

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();
}
}
}

Prevent user account creation with sign in by email in firestore

I have setup a passwordless login using firebase sign in with email link. Everything is working as expected, however when the user receives the email and clicks the link to login they are automatically created as a registered user.
https://firebase.google.com/docs/auth/web/email-link-auth
After a user signs in for the first time, a new user account is
created and linked to the credentials...
This means anyone who makes a request in the login screen will get an email and get access to the site.
I am not sure if there is any configuration or setup that i need to complete in order to require that the user requesting the signup link are only checked against the users that are registered.
Something like
firebase.auth().sendLoginLinkToEmail(email,{url:...,handleCodeInApp:true}).then(() =>{
....
}, error =>{
// return if not an authenticated user
})
And if the email is not registered then it returns an error.
The idea is to have an administrator that creates users and then those created users just login with an email link ( no password )
Is this possible? To prevent firebase from creating an account with.signInWithEmailLink() ?
Passwordless email sign in allows the user to prove they have access to a certain mail box. It does inherently do nothing more than that. Once the user clicks the link, they are authenticated. Beyond enabling/disabling the entire sign-in provider, you cannot control who can sign-in/authenticate.
After that it is up to your application to determine what this user is allowed to do. This is a separate step, typically called authorization.
Firebase Authentication takes (as its name implies) care of authentication only. You will have to handle authorization elsewhere, depending on what services you provide the users access to.
What makes an email "registered" in your app? I.e. where does the admin create those users? For example, if you store the users in the Cloud Firestore in a collection allowed_users, with documents like this:
allowed_users: // collection
"arkade#domain,com": { ... } // document
"puf#domain,com": { ... } // document
Now you can limit that only allowed users can access other data with Firestore's server-side security rules. Say you have a collection of posts, you can allow only these users to read posts with:
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{post} {
// Make sure a 'allowed_users' document exists for the requesting user before
// allowing any reads from the 'posts' collection
allow read: if exists(/databases/$(database)/documents/allowed_users/$(request.auth.email))
}
}
The syntax is a bit long, but your can see that is only allows the reading of a post if the current user's email address (request.auth.email) exists as a document in allowed_users.
In rules version 2 of the Firestore rules, you access the current user's email address a little differently. You can do it via request.auth.token.email. The example below also shows how you can get a boolean property in the current user's document, if you identify that user by email:
allow write: if get(/databases/$(database)/documents/users/$(request.auth.token.email)).data.admin == true;

Resources