Under The Hood
I am using Firebase Authentication in my Android app to sign up/in users using Google, Facebook and Email/Password. So far, almost everything works fine except for a single scenario.
The Scenario
I need to disable or delete user accounts from the Firebase console sometimes to ban some users of my app.
In that case, when I disable or delete that particular user, the user must get logged out from the app instantly and should not be able to use it any further.
The Bug
I have used the AuthStateListener to listen for authentication state changes and log out the user automatically as soon as their account is disabled or deleted.
FirebaseAuth.getInstance().addAuthStateListener(firebaseAuth -> {
if (firebaseAuth.getCurrentUser() == null) {
Intent intent = AuthFlowActivity.getCallingIntent(AuthFlowActivity.FORCE_LOGOUT);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
activityExitAnimation(BaseAppActivity.this);
}
});
But I have never seen the AuthStateListener fire any events for these actions. So I am unable to log out the user instantly and the user can still keep on using the app.
I would appreciate if anyone can help in resolving this issue.
Disabling or deleting a user account does not fire an auth state change. Nor should it, the user is still authenticated. In at most an hour, Firebase Authentication will try to refresh the access token for the user. That refresh will fail, at which point the user will become unauthenticated and the auth state change event will fire.
If you're looking to revoke the user's authorization immediately, you will have to do so in another part of your application logic. A common way to do this is by having a blacklist in your application, e.g. in the Firebase Database:
/bannedUsers
uidOfBannedUser: true
Now when you delete/disable a user's account in the Autentication panel, you also add their uid to the list of banned users in the database.
The database can then be secured against access from unauthorized users by adding a clause to your database security rules, e.g.
{
"rules": {
"bannedUsers": {
".read": true,
".write": false // only admins can write these
},
"messages": {
".read": "auth != null && !root.child('bannedUsers').child(auth.uid).exists()"
}
}
}
If you use a different back-end, the implementation will be different. But a blacklist like this is a common approach to ban users. You'll find that you may even care little enough about their authentication that you only ban them, instead of deleting their credentials (which they could simply recreate).
Related
My landing page is a coming soon page with Call to Action of subscribing which takes in name, email address etc from the user. The user need not be authenticated to subscribe. He can be any random person who visits this page. I feel the rule should be ".read": false, ".write": true. But Google warns me saying write:true will allow anyone to write to the database even people who do not use the app.
I feel that this is only natural until I put a CAPTCHA or something. How are such issues tackled(spam prevention in subscription or coming soon pages which have forms)?
What you could do to prevent abuse only using security rules (it will not prevent all sorts of spam, for that you should go into captchas and other solutions), you could setup e-mail authentication on Firebase and request e-mail confirmation. Go under Authentication > Sign-in methods > Enable e-mail.
When you save the user e-mail and username on your database, you can create a "users" node in your database and save it under the user id Firebase assigns to the user.
(0TTUf... is the id assigned by Firebase)
Firebase will request a password, as you don't need users to be subscribed and you just need to keep their e-mail and password, just assign a mock password to all users.
It would look like this:
As you're just capturing e-mail and name for yourself and you don't need users to be authenticated, I don't see why you would give users write or read permissions. Just do the logic within your program to save this data.
Then your rules would look like this:
{
"rules": {
".read": false,
".write": false
}
}
No user is allowed to read or write to your database as you're managing it yourself.
Summing it up: Request name and e-mail, assign a password, send this data to
Firebase as a new user signing in, save e-mail and name under
/users/$user_id
Introduction
I am building a firebase web client app. I would like set Firebase Database rules.
New user registered to a firebase app. Firebase gave him a user.UID.
Then, admin delete OR disabled the user from firebase admin console.
User refresh client app.
(I find out that) user can still write to firebase database even though his account has been deleted/disabled.
.
Goal / Intention
I would like to set a rule that prevent access (.read OR .write) to firebase database when user does not exist OR disabled in admin console/(auth/users).
Some thing like this:
"rules":{
"$uid":{
".write":"auth.isUserActive(auth.uid) == true"
}
}
.
FIREBASE REFERENCE DOC: https://firebase.google.com/docs/reference/security/database/#auth
Question
How can I achieve the above intention? What are the rules should I set to firebase DB?
Deleting a user doesn't revoke existing tokens for that user. See Firebase authentication not revoked when user deleted?. If you're using one of the standard identity providers, this means that the users may still be able to access the data for an hour after you delete the account.
There is no API for you code to check whether a given uid still exists. And even if such an API existed, it wouldn't help in this case, since a malicious user could just bypass that check and call the API directly.
A simple way to deal with this scenario is to keep a whitelist of allowed or blacklist of disallowed users in your database. For a blacklist, you'd keep a top-level (world readable, admin only writeable) list of banned/deleted users:
banned
uid12345: true
When your admins delete a user, they also add them to this list.
And then in your security rules, you check and disallow access for banned users. E.g.:
"posts": {
".read": "auth != null && !root.child('banned').child(auth.uid).exists()"
}
You can do it by User Based Security as per the doc -v2
var FirebaseTokenGenerator = require("firebase-token-generator.js");
var tokenGenerator = new FirebaseTokenGenerator(FIREBASE_SECRET);
var token = tokenGenerator.createToken({ "uid": "1", "hasEmergencyTowel": true });
For the above created token, the you could write the rules as follows:
{
"rules": {
"frood": {
".read": "auth.hasEmergencyTowel === false"
}
}
}
This could be called once the UID Scope id about to end.
For reference: User Based Security Doc -v2
Is there a way to restrict users from registering firebase email/password accounts so that new users can't sign up? I have a small app that only a few admins need to have access to(which I've manually created in the Firebase admin) and the way it's setup right now it seems like anybody could inject a little javascript and register an account.
Firebase Simple Login is an abstraction built on top of Firebase Custom Login for convenience. When using the email / password authentication, it's worth keeping in mind that this is just creating a mapping between that email and password, and automatically generating authentication tokens for use in your security rules.
In your specific case, if all of the "admin" users have already been created, you can achieve the behavior you're looking for through security rules. For example, if you want to only allow read / write access to your Firebase data tree to authenticated users in that list, try writing top-level read / write security rules that require that user to be in the "admins" list:
{
".read" : "auth != null && root.child('admins').hasChild(auth.uid)",
".write" : "auth != null && root.child('admins').hasChild(auth.uid)"
}
Those rules above ensure that the user is authenticated (via auth != null) and require that the authenticated user's id is in the list of admins (root.child('admins').hasChild(auth.uid)).
The last step is to actually make those users admins, by writing them to the admins subtree. Let's say you have users 1, 4, and 7 to make admins, update your data tree to reflect the following:
{
...
"admins": {
"1": true,
"4": true,
"7": true
}
}
As a result of this, even if other users are able to generate new email / password mappings using Firebase Simple Login, those mappings will have no impact on your application as it is restricted using security rules.
#RobDiMarco provided a great answer, but it has a flaw.
The rule root.child('admins').hasChild(auth.uid) will pass, in case auth.uid will be an empty string.
You can test this in Firebase Database Security Simulator, clearing out uid field ({ "provider": "anonymous", "uid": ""}).
This rule root.child('admins').child(auth.uid).val() === true will not pass with an empty uid.
You can also turn off new user registration by adding a cloud function that deletes new users upon registration. See this answer:
How to prevent new user registration on Firebase?
I'm new to Firebase and I'm attempting to set-up a simple authentication system using e-mail/password. The initial concept is simple: you register. Then, after logging in, you can access the rest of the mobile app.
In the past, I could set this up with PHP in just a few minutes. But with Firebase, this has become a battle that I can't seem to win.
Using the light documentation found on Firebase's site, I was finally able to successfully register and authenticate a user. Great.
Unfortunately, people can still access the rest of the app whether they are logged in or not. How do I keep the app protected from non-authenticated users?
Also, how do I associated data submitted on a page with an authenticated user?
I've looked at Firebase's documentation. It lacks practical examples for authentication. It keeps referring me to the Firefeed app as a sample. I've looked at Firefeed's code and the authentication system seems 1) excessively complicated for a login system and 2) too intricately tied in to news feeds to be a practical example to learn from.
On the other hand, perhaps I'm just missing something obvious and fundamental. If someone could point me in the right direction, that would be great. Thanks! :-)
(By the way, I tried e-mailing this question to firebase-talk#googlegroups.com, as suggested on Firebase's site... but the group does not appear to exist, according to the bounce-back message from Google.)
Stepping back for a moment, it's worth noting that Firebase Simple Login is an abstraction built on top of Firebase Custom Login for convenience. You can still use your existing authentication with Firebase using Custom Login, if you like.
Firebase Simple Login eliminates the need for you to run a server just for authentication. However, there is no 1-to-1 parallel to the PHP example where the server would govern request access based upon a detected session on the server because all of your logic, templates, etc. lives in client-side code.
In most cases, your client-side logic, templates, assets, etc. will be static and public. What you're really looking to secure is user and application data, and this is where Firebase Authentication (whether using Simple Login or Custom Login) comes in. Firebase Authentication is essentially token generation - taking confirmed, identifiable user data and passing it securely to Firebase so that it cannot be spoofed.
Read / write access to different paths in your Firebase data tree is governed by Firebase Security Rules, which allow you to write JavaScript-like expressions to control which clients can access which data.
Here's an example:
Suppose you have a user list, where each user is keyed by user id,
such as /users/<user-id>/<data>, and you want to ensure that only
the logged in user can read / write their own data. With Simple Login,
this is really easy!
Looking at the After
Authenticating
section of Email / Password authentication docs, we see that the
auth variable in our security rules will contain a number of fields
after authenticating, including id, the user's unique user id. Now
we can write our security rules:
{
"rules": {
".read": false,
".write": false,
"users": {
"$userid": {
".read": "auth != null && auth.uid == $userid",
".write": "auth != null && auth.uid == $userid"
}
}
}
}
What's going on here? Firebase Authentication (using Simple Login)
securely generated a token containing your verified user data upon
login, and that token data becomes available in your security rules
via the auth variable for the connection. Now, in order for a client
connection to read or write to /users/xyz, the user must be
authenticated and authenticated as user xyz.
Most of the above is covered in the Security Quickstart but it is admittedly a little hard to wrap your head around.
Back to your initial question, if you want to redirect away from certain paths when a user is not authenticated, you can do the following:
var ref = new Firebase(...);
var auth = new FirebaseSimpleLogin(ref, function(error, user) {
if (!user) {
// we're logged out, so redirect to somewhere else
} else {
// we're logged in! proceed as normal
}
});
Hope that helps!
Please note:
Login is now a core feature of Firebase. Simple Login has been
deprecated and documentation for this client is now available on
Github.
See this page for more info:
https://www.firebase.com/docs/web/guide/user-auth.html
It seems like the only thing that can be changed is the password (via auth.changePassword()). How do I let a user change their email address or display name?
The firebase Auth object is pretty simple but it will provide you the user id generated when the user authenticates to your system. You would then take this user id and map it to a Users location where you can store additional information such as display name.
For example, after the user has authenticated and you have your auth object with id value, you could do:
new Firebase('https://your_fb_url.firebase.io').child('users/'+id).set({email: email, name: name}, function(err) {})
You'd want to have read/write rules setup on that location to only allow the authenticated user to see & make changes. Something like:
{
"rules": {
"users": {
"$user": {
".read": "$user == auth.uid",
".write": "$user == auth.uid",
}
}
}
}
6/12/2015 - UPDATE - Below is Outdated
As for changing the actual login e-mail (for Firebase Simple Login Web), that I'm not so sure about. I know they provide a change password method but I haven't seen any documentation about a change login/email method.
The underlying code for firebase simple password doesn't appear to include any methods for changing the login e-mail address associated with the account. The changePassword method eventually performs a jsonp call out to /auth/firebase/update with the email, old password, and new password.
I'd hate to suggest using a combination of removeUser/createUser to remove the old account, create a new account, and update any user id associations you have you in your app - but I don't see a straightforward "changeEmail" method. The remove/create route would require the user to enter their password again - though that's a pretty common practice for updating logins these days anyway.
6/12/2015 - UPDATE - New API
Firebase has moved away from Firebase Simple Login as a separate module and now the core Firebase 2.x library has authentication related methods baked in. Including a method to change the e-mail account used for the authWithPassword methods.
See updated 2.x docs for changeEmail()