35

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!

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Seth Ladd
  • 112,095
  • 66
  • 196
  • 279
  • 1
    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 Apr 29 '16 at 17:43
  • I mean, "anyone from @foobar.com, as validated/authenticated by Google, can read and write the database" – Seth Ladd Apr 29 '16 at 17:50
  • 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 Apr 30 '16 at 14:04
  • 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 May 02 '16 at 04:17
  • 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! – Jay May 02 '16 at 17:35
  • 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. – Seth Ladd May 07 '16 at 02:50
  • 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. – Jay May 07 '16 at 12:14
  • 1
    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 May 08 '16 at 13:54
  • 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, person@mydomain.com, 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. – Jay May 08 '16 at 14:33
  • Correct, I will need to run my own server in order to trust the email returned from the OAuth flow. – Seth Ladd May 14 '16 at 05:00
  • @Jay 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... – duhaime Jun 20 '18 at 23:58
  • @duhaime 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. – Jay Jun 21 '18 at 09:09
  • @Jay 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... – duhaime Jun 21 '18 at 13:35
  • @duhaime 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. – Jay Jun 21 '18 at 14:15
  • Ah, well there we have it--I believe Jay and myself are speaking of web apps! Very well! – duhaime Jun 21 '18 at 14:35

6 Answers6

59

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.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 3
    https://firebase.google.com/docs/reference/security/database/#authtoken holds the documentation what is available in the `auth.token`. – Tim Roes Aug 14 '16 at 08:23
  • 1
    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. – Hobbyist Aug 24 '16 at 06:11
  • Is there any alternative that doesn't require hard-coding each domain - like "gmailUsers"? – skunkwerk Jun 20 '17 at 20:01
  • how to restrict gmail domain that only specific domain user can register. Like my company `abc` is using gmail mail services so any user `good@abc.com` can register, I'm using the gmail OAuth firestore for android. – Asif Mushtaq Apr 23 '18 at 17:23
  • 1
    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. – Frank van Puffelen Apr 23 '18 at 18:53
  • 1
    is there anything similar for firestore ? – jsaddwater Aug 14 '18 at 17:17
  • The same claims are available in Firestore security rules. It's just the syntax that is different. – Frank van Puffelen Aug 14 '18 at 21:53
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$/)"
      }
    }
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.
        })
    }
}
Shirish
  • 77
  • 1
  • 1
  • 5
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).

Seth Ladd
  • 112,095
  • 66
  • 196
  • 279
  • 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. – Frank van Puffelen Apr 30 '16 at 03:35
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$");
    }
  }
}
bearacuda13
  • 1,779
  • 3
  • 24
  • 32
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));
Michelangelo
  • 5,888
  • 5
  • 31
  • 50