How do I lock down Firebase Database to any user from a specific (email) domain?
Asked Answered
D

7

36

I have a small, personal Firebase webapp that uses Firebase Database. I want to secure (lock down) this app to any user from a single, specific domain. I want to authenticate with Google. I'm not clear how to configure the rules to say "only users from a single, specific domain (say @foobar.com) can read and write to this database".

(Part of the issue that I see: it's hard to bootstrap a Database with enough info to make this use case work. I need to know the user's email at the time of authentication, but auth object doesn't contain email. It seems to be a chicken-egg problem, because I need to write Firebase rules that refer to data in the Database, but that data doesn't exist yet because my user can't write to the database.)

If auth had email, then I could write the rules easily.

Thanks in advance!

Demonize answered 29/4, 2016 at 16:29 Comment(15)
question: when you say lock down from a single domain, does that mean lock down to a single provider, like google or does it literally mean to only allows users from myDomain to access your Firebase i.e. test@myDomain authenticated by password is ok, but thing@anotherDomain authenticated via google is not allowed.Avina
I mean, "anyone from @foobar.com, as validated/authenticated by Google, can read and write the database"Demonize
Do you actually need a rule? At some point the user must enter their email address to be able to log in or have their account created in the first place so can't you just parse that at the time it's entered and reject any that doesn't have @foobar.com as the domain?Avina
Unfortunately, a determined user can use their browser's DevTools to insert any email they want. The email address isn't trusted data from the client.Demonize
Even with DevTools, you can prevent any reading/writing to your Firebase. If the user doesn't have access to your Firebase dashboard, and if they are not authenticated, a simple rule totally locks your Firebase from being accessed. Only allow writes if auth is not nil. Your front end UI can simply deny access from any emails other than the one you want. If it matches the one you want, allow the user to be created in firebase so they can login via that email in the future. Just a thought!Avina
Thanks, but in my use case, I don't know all the authorized emails ahead of time. I only know I want to allow everyone from a single specific domain.Demonize
I think you misunderstood. My suggestion is to parse the email address the user enters for the string '@mydomain.com'. That can be done up front in the UI and doesn't involve Firebae at all. Block all emails that do not end in @mydomain.com domain, and allow those that do. Simple solution and you can determine if the email has a valid domain in one line of code - maybe 2.Avina
The problem is, I don't trust the web browser because dev tools can easily allow a malicious user to fake the data returned by the oauth provider.Demonize
If you are using oauth but don't trust it (or the browser) because it can be faked, why use it to begin with? If the user is sitting at a browser, goes to the website and logs in, [email protected], what's going to prevent the return data from the oauth provider from being faked? In other words, if you are using Google for oauth, anyone can create a google account mydomain.com. How would your app know if that's valid or not? Seems like you may want to roll your own server (code) per @FrankvanPuffelen comment to your answer.Avina
Correct, I will need to run my own server in order to trust the email returned from the OAuth flow.Demonize
@Avina what makes you say "Your front end UI can simply deny access from any emails other than the one you want." Users can change any element of the front end UI. So if we say "only permit email addresses from cats.com host address" users can change that rule. I don't see any option but validating the user's host address server side, as FrankvnPuffelen's answer permits...Crackbrain
@Crackbrain Users don't have access to your code and cannot change the UI you created so if you're code doesn't accept any email with cats.com in it, they cannot use that email. Front end UI means the user interface of your app the user interacts with that you created in code.Avina
@Avina of course users can change any element of a UI. Open your console and run some javascript and anything can be changed. Events can be bound, unbound, new scripts can be loaded.... Users can't manipulate your server-side code unless they really hack you, which is what makes Frank's answer secure...Crackbrain
@Crackbrain lol. No, users cannot change any element of a UI. If I create an iPhone app, submit it to the App store for distribution and a user downloads the app. They won't be able to change a UI of the app. You missed the of your app part. Assuming it's a mobile app. On the other hand if we are javascripting in console then of course it can be. Franks answer is totally on point which is why i referenced it in an above comment and upvoted it two years ago. Note that the OP didn't have the email addresses beforehand which adds some complexity.Avina
Ah, well there we have it--I believe Jay and myself are speaking of web apps! Very well!Crackbrain
Z
60

If you're using the new Firebase this is now possible, since the email is available in the security rules.

In the security rules you can access both the email address and whether it is verified, which makes some great use-cases possible. With these rules for example only an authenticated, verified gmail user can write their profile:

{
  "rules": {
    ".read": "auth != null",
    "gmailUsers": {
      "$uid": {
        ".write": "auth.token.email_verified == true && 
                   auth.token.email.matches(/.*@gmail.com$/)"
      }
    }
  }
}

You can enter these rules in the Firebase Database console of your project.

Zabaglione answered 24/6, 2016 at 18:23 Comment(7)
firebase.google.com/docs/reference/security/database/#authtoken holds the documentation what is available in the auth.token.Wiese
Would be cool to see an example of this that pulled @gmail.com from a root in the database, for example my_database/acceptable_domains/ in case for example, they only wanted to open their application up to a handful of email providers. Such as authorized schools and businesses.Cornute
Is there any alternative that doesn't require hard-coding each domain - like "gmailUsers"?Shanley
how to restrict gmail domain that only specific domain user can register. Like my company abc is using gmail mail services so any user [email protected] can register, I'm using the gmail OAuth firestore for android.Marrakech
You cannot currently limit what users can sign in with Firebase Authentication based on their domain. You can limit what data they can access based on their domain, with the security rules I gave above.Zabaglione
is there anything similar for firestore ?Hash
The same claims are available in Firestore security rules. It's just the syntax that is different.Zabaglione
F
3

Here is code working fine with my database , I have set rule that only my company emails can read and write data of my firebase database .

{
  "rules": {
    ".read": "auth.token.email.matches(/.*@yourcompany.com$/)",


        ".write": "auth.token.email.matches(/.*@yourcompany.com$/)"
      }
    }
Fellers answered 4/9, 2017 at 10:16 Comment(0)
P
3

Code which is working for me.

export class AuthenticationService {

    user: Observable<firebase.User>;

    constructor(public afAuth: AngularFireAuth) {
        this.user = afAuth.authState;
    }

    login(){
        var provider = new firebase.auth.GoogleAuthProvider();
        provider.setCustomParameters({'hd': '<your domain>'});
        this.afAuth.auth.signInWithPopup(provider)
        .then(response => {
            let token = response.credential.accessToken;
            //Your code. Token is now available.
        })
    }
}
Promptitude answered 11/9, 2017 at 20:24 Comment(0)
D
2

WARNING: do not trust this answer. Just here for discussion.

tldr: I don't think it's possible, without running your own server.

Here's my attempt thus far:

{
  "rules": {
    ".read": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('@foobar.com')",
    ".write": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('@foobar.com')",
    "users": {
      "$user_id": {
        ".write": "auth.provider === 'google' && $user_id === auth.uid && newData.child('email').val().endsWith('@foobar.com')"
      }
    }
  }
}

I believe the above says "only allow people to create a new user if they are authenticated by Google, are trying to write into the database node for themselve ($user_id === auth.uid) and their email ends in foobar.com".

However, a problem was pointed out: any web client can easily change their email (using the dev console) before the message is sent to Firebase. So we can't trust the user entry's data when stored into Firebase.

I think the only thing we can actually trust is the auth object in the rules. That auth object is populated by Firebase's backend. And, unfortunately, the auth object does not include the email address.

For the record, I am inserting my user this way:

function authDataCallback(authData) {
  if (authData) {
    console.log("User " + authData.uid + " is logged in with " + authData.provider + " and has displayName " + authData.google.displayName);
    // save the user's profile into the database so we can list users,
    // use them in Security and Firebase Rules, and show profiles
    ref.child("users").child(authData.uid).set({
      provider: authData.provider,
      name: getName(authData),
      email: authData.google.email
    });

As you might be able to imagine, a determined user could overwrite the value of email here (by using the DevTools, for examples).

Demonize answered 29/4, 2016 at 18:29 Comment(1)
As you indeed discovered: .write rules can control who can write to a location and .validate rules can validate that what they write is a valid email address. But there's no way to verify in security rules that it is actually that user's email address. For that you'll need to run some trusted code (typically on a server) that will write this verified email address to a (otherwise read-only) location in the database.Zabaglione
P
2

This should work for anyone looking for a Cloud Firestore option, inspired by Frank van Puffelen's answer.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
    // Allows all users to access data if they're signed into the app with an email of the domain "company.com"
      allow read, write: if request.auth.uid != null && request.auth.token.email.matches(".*@company.com$");
    }
  }
}
Picture answered 8/6, 2020 at 20:39 Comment(0)
I
0

For anyone really not wanting to have unverified accounts logging in. Maybe dirty, but very effective.

This is my workaround (Angular app):

this.userService.login(this.email.value, this.password.value).then(data => {
  if (data.user.emailVerified === true) {
    //user is allowed
  } else {
    //user not allowed, log them out immediatly
    this.userService.logout();
  }
}).catch(error => console.log(error));
Introspect answered 13/6, 2019 at 20:26 Comment(0)
Q
0

You can Extend Firebase Authentication with blocking functions.

For example:

const identity_1 = require("firebase-functions/v2/identity");
const logger = require("firebase-functions/logger");
    
/**
 * Block account creation for all but @foobar.com
 */
exports.blocking = (0, identity_1.beforeUserCreated)((event) => {
    const user = event.data;
    if (!user.email || !user.email.endsWith("@foobar.com")) {
        logger.info("ACCOUNT CREATION BLOCKED", { email: user.email });
        throw new identity_1.HttpsError("invalid-argument", "Unauthorized email");
    }
    else {
        logger.info("ACCOUNT CREATED", { email: user.email });
    }
});
Qualitative answered 19/6, 2024 at 15:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.