I am making an android app that when a user register an account on firebase, he saves his email, phone number and password. but when he wants to login, he uses his phone number and password. Because of that requirement I must also make the phone number unique There are a number of ways too do this but my options are limited because the database is already being used by a working system and me making changes to it will cause the other system not to work. The best way to solve my problem without affecting the other system is by modifying the rules to prevent a user from adding an phone number that already exist.
My database structure is like this
users {
userID {
name: "John"
phone: "2342222"
address: "23rd Avenue"
email: "email#mail.com"
}userID 2 {
name: "Mark"
phone: "2342222"
address: "5th Avenue"
email: "email#mail.com"
}
}
Now my rules look like this
service cloud.firestore {
match /databases/{database}/documents {
match /Users/{anything=**} {
allow write: if auth != null && !data.exists() && data == request.auth.uid;
}match /Users/{userId} {
allow read: if request.auth.uid == userId;
// I need a rule maybe here that will allow me write phone number to the database
// If the number doesn't exist in my entire Users database.
}match /user-compare/{anything=**} {
allow read: if true;
}
}
}
Ive tried
allow write: if request.auth.uid == userId && !request.resource.data.phone in resource.data.phone;
I want to make sure that a user can only read and write data inside its user ID, but my rules should be the one to prevent duplicate data not the source codes because due to the way the other system is setup, if multiple users have the same phone number, it will crash, and right now my app is the only way to make multiple users with the same phone number.
I know I can create a new collection to holder the numbers and then check if the number exist there for the registration process, but if anyone uses the other system to register, the numbers will not appear in this new collection.
The best way is if I can write a rule. My problem is a bit different from what I've seen and I hope I'm explaining properly. any help would be appreciated, thanks.
I couldn't solve it with rules no matter what I tried but Using Firebase Admin SDK will allow a developer write/edit/view information in Authentication section of the Service and this will allow you make the changes and validate them. You can learn more Here. This can be added as a dependency on the app but Due to the nature of the service I didn't feel that It'll be safe giving users access to an app with admin privileges. But this is a solution when you have the application for example in the office and users only have access to it in a save environment and can be used to add more functionality for staffs or authorized member's use.
For me, what solved my security concerns was to install the Admin SDK on a server. For my problem this can be used in two ways. I can make a custom authentication found Here and use that to sign in users using their phone number and password. But what I chose to do was make a simple API to take the phone number and check if there's any email address associated with it. if Yes retrieve the email and use that together with the password and sign in with email and password. so basically it uses the usual email and password sign in, but the user input their number and password instead. This was what worked for me and I hope it helped someone in a similar position.
Related
I want to check if the user's username and phone number is unique. I have implemented it using
FirebaseFirestore.getInstance().collection("Users").whereEqualTo("phone",ph).get().addOnCompleteListener(...);
This is my firestore rule:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: true;
allow write: if request.auth.uid != null;
}
match /Users/{userID} {
allow update: if request.auth.uid == userID
&& (request.resource.data.username == resource.data.username
|| isUserNameAvailable(request.resource.data.username)
);
}
}
The code works fine with no issues. I am checking this before the signInWithCredential method and hence the request.auth.uid will always be null. To make the code work ill have to keep allow read: true;
But, now i am getting this warning
We've detected the following issue(s) with your security rules:
any user can read your entire database
Is there any workaround to prevent this?
This is a common mistake developers make when using usernames. You gave anyone permission to read your whole database. And even if you manage to put that to only read Users anyone could read all your users data.
I would recommend to remove the first allow read:true and write rules for each path as they should be. Also create a separated collection just for usernames and save all of them there like /usernames/${username} and /phones/${phone}. If the need to be saved together you can do that to.
That way you can very easy check without any query if a username or phone exists by just checking if that path exsists in your database.
You can very easy sync the users collection with those two and even write security rules that prevent creating users in the collection that have a username that already exists in the usernames collection.
I have an android app which uses firebase for authentication and firestore for storing user data. Once the authentication is complete for a first time user, we collect extra info like nick name, age etc and store them in Firestore. We also have an important field called USER_BALANCE. This is set to 0 on new account creation. How can i make sure they dont manipulate this field.
int USER_BALANCE = 0;
User user = new User(name, email, USER_BALANCE,0,0, refreshedToken); db.collection(FIREBASE_COLLECTION_USERS).document(firebaseUser.getUid()).set(user).addOnSuccessListener(this);
We also have certain task in app,on completion where user gets rewarded and points will be added to USER_BALANCE in firestore. I want to make sure nobody decompile the app and can update the field with whatever value they want.
You can use firebase rules for that. Firebase rules check if a user is able to manipulate your data. You can choose for the user to be able to only read the specific value. Because you haven't provided a view of your database structure I haven't tell you how the specific rule that you need will be framed. Check this blog it really helped me a lot when I was starting.
---Edit---
The below firebase rule checks if the user tries to update the specific field and blocks it. You can check this post for more information.
function notUpdating(field) {
return !(field in request.resource.data)
|| resource.data[field] == request.resource.data[field]
}
match /Users/{userId}{
allow read;
allow update: notUpdating('user_balance');
}
Anybody can decompile the app and attempt to make changes. There is no way to prevent that. They don't even need your app to write the database, since it has a public REST API.
What you will need to do instead is use Firebase Authentication along with security rules to determine who can read and write which documents. There is no other way to control access to your database if you intend for your app to be able to read and write it directly. If you can certainly disable public access entirely and create your own backend - but you have just shifted the responsibility onto the backend for making sure the API it exposes also does not allow arbitrary callers to make unauthorized changes.
I am working on one the Android APP & want to integrate it with the Firebase & it's Realtime Database. I have a list of 1000 users(Excel) with details like EmpId, EmpName, Team, Mobile Number, etc.
I want to restrict my APP only to the users from this list & also want to authenticate them using the mobile number present in the list against there name.
Can I use Firebase Auth for the above requirement & if yes, how to do that?
If with FireBase Auth, this is not possible what is the alternative solution?
Please help.
Firebase Authentication only allows the users to identify themselves. What you're describing is limiting what users are allowed to use your app, which is known as authorization, and Firebase Authentication doesn't handle that.
Luckily you tagged with firebase-realtime-database too, and authorization is definitely built into that. What I'd usually do is create a top-level node in the database that contains the UID of users that are allowed to use the app:
"allowedUsers": {
"uidOfUser1": true,
"uidOfUser2": true
...
}
Then in other security rules you'll check if the user's UID is in this list before allowing them access to data, with something like:
{
"rules": {
"employees": {
".read": "root.child('allowedUsers').child(auth.uid).exists()",
"$uid": {
".read": "auth.uid === $uid && root.child('allowedUsers').child(auth.uid).exists()"
}
}
}
}
With these rules:
Allowed users that are signed in can read all employee data.
But they can only modify their own employee data.
Of course you'll want to modify these rules to fit your own requirements, but hopefully this allows you to get started.
A few things to keep in mind:
The UID is only created once the users sign up in Firebase Authentication. This means you may have to map from the email addresses you have to the corresponding UIDs. You can either do this by precreating all users, do it in batches, or use a Cloud Function that triggers on user creation to add the known users to the allowedUsers list.
Alternative you can store the list of email addresses in the database. Just keep in mind that somebody could sign in with somebody else's email address, unless you require email address verification. Oh, and you can't store the email address as a key in the database as-is, since . characters are not allowed, so you'll have to do some form of encoding on that.
Also see:
How do I lock down Firebase Database to any user from a specific (email) domain? (also shows how to check for email verification)
Restrict Firebase users by email (shows using encoded email addresses)
firebase realtime db security rule to allow specific users
How to disable Signup in Firebase 3.x
Firebase - Prevent user authentication
I know it's late but for anyone who may be referencing this question, my recommendation is blocking functions. You essentially create a Firebase Cloud Function that can accept or deny requests to make an account. Here's what it could look like:
const functions = require('firebase-functions');
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
var allowedUsers = ['johndoe#stackoverflow.com'];
// if they are not in the list, they cannot make an account
// throwing this error will prevent account creation
if(allowUsers.indexOf(user.email) == -1) throw new functions.auth.HttpsError('permission-denied');
});
This way is better in my opinion because the security rules doesn't have to reference the database. Instead, the security rules can allow requests from any authenticated user, because the only authenticated users are ones allowed by this function.
It appears that when someone authenticates via oAuth, Firebase creates a uid that looks something like google:111413554342829501512, for example.
In Firebase rules, you can do (read and/or write):
".read": "root.child('users').child(auth.uid).child('isAdmin').val() == true"
Is it assumed that I can't read the message by sniffing the network because of the use of HTTPS? Is this how it works - the UID is a shared key used by Firebase rules?
I see that UID in firebase:session::ack in Local Storage in my browser once authenticated.
Knowing someones user id is not a security risk.
For example, I know that your Stack Overflow user id is 4797603. That fact alone allows me to potentially find you on Stack Overflow.
But it does not in any way allow me to pretend that I am Ron Royston. To do the latter I'd need to know the username and password (and any other factor) that you use to sign-in.
The same applies to Firebase. If you know that my uid in some Firebase-backed application is google:105913491982570113897, you cannot suddenly pretend to be me. The Firebase servers verify that the auth.uid value is based on the actual credentials of that user. The only way to do is by signing in as me, which in this case requires you to know my Google credentials.
I advise to use custom ID along side with UID. When your app grows, you do not want to share the UID or pass it around. Also when setting firebase-rules, you'll be referring to UID, which should be kept private.
generate a random string for the ID.
And for sensitive user data, set a rule in firestore, to only allow reading of the document if request.auth.uid == user.uid. This will prevent unwanted access. Read up a bit more on firestore rules, might be relevant for your use case.
In one of my App, I would like to give a feature as a trial for only 20 attempts. Once the user wants to access the feature for the 21st attempt, he/she will be prompted for in-App purchase.
My question is how can I make this variable persistent so that even if the user uninstalls the app and re-installs, the counter does not reset and takes the last value before the uninstall.
Please help.
The easiest way is to use Backup API and backup your data online. It is very easy to use and here is tutorial:
http://developer.android.com/training/cloudsync/backupapi.html
The disadvantage of this method is that user can disable backups in phone's settings.
Another method requires you to send data to server. I would suggest to use parse.com since it does not require you to write any server code, they have very easy-to-use library for Android and their free plan is very generous. Here is a little tutorial:
https://parse.com/tutorials/get-started-with-android
You can send some sort of unique id (device or account specific) and counter to the cloud every time user triggers your trial feature. Also you can query your counter from the cloud when application launches the first time.
To create that unique id you can approach with few methods:
Try to obtain 64-bit unique number, which is generated when device first boots:
String androidID = Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID);
Note that it was reported that for some device this value might be null. You can verify your androidID variable and if it is null, you can try another method that will allow you to access user's primary e-mail (you probably want to hash it prior to sending it your server).
Also make sure to add android.permission.GET_ACCOUNTS permissions to your manifest file:
final Account[] accounts = AccountManager.get(context).getAccounts();
for (final Account account : accounts) {
if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
final String email = account.name;
// hash email and use it
}
}
if email cannot be obtained - you might also want to try to get device id from telephony manager:
// For CDMA phone getDeviceId() is equal to value request for Secure.ANDROID_ID
final TelephonyManager telMgr = (TelephonyManager)getBaseContext().getSystemService(Context.TELEPHONY_SERVICE);
final String deviceId = telMgr.getDeviceId();
(don't forget to add android.permission.READ_PHONE_STATE permission to your manifest)
Hope that it gave you enough information to add trial functionality into your code.
Also if your in-app purchase is actually a subscription, then Google already implemented trial functionality for you:
http://developer.android.com/google/play/billing/billing_subscriptions.html
(search for free trial)
You have no way of maintaining anything locally for the user if they uninstall or reinstall the application.
If you need the uninstall/re-install requirement, this is counter that will need to be logged on your back-end rather than on the device. You can do this by storing the device's id on your server and incrementing your ticker when appropriate.
I simply define a serializable Expiration object that I store to SD with a dot in front of it to make it hidden.
The object contains time of creation and other metadata.
When the app starts, it checks whether Expiration exists, if not it is create. This way I always know the exact time when the app was installed, despite uninstall/reinstalls.
The downside to this method is that it requires some sort of external storage as we want to go outside of the standard app folder of the internal storage, which is wiped upon uninstall.
To circumvent this, you could look for some way of getting/giving a device a unique id, that remains the same after reinstall, and upload the data to a server. This has the benefit that the user cannot reset the expiration by wiping the SD either.