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!
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.
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$/)"
}
}
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.
})
}
}
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).
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$");
}
}
}
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));
Related
I used this protection rules in the database like this
{
"rules": {
"users": {
"$userId": {
".read": "$userId === auth.uid",
".write": "$userId === auth.uid"
}
}
}
}
In postman I tried GET with url.json but I get permission denied I tried to search the documentations how to find and pass current UID but could not find
Also for authentication with email and password I select in the postman Authorization "Basic Auth" and I went to firebase Project settings then I add member then I add email and select role "Firebase Admin" but where I get this user entered password to save it in postman ?
If you want to access the user's data via the REST API, you'll have to make sure the calls are authenticated as shown in the documentation.
The options for this are to add a parameter to the request:
either pass the OAuth2 token of a collaborator on the project in an access_token parameter,
or pass the ID token of the correct Firebase Authentication user in an auth parameter.
There is no way to pass just the UID of the user, as that would not be secure - as anyone could pass any value for as their UID in that case.
Trying to put my question in its general concept so that your reply will stay relevant to all the ones who are stuck firebase rules for a chat app.
My chat database is a simple one and has 3 nodes: Messages / Connections / Users as below:
Database
-Messages
--message :
--$uid
...
-Connections
--Connection1
--Connection2
--ConnectionX
...
-Users
--UID1
--UID2
Here's what I need to proofread pls for my realtime database rules:
{
"rules": {
"users": {
"$uid": {
".read": "auth != null" && "$uid/current_page" == "3w_stringofmywebsite/*",
".write": "auth != null" && "$uid/current_page" == "3w_stringofmywebsite/*"
}
// .read & .write to authenticated users & variable $uid/current_page= "3w_stringofmywebsite/*" (allow access from requests from all pages from my web site only)
}
"connections": {
"$uid": {
".read": "auth != null" && root.child(‘users’).child($uid).child(“current_page ") == "
3 w_stringofmywebsite/*"
}
// .read to authenticated users & variable from another path “users/$uid/current_page” = "3w_stringofmywebsite/*" (allow access from from all pages from my web site only)
}
"messages": {
".read" : "auth != null" && root.child(‘users’).child($uid).child(“current_page") == "3w_stringofmywebsite/*" ,
".validate": "auth!=null" && data.child('msg').val ().length <= 140 && data.child('msg').isString"
}
// .read to authenticated users & variable from another path “users/$uid/current_page” = "3w_stringofmywebsite/*" (allow access from all pages from my web site only)
// .write to authenticated users & variable “msg” is a string & < 140 characters
}
Thank you for your generous correction to the code above so that it serves all other newcomers.
Thank you dear Frank,
the syntax of commands has many errors (tried many times to do the corrections but the list is long) and I don't want to fill the ticket here with beginners mistakes.
=> what I need is someone who masters the syntax and relieve me from bumping my head at each step in each command!
*For the "users" node:
Read: right to only authenticated connections and the ones that are coming from my website only (as the chat tool is on this website)
Write: right similar to above.
For the "connections" node:
Read to authenticated users & the connections that are coming from my website [there's a variable "current_page" from another path “users/ "ABC"/current_page” = "3w_stringofmywebsite/" that I can use to match the string of my website as a test
NB: "ABC" above is a string auto generated by the node and I don't know how I can point to the variable "current_page" below it since it keeps varying from one node entry to another!
*For the "messages" node:
Read: privilege to authenticated and again check if the variable “users/ "ABC"/current_page” is from my web site
Write: privilege to authenticated and the message is a string < 140 characters.
Appreciate your support in this.
Thank you.
I'm a bit confused about firebase rules. This is my realtime database. Each node inside "1" is created using the firebase unique id of the user. And in the user's node there is a list of objects.
The objective is for the user to be able to create this node if it doesn't exist, and allow the user to read/write only inside this node.
I tried this but it doesn't work. I get permission error.
{
"rules": {
"1": {
"$key": {
".read": "auth != null && auth.uid == $key",
".write": "auth != null && auth.uid == $key"
}
}
}
}
Note: In the future there will be other parent nodes ("2","3" etc) So it is important to keep the "1". Also in case it matters I am using firebase anonymous sign in.
I appreciate the help.
UPDATE:
I retrieve the installationId like this:
Task<String> getIdTask = FirebaseInstallations.getInstance().getId()
and access the database like this:
FirebaseDatabase.getInstance()
.getReference()
.child("1")
.child(installationId)
Trying to access the database using above code gives this:
Listen at /1/cKYZwWrlRmSof79rtfuX82 failed: DatabaseError: Permission denied
SOLUTION:
I just realized the magnitude of my mistake. To retrieve the userId I was using
FirebaseInstallations.getInstance().getId()
instead of this which is what firebase sees as userId:
FirebaseAuth.getInstance().getCurrentUser().getUid();
Using the later one solved the issue.
Unless you'd add another node, that actually links the UID with your user ID, how shall it know about it? I'd suggest to reconsider the structure and get rid of that superfluous node altogether; just use the UID. It's not that it wouldn't be possible, to lookup values by UID ... but it might be an unfortunate database design, which ignores the given environment.
We receive Firebase warning emails about our realtime database having insecure rules. Which is true, but I am struggling with how to change them. Our application is about 2 apps with a Chat functionality between a passenger and a driver.
This is what we store in the database:
Currently we have:
{
"rules": {
".read": true,
".write": true
}
}
Access should be granted if driverId / passengerId are supplied in the database read or write request.
Help is much appreciated.
I have only basic info about Firebase rules. I hope my below rules can help or atleast give you some idea about how to approach your situation.
So basically for read and write requests, we'll check if your data has the field driverId of passengerId or not:
{
"rules": {
"chats": {
".read": data.hasChildren([‘driverId’, ‘passengerId’]) === "true"
".write": newData.hasChildren([‘driverId’, ‘passengerId’]) === "true"
}
}
}
Here, newData corresponds to the incoming data and data corresponds to the data already and curently residing in the database.
More help can be found here and here in Firebase's official documentation for security rules.
Your rules allow anyone to read & write anything anywhere. You should use authentication (to be able to identify users by their UID in the rules), structure you db so that information with different access logic is stored in different paths, then write rules to enforce your access logic.
I am getting confused about Firebase Security. Here why...
For example; I have the following database:
clients: {
$key: {
//client data
}
}
trainer: {
$key: {
//trainer data
}
}
I need a client to be able to see their own information. I need a trainer to be able to see the information of all their clients, but not other trainers.
So client A, B and C can see their personal, private data. But Trainer A can only see client A and B's details (he doesn't train C).
The problem I run into is that you can't seem to request for example all clients, but only return the ones that checkout with security rules. As the docs state, if one in the list returns false, the whole bunch does.
How can I create the correct structure and security rules?
You should impliment something like adding clients to trainer as friend. Create a node sharedwith which contains trainer as child and trainer will contains A & B clients key and their data .
Similar to examples in the Firebase docs (https://firebase.google.com/docs/database/security/user-security), this might work for you.
I think at a minimum your structure could benefit from having a direct key tie between client and trainer. Something like
clients: {
clientA: {
trainerKey: "trainerA"
}
}
trainers: {
trainerA: {
clients: { clientA: true, clientB: true }
}
}
Security Rules - edited to include user
"clients": {
".read": "auth !== null && (root.child('trainers/' + $trainerKey + '/clients').child($uid).exists() || auth.uid == $uid")
}
This a) checks that a user is authenticated and b) looks to see if the client is in the list of clients for the trainer OR whether this is the client.
This is untested, but hopefully gets you where you're trying to go. I'm also making the assumption that your clients IDs are the same as their auth ID.