firebase auth is it possible to restrict login to some users? - firebase

currently I have implemented a login function with firestore.
btnLogin.addEventListener('click', e => {
// Get email and password
const email = txtEmail.value;
const pass = txtPassword.value;
const auth = firebase.auth();
// Sign in
const promise = auth.signInWithEmailAndPassword(email, pass);
promise.catch(e => console.log(e.message));
})
I am wondering if it's possible to restrict the login to only a particular email, the only way I am thinking is manually checking for the email before authenticating - but wondering if there are any official ways of doing this. Even extended for more than one email.
My temporary solution (?)
btnLogin.addEventListener('click', e => {
// Get email and password
const email = txtEmail.value;
const pass = txtPassword.value;
const auth = firebase.auth();
// Sign in
if (email == "email_desired" | email == "email_desired2"){
const promise = auth.signInWithEmailAndPassword(email, pass);
promise.catch(e => console.log(e.message));
}
else{
console.log("Email is not accepted at login")
}
})
The purpose I am doing this is to only allow fixed users to access the admin login-panel.

In ur app, dont give option to create new user.
Only keep ur app limted to Login with email and password.
You cam manually create users on firebase web console.
This way only users u created will be able to login.

If you have email/password sign-in enabled, you can't stop people from trying to create accounts and signing in. What you've suggested in your question isn't really "security" since client code can be compromised and bypassed. However, you can use security rules to determine who can actually access your database. Since you tagged this google-cloud-firestore, I'll assume that's what you're using.
Firestore has security rules that you can use to determine who has access to what data. For an admin panel where you trust a certain set of accounts under your control (that you can create manually yourself), you can simply whitelist them in your rules. Assuming that you have a list of UIDs, the easiest thing to do, if you trust them all with any actions in the database, is simply give them all full access:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid in ['uid1', 'uid2', 'uid3'];
}
}
}
You might want something more sophisticated, in which case, you will really have to learn the security rules system and make the best use of it. Or, you can route all user actions through a backend you control, and verify the user's Firebase Auth ID token using the Firebase Admin SDK before committing the action.

You can block those users in firebase console by disable those accounts in Authentication Section.

Elaborating a bit on Doug's answer above, I've found something like this helpful:
Create a collection (assuming Firestore) called cms_users or similar where you will keep the users that should be able to access the application. I also store more granular permissions on the table.
Create a Firestore rule function that will get whether or not the requesting user appears in that table, e.g.
function getCMSUser() { return get(/databases/$(database)/documents/cms_users/$(request.auth.uid)); }
Create a rule that ensures that the collection is only readable by users that are in that collection:
match /phoenixcms_users/{userId} { allow read: if getCMSUser() != null; }
And now, for example, if you have data that you want to restrict read/writes to only those users, something like:
match /cms_secret_collection/{document=**} { allow read: if getCMSUser() != null; allow write: if getCMSUser() != null && getCMSUser().data.permissionLevel in ["owner", "admin"]; }
In the above example, you can see that all 'CMS' users can view the data, but only certain CMS users with owner or admin permission can write the data.

Related

Firebase rules not working with next ssr serversideprops

In my NextJS serversideprops, I am trying to pre fetch data from a firebase collection which has the rule that the user must be logged in (allow read, write: if request.auth.uid != null;)
I do save and get the user cookies using nookies and I am also able to verify the users token via
const { token } = nookies.get(context);
const user = await admin.auth().verifyIdToken(token);
This does return me the user data, however even though it returns me the data, I do not think the user is logged in in the server, hence why the firestore rule blocks it. How do I get around this?

Set custom claims & role-based access for specific Google sign-in account

