I've been working on a small application, using firestore. I also implemented basic auth using email/password (from the Firebase UI kit).
Now existing users (which I made manually) can login, but if the e-mail is not found, the auth let's you sign-up. Can this be disabled? Because I want to somehow restrict the access a atleast a little bit.
__
What I've done for now is wrote a db-rule so that only a user in my 'users'-collection (where document uid = userid) and has a boolean field 'admin' and give them write access.
The rule itself goes as follows:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if request.auth.uid != null;
allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
}
}
}
is this 'safe' and 'ok' to be implemented like this?
As you can see on this community question, in firebase you cannot disable sign-up without disabling sign-in for all users, so in order to create this control you would have to either:
Set that on your provider, since you are using FirebaseUI, when you build the class doing something like this:
List<AuthUI.IdpConfig> providers = new ArrayList<>();
providers.add(new AuthUI.IdpConfig.EmailBuilder()
.setAllowNewAccounts(false)
.build());
Control it using Cloud Functions by doing something like this:
const admin = require('firebase-admin');
exports.blockSignup = functions.auth.user().onCreate(event => {
return admin.auth()
.updateUser(event.uid, {disabled: true})
.then(blockedUser => console.log(`The user ${blockedUser.toJSON()} has been blocked from SignIn`))
.catch(error => console.log(`${error}`));
});
The solution you already implemented that has a list of authorized users and that blocks out all users that are not, this is a good choice if you have a limited number of users. Also to you security point, this would only be visible to the firebase rules themselves and the users would still need to sign in so the rules can get the uids to compare with the list, so I would say that this would be secure enough.
Hope this helps.
Related
I am using Firebase Firestore to store a list of transactions in a collection called transactions .
I use react to get the transaction from the collection, using a URL with the id of the transaction document: http://myurl.com/h1kj54h2jk35h
const id = match.params.id;
firebase.firestore().collection('transactions').doc(id).get()
The same way I create a new doc. I have not firebase authentication.
How can I secure my Firestore rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /transactions/{id} {
allow read, write;
}
}
}
If I don't allow write, I can't create new transactions.
If I don't allow read, I can't read the transaction, but I don't want to allow the read of all transactions. Only the once when the id from the URL is valid.
Thus, I am looking for a way to protect my database agains unwanted document creations and unwanted document reads.
I think you're looking for what are called granular operations. From that link:
In some situations, it's useful to break down read and write into more granular operations. For example, your app may want to enforce different conditions on document creation than on document deletion. Or you may want to allow single document reads but deny large queries.
A read rule can be broken into get and list, while a write rule can be broken into create, update, and delete.
So if you want to only allow creation of new documents, and getting of document for which a user must know the ID, that'd be:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /transactions/{id} {
allow create, get;
}
}
}
I have an app that is designed so authenticated users via Google only have access to their own data with no "social" features. I want to know the security rules for the below criteria.
Let's say I have 5 collections and one of them is called "todos" and the data mirrors the other collections in that it has a field for the authenticated users uid. The typical document looks something like this:
Todos
todo:{
title:"some titled",
body:"we are the world , we are the children",
uid:"2378y4c2378rdt2387btyc23r7y"
}
Some other collection
thing:{
name:"some name",
content:"Some content",
whatever:"whu-eva",
uid:"2378y4c2378rdt2387btyc23r7y"
}
I want the authenticated Google user to be able to CRUD any data that has said users uid in the uid field. I want all other data to be inaccessible to the logged in user.
I want to know how to create rules for this scenario.
I'm mulling through the documentation now but I figure I might be able to save some time by asking.
I do not have specific roles for the app.
https://firebase.google.com/docs/firestore/solutions/role-based-access
As a side note, is their a feature in Firebase to automatically bind an authenticated Google users uid to documents created while they are logged in? (I am assuming the answer is no and I was planning on manually grabbing the uid in my app and setting it on the client prior to document creation).
Thank you.
Update
I tried using the code that Klugjo posted below.
When I try to test it in the simulator I get an error.
Here is my collection and a screenshot of the error.
Here is something else I tried:
Based on everything I've read it seems like the following code should work - but it doesn't. I've supplemented the key "userId" in place of " uid" that is written in the object data at the top of this post. I changed the key to distinguish it from the uid.
service cloud.firestore {
match /databases/{database}/documents {
match /todos/{id} {
allow read: if request.auth.uid == request.resource.data.userId;
allow create, update, delete:
if request.resource.data.userId == request.auth.uid;
}
}
}
I've created a video where I try to GET and CREATE a document.
I don't think I am using the testing feature correctly.
Video
https://www.youtube.com/watch?v=W7GZNxmBCBo&feature=youtu.be
EDIT
I have it working when I test with a hard-coded request.auth.uid.
In the image below I hardcoded "test" as the request.auth.uid.
My problem now is that I would really like to know how to test it in the rules editor without hard-coding this information.
Edit
Here is a video demo of the problem using a real app.
https://www.youtube.com/watch?v=J8qctcpKd4Y&feature=youtu.be
Here is a sample secure rule set for your requirements.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{id}/{u=**} {
allow read, write: if (isSignedIn() && isUser(id));
}
match /todos/{id}/{t=**} {
allow read, write: if (isSignedIn() && isUserOwner());
}
match /{document=**} {
allow read, write: if false;
}
function isSignedIn() {
return request.auth != null;
}
function isUser(uid) {
return uid == request.auth.uid;
}
function isUserOwner() {
return getResourceData().uid == request.auth.uid;
}
function getResourceData() {
return resource == null ? request.resource.data : resource.data
}
}
}
All documents are publicly inaccessible.
The rests will be decided based on the data already saved in DB and / or the data being sent by the user. The key point is resource only exists when reading from DB and request.resource only exists when writing to DB (reading from the user).
Documents under todos can be read and written only if they have a saved uid which is the same as the sent request's uid.
Documents under users can be read and written only if their document id is the same as the sent request's uid.
isSignedIn() function checks if request is authorised.
isUser(id) function checks if id matches the authorised request's uid.
isUserOwner() function checks if document's uid matches the authorised request's uid.
I think what you are looking for is the "resource" parameter in the security rules: https://firebase.google.com/docs/firestore/security/rules-conditions#data_validation
Try something like:
service cloud.firestore {
match /databases/{database}/documents {
match /todos/{id} {
allow read, write: if request.auth.uid == resource.data.userId;
}
}
}
EDIT:
Subcollection strategy
If you change your DB to look like the following:
/users/{userId}/todos/**
then you could allow users to read/write anything under their own document with the following rule:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid}/{doc=**} {
allow read, write: if request.auth != null && request.auth.uid == uid;
}
}
}
This would have the advantage of not needing to introspect the contents of the data which I believe might count against your read quota.
You are looking for something like this
service.cloud.firestore {
match /databases/{database}/documents {
match /todos/{userId} {
allow read, update, delete: if request.auth.uid == userId;
allow create: if request.auth.uid != null;
}
}
}
match /todos/{userId} makes the userId variable available in the rule condition
request.auth.uid matches the auth'd user uid
I am using firebase in my create-react-app project.
For SignUp purpose I am using
firebaseAuth().createUserWithEmailAndPassword(email, password)
Then after SignUp I am saving their phoneNumber in localStorage and redirect them to PhoneAuth page using then I am using this function
export function PhnAuth(phone) {
window.recaptchaVerifier = new firebaseAuth.RecaptchaVerifier('recaptcha-container',{'size': 'small'});
return firebaseAuth().currentUser.linkWithPhoneNumber(phone, window.recaptchaVerifier)
.then(function (confirmationResult) {
window.confirmationResult = confirmationResult;
}).catch(function (error) {
})
}
After the recaptcha and all done I get the I successfully linked the user's email with their phoneNumber. But how to update that phoneNumber later ? I couldn't find anything regarding updating a linked phoneNumber in the docs.
There's an updatePhoneNumber method on the User object for that purpose.
See the reference docs and the documentation on updating a user's profile.
Note that you'll need a phoneCredential for this, meaning that this must be a verified phone number. See how to update user phone number in firebase.auth (js, ts).
If you want to update a user's phone number without verifying it, that can be done from the Admin SDK. For an example of this, see How to update phone number on Firebase Authentication in NodeJS?
You need to unlink the current phone (provider.providerId === 'phone')
Then you can link a new one
const currentUser = firebaseAuth().currentUser;
currentUser.unlink('phone').then(successCallback).catch(errorCallback)
To check if the phone is linked to the current user you need to check the list of providers
const phoneProviders = currentUser.providerData.filter(
provider => provider.providerId === 'phone'
);
if (phoneProviders.length > 0) {
currentUser.unlink('phone').then(successCallback).catch(errorCallback);
}
I am using ionic 3 angular for my mobile app and fb native cordova plugin is used to login.
The firebase db security documentation uses the syntax like
{
"rules":{
"users":{
"$user_id":{
".write":"$user_id === auth.id"
}
}
}
}
the fb authentication looks like below in my app
doLogin(){
if (this.platform.is('cordova')) {
return this.fb.login(['email', 'public_profile']).then(res => {
const facebookCredential = firebase.auth.FacebookAuthProvider.credential(res.authResponse.accessToken);
firebase.auth().signInWithCredential(facebookCredential);
this.navCtrl.setRoot(TabsPage);
})
}
}
my question is the auth firebase variable is taken care with above code or i need to something extra for auth to get required uid etc. ?
The auth firebase variable is token care of in theory: assuming you have the Facebook sign-in method enabled already. However, the database rules you are showing are not necessarily related.
These rules (the same as above):
"rules": {
"users":{
"$variable":{ ".write": "$variable=== auth.uid" }
Dictate that users can only write to a child node with the same uid. I changed $user_id to $variable to highlight that the $ simply denotes a variable that represents the child node's name.
(I should probably mention that it should be auth.uid not auth.id)
This is used to save user specific data. So, when they signup you could have a function that says
firebase.database().ref('users').child(firebase.auth().currentUser.uid).update(<your custom data here>);
*please note how the child of users is the "firebase.auth().currentUser.uid" which can optionally be retrieved from the firebase.auth().signInWithCredential() promise.
Sorry if the explanation was more that necessary. In short. the uid is always present with firebase.auth().currentUser.uid after login and that uid is what the database rules are referring to in auth.uid and last, the auth/uid/etc is pretty much 100% taken care of with firebase.
I am looking to fetch Auth User(s) UID from Firebase via NodeJS or Javascript API.
I have attached screenshot for it so that you will have idea what I am looking for.
Hope, you guys help me out with this.
Auth data is asynchronous in Firebase 3. So you need to wait for the event and then you have access to the current logged in user's UID. You won't be able to get the others. It will get called when the app opens too.
You can also render your app only once receiving the event if you prefer, to avoid extra logic in there to determine if the event has fired yet.
You could also trigger route changes from here based on the presence of user, this combined with a check before loading a route is a solid way to ensure only the right people are viewing publicOnly or privateOnly pages.
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User logged in already or has just logged in.
console.log(user.uid);
} else {
// User not logged in or has just logged out.
}
});
Within your app you can either save this user object, or get the current user at any time with firebase.auth().currentUser.
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged
if a user is logged in then the console.log will print out:
if (firebase.auth().currentUser !== null)
console.log("user id: " + firebase.auth().currentUser.uid);
on server side you can use firebase admin sdk to get all user information :
const admin = require('firebase-admin')
var serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://yourprojecturl.firebaseio.com",
});
admin.auth().listUsers().then(data=>{
console.log(data.users)
})
This is an old question but I believe the accepted answer provides a correct answer to a different question; and although the answer from Dipanjan Panja seems to answer the original question, the original poster clarified later in a reply with a different question:
Basically, I need to generate token from UID by Firebase.auth().createCustomToken(UID) to sign in user on firebase with the following function firebase.auth().signInWithCustomToken(token).
Because the original question was clarified that the intent is to use
createCustomToken and signInWithCustomToken, I believe this is a question about using the Firebase Admin SDK or Firebase Functions (both server-side) to provide custom authentication, probably based on a username and password combination, rather than using an email address and password.
I also think there's some confusion over "uid" here, where in the code example below, it does NOT refer to the user's Firebase uid, but rather the uid indicated in the doc for createCustomToken, which shows:
admin
.auth()
.createCustomToken(uid)
.then((customToken) => {
...
In this case, the uid parameter on the createCustomToken call is not the Firebase uid field (which would not yet be known), thus providing a series of frustrating replies to the coder asking this question.
Instead, the uid here refers to any arbitrary basis for logging in for which this custom auth will support. (For example, it could also be an email address, social security number, employee number, anything...)
If you look above that short code block from the documentation page, you'll see that in this case uid was defined as:
const uid = 'some-uid';
Again, this could represent anything that the custom auth wanted it to be, but in this case, let's assume it's username/userid to be paired with a password. So it could have a value of 'admin' or 'appurist' or '123456' or something else.
Answer: So in this case, this particular uid (misnamed) is probably coming from user input, on a login form, which is why it is available at (before) login time. If you know who is trying to log in, some Admin SDK code code then search all users for a matching field (stored on new user registration).
It seems all of this is to get around the fact that Firebase does not support a signInWithUsernameAndPassword (arbitrary userid/username) or even a signInWithUidAndPassword (Firebase UID). So we need Admin SDK workarounds, or Firebase Functions, and the serverless aspect of Firebase is seriously weakened.
For a 6-minute video on the topic of custom auth tokens, I strongly recommend Jen Person's YouTube video for Firebase here:
Minting Custom Tokens with the Admin SDK for Node.js - Firecasts
As of now in Firebase console, there is no direct API to get a list of users, Auth User(s) UID.
But inside your Firebase database, you can maintain the User UID at user level. As below,
"users": {
"user-1": {
"uid": "abcd..",
....
},
"user-2": {
"uid": "abcd..",
....
},
"user-3": {
"uid": "abcd..",
....
}
}
Then you can make a query and retrieve it whenever you need uid's.
Hope this simple solution could help you!
From Firebase docs, use Firebase.getAuth():
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
var authData = ref.getAuth();
if (authData) {
console.log("Authenticated user with uid:", authData.uid);
}
Source:
Firebase.getAuth()