33
votes

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!

6
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.Jay
I mean, "anyone from @foobar.com, as validated/authenticated by Google, can read and write the database"Seth Ladd
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?Jay
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.Seth Ladd
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.Seth Ladd

6 Answers

57
votes

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.

3
votes

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$/)"
      }
    }
2
votes

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).

2
votes

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.
        })
    }
}
2
votes

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$");
    }
  }
}
0
votes

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