I added Firebase to my website and created a signup form.
When the user clicks the "signup" button it creates a user using "Firebase Authentication", then it updates the user's displayName, and finally adds some data about the user to the database:
firebase.auth().createUserWithEmailAndPassword(email, password).then(function () {
profile = firebase.auth().currentUser;
profile.updateProfile({
displayName: "name-value"
}).then(function() {
// Update successful.
firebase.database().ref("/users/" + profile.uid).set({
data:{
country: "country-value",
city: "city-value"
}
});
});
}).catch(function(error) {
// Handle Errors here.
});
Everything worked fine until I changed the database rules from:
{
"rules": {
".read": true,
".write": true
}
}
}
To:
{
"rules": {
"users": {
".read": true,
"$user": {
".write": "auth.token.name !== null"
}
}
}
}
I used auth.token.name as it is written in the guide to check whether or not the user's displayName exists (I planned to make it check some more things about it later).
Somehow "firebase rules" 'thinks' that auth.token.name equals null, even though I set the displayName before. Why is it happening and what can I do to make it work?
Thanks in advance :)
First, Firestore has replaced Realtime Database and should be used for all projects going forward - more features.
Re Realtime DB rules, it's is best practice to check uid variable vs display name. See Variables:
{
"rules": {
".read": true,
"$comment": {
".write": "!data.exists() && newData.child('user_id').val() == auth.uid"
}
}
}
Finally, IIF you implement database security rules using Firebase Authentication, you MUST use .onAuthStateChanged to drive your application as your auth() variables WILL BE null for a second on initial page/app load as this is an asynchronous method. So, don't try and write or read anything on DOM ready. You must trigger db conversations within .onAuthStateChanged like below:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
//USER SIGNED IN. WE ARE READY
}
else {
//USER IS NOT SIGNED IN
}
});
I had the same problem and figured out a better solution. Firebase has a feature called custom claims that is meant for the use case we have. You want to set some id on a Firebase Anonymous Auth User that is accessible from database security rules.
The high-level of the solution is to make a Cloud Function that uses the admin api to set a custom claim. You can't set custom claims from the client side.
Next, call the cloud function to update the custom claim then use Auth.auth().currentUser?.getIDTokenResult(forcingRefresh: true,... in order to retrieve the updated token which includes the custom claim. This step is critical because you need your custom claim to be sent as part of every request moving forward.
If anyone actually reads this I can type up a more detailed implementation example.
Related
This question already has an answer here:
Firestore online rules simulator fails with custom claims
(1 answer)
Closed 1 year ago.
All the users for firebase project are authenticated using Phone provider.
I am setting custom claim for all users using C# as follows -
var claims = new Dictionary<string, object>()
{
{ "admin", true },
};
var pagedEnumerable = FirebaseAuth.DefaultInstance.ListUsersAsync(null);
var responses = pagedEnumerable.AsRawResponses().GetAsyncEnumerator();
while (responses.MoveNextAsync().Result)
{
ExportedUserRecords response = responses.Current;
foreach (ExportedUserRecord user in response.Users)
{
FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync(user.Uid, claims);
}
}
In the firebase realtime database I have following nodes -
{
"Configuration" : {
"Sync" : "XYZ"
},
"adminContent" : {
"key" : "val1"
}
}
I am trying to configure database access rules using custom claim as -
{
"rules": {
"adminContent": {
".read": "auth.token.admin === true",
".write": "auth.token.admin === true"
}
}
}
I am trying to use the rule playground to verify read access to adminContent node and getting error as -
The result pop up reads as -
Request -
Type read
Location /adminContent
Data null
Auth { "provider": "anonymous", "uid": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
Admin false
Response -
READ Denied
Line 5 (/adminContent)
read: "auth.token.admin === true"
Using C# code I have verified that admin custom claim exists on the user.
Will any one pls help to fix this error?
The rules playground in the Firebase console doesn't read any profile information of existing users. If you want to test whether the read is allowed when a user has the admin claim, you'll need to specify that claim in the rules playground by selecting the Custom provider, and then editing the Auth token payload to include it.
Also see: Firestore online rules simulator fails with custom claims, which I just found and will actually close your question as a duplicate against.
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));
I create an app with GatsbyJS (React) + Firebase.
Users have a score saved in Firebase Realtime Database with their data.
{
users: {
$uid: {
score: 10,
name: "Antho",
...
}
}
}
I do not want users to be able to change their score themselves, so I split my database like this:
{
users: {
$uid: {
name: "uciska",
...
}
},
scores: {
$uid: {
score: 10
}
}
}
But I do not know what rules to put in place.
I think I need some backend code. If so I would like to go through Amazon Lambda functions without having to set up a Node.js server.
Is it possible ? Where to start ?
Could Cloud Firestore do that more simply?
If you don't want your user to be able to write the scores, you can simple use these rules:
{
"rules": {
"users": {
"$uid": {
".write": "auth.uid == $uid"
}
}
"scores": {
".write": false // this is not strictly needed, since the default is false
}
}
}
With this, each user can write their own node under /users/$uid and no user can write any score.
Next, you'll want to use the Firebase Admin SDK in a trusted environment to write the scores. A trusted environment is something you control access to, such as a server, your development machine, or Cloud Functions. Code that runs in Cloud Functions, or otherwise uses the Admin SDK, has administrative privileges, which means it bypasses the security rules. So that code can read/write the entire database, including the scores.
I've a users database on firebase realtime database.
My structure is:
Users
-> uid
-> user_infos (in this level I've a property called: 'cpf')
So I created a .validate rule for cpf:
{
"rules": {
"users": {
"$user_id": {
"$cpf": {
".validate": "!data.exists()"
}
}
}
}
}
The problem is when I try to save user, I receive a PERMISSION_DENIED error.
I'm using firebase javascript lib, 3.4.1
the method that I use for save user is:
firebase.database().ref('users/' + uid).set(userDb);
My intention is that cpf property to be unique on database.
Thank you.
I fixed it, doing manually a verification to firebase. It's not so great solution, but worked.
I'm working on an app using Ionic Framework and Firebase. I have the following data structure on Firebase:
Users {
mary#fb,com: {
Group: {
group123: {
Contacts: {email1#gmail.com, email2#gmail.com, etc. }
}
group456: {
Contacts: {email3#gmail.com, email4#gmail.com, etc. }
}
}
}
leo#fb.com: {}
wayne#fb.com: {}
etc.
}
Users on the app can create groups and invite their friends to a group.
I'm trying to figure out how to give "email1#gmail.com", "email2#gmail.com" etc. access to the path Users/mary#fb,com/Group/group123 using Firebase rules. I'm also having trouble giving mary#fb,com permissions to read and write. How do I use rules like below for using a custom Unique ID like the the User's email?
{
"rules": {
"Users": {
"$user_id": {
".read": "$user_id === auth.uid",
".write": "$user_id === auth.uid"
}
}
}
}
Circumventing use of the user's auth uid as the unique identifier should be discouraged and probably is only going to make you sad. I'd rethink this approach and encourage others not to follow suit.
Assuming you can't avoid this, then the following will be necessary:
implement your own auth schema
sign your own tokens
include email as part of the token data or, depending on your use case (it helps a great deal to share this in the question, see XY Problem) maybe just use an escaped email as the uid
refer to auth.email in place of auth.uid in your security rules
Thus, in a server/node/etc script:
// after some auth process to verify
// the user and obtain the email
var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator("<YOUR_FIREBASE_SECRET>");
var token = tokenGenerator.createToken({uid: uniqueIdOrMaybeEscapedEmail, email: escapedEmailAddress});
And in your rules:
".read": "$user_id === auth.email",