My firebase functions app makes use of Firebase & Google for authentication, checking for valid domains to grant access.
My concern is that a rogue employee can come along and destroy sensitive data. Thus, I wish to set custom claims for each account access the site from "admin" user of sorts.
There is already 2 questions (here and here) relating to this, being able to set claims in the Firebase console but this isn't yet a feature
My question has 2 parts:
How can I determine who my "admin" user is (I know the email, how do I set this user as the admin user)
How can this admin user set permissions for these accounts.
Possible solution:
The only solution I can think of is, upon for login, the account is saved in firestore e.g. /portal-users/${email}/mode
Modes:
none: on initial registration, the "admin" user needs to accept this person as a valid portal user
user: valid system user
revoked: revoked email, cannot access system
admin: well, admin I guess
My implementation using Firebase Functions & express follows this flow:
When signing in to POST:/user/createSession, I read the Firestore database and set custom claims using this as a base:
// if you are wondering what happened to CSRF - I couldn't get that to work :(
const idToken = req.body.idToken.toString();
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const auth = admin.auth();
auth.verifyIdToken(idToken).then(value => {
console.log("Token verified")
auth.setCustomUserClaims(value.uid, {mode: `insert mode here from firestore`}).then(value1 => {
return auth.createSessionCookie(idToken, {expiresIn})
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
})
}).catch(reason => {
console.error("Unable to verify token");
console.error(reason);
res.status(401).send('INVALID TOKEN!');
});
When requesting a auth /any/route/with/private/data route, the following check should be done (roughly) this way when validating a request:
admin
.auth()
.verifySessionCookie(sessionCookie, true)
.then((decodedClaims) => {
// Where the magic happens
switch (decodedClaims.mode) {
case "none": {
// return to login screen with awaiting verification message
break;
}
case "revoked": {
// return to login screen with revoked permissions message
break;
}
case "user":
case "admin":{
// continue
break;
}
}
res.status(401).send('UNAUTHORIZED REQUEST!');
})
.catch((error) => {
// Session cookie is unavailable or invalid. Force user to login.
res.redirect('/login');
});
To manage users' permissions, the admin user has a special page to set modes for each user on the system (which will eventually call setCustomClaims(), see above).
Are there any issues or security problems I might face? (except the CSRF issue ofc)
A few things to be aware of. Custom claims are cached from the client, these can lead to valid tokens with expired information making false-positive modifications to your database. This is an inherit flaw with Firebase's Auth system being restful by design, to get around this you must revoke the users' Auth token when their custom claims have changed, there are several ways to handle this:
Propagate: https://firebase.google.com/docs/auth/admin/custom-claims#propagate_custom_claims_to_the_client
Revoke: https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens
The main issue is that the user will have to re-authenticate to update these changes and should only be used on extreme account-wide conditions, such as setting administrator privileges, etc. In contrast, Cloud Functions have the luxury of validating the user object and accessing auth directly for the updated claims but these are not reflected in Security Rules. (this would be my dream fix)
Since Firestore and Realtime do suffer from cached claims, it's often more viable to have these flags set on the user based on a database profile or 'role' table with their UID associated with the correct Role. doing multiple reads and writes is also helpful as these reads from within Firestore are Cached per request, up to a maximum of 10 unique documents.
Source: https://firebase.google.com/docs/firestore/security/rules-conditions#access_other_documents
Custom Claims currently is not a viable solution to Secure your database, only validation on Cloud Functions and user flags on the client. Using a combination of the above methods and Database roles is the best solution.
Finding the user
You can setup a onUser create trigger within cloud functions that checks the user's credentials and confirms it with your backend, ie: is the user auth'd from "google.com" and has a specific prefix, etc.
or if the user is already within your Firebase Auth collection, you can target them by UID or email - Email is not as secure compared to the user's UID, as this does not validate the auth provider.
One popular solution is a Cloud Function Extention that adds claims based on Firestore document changes
Source:
Cloud Function Extention: https://github.com/FirebaseExtended/experimental-extensions/blob/next/firestore-auth-claims/README.md
Custom Cloud Function: https://github.com/digimbyte/FirebaseDevelopers/blob/master/Cloud%20Functions/Firestore%20Admin%20Manager.md

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.

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;

Does using Firebase Anonymous authentication allow me to secure my Firestore data and Cloud functions to only be accessed by my application?

I'm creating a mobile app that does not have any reason for the users to authenticate. However I don't want other people to write apps or websites that can access my data in Firestore, or call any of my Cloud Functions.
Does this mean I need to implement Anonymous Authentication and then write access rules that require the request to come from an authenticated user?
Or is there a way to write the rules to just say they must come from my application?
We had the same problem: an app for deployment to many people where it needs to be able to only read from firestore documents, but another admin app (actually just a web page) that will not be distributed, that needs to be able to write to those documents.
Our solution is to:
create another user for the database, with email and password authentication. (We are not allowing users to create accounts -- just having the one static email/password we've created.)
use only anonymous login for the regular app
have email/password signin for the "admin app".
add rules like these for each document:
allow read;
allow write: if request.auth.uid == 'notshowingyououridhere-sorry';
We are using ionic with typescript, so the code to do the user/password login is relatively simple:
firebase.initializeApp(credentials);
firebase.auth()
.signInWithEmailAndPassword('obfuscated#sorry.com', 'sorrynotshowingyouourpassword')
.catch(err => {
console.log('Something went wrong:', err.message);
});
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in.
const isAnonymous = user.isAnonymous;
const uid = user.uid;
console.log('onAuthStatChanged: isAnon', isAnonymous);
console.log('userid', user.uid);
} else {
console.log('onAuthStateChanged: else part...');
}
});
Hope this helps.

Resources