I'm having trouble getting Firebase authentication to work with an anonymous user. I'm building a chat app using Firechat.js, which attaches a onDisconnect() handler to set the user "offline".
However, it seems when I call firebase.unauth() it logs the user out before the onDisconnect() has a chance to set the user "offline", so it fails with a permission error (this is my theory).
The log shows exactly how everything transpires as I login and then logout:
app.js: using username: John Smith <<logged in
app.js: calling firebase.unauth() <<logged out
firechat.js: Firechat Warning: Firechat requires an authenticated Firebase reference. Pass an authenticated reference before loading.
app.js: using username: Anonymous3aa-437b <<after logout, reauthenticate as anonymous
firebase.js: FIREBASE WARNING: set at /chat/user-names-online/john smith/-KE9LcpieTwxj_A4sBHz failed: permission_denied
As you see the user is anonymous before Firechat has a chance to set the previous user offline. Here is the onDisconnect() bit in firechat.js
Here's my app code:
var $firebase = new Firebase(...);
var $firechat = new Firechat(...);
$firebase.onAuth(function(authData) {
if (authData) {
var username = authData[authData.provider].displayName;
var anonusername = "Anonymous" + authData.uid.substr(10, 8);
console.log('using username: ' + username || anonusername);
//set firechat user and resume chatting session (user becomes "online")
$firechat.setUser(authData.uid, username || anonusername, function() {
$firechat.resumeSession();
});
} else {
//if not logged in, authenticate anonymously
$firebase.authAnonymously(function(error) {
if (error) {
console.log(error);
}
});
}
});
Here's the security rule for user-names-online:
"user-names-online": {
// A mapping of active, online lowercase usernames to sessions and user ids.
".read": true,
"$username": {
"$sessionId": {
".write": "(auth != null) && (!data.exists() || !newData.exists() || data.child('id').val() === auth.uid || auth.provider === 'anonymous')",
"id": {
".validate": "(newData.val() === auth.uid)"
},
"name": {
".validate": "(newData.isString())"
}
}
}
},
It seems firechat is built for this scenario, so why is it failing?
You seem to be confusing a few things:
calling unauth() drop the authentication session for the user.
It does not disconnect the user.
It does cancel any listeners for location that require authentication.
the code you attach to onDisconnect() runs on the Firebase servers, once they detect that the client has disconnected.
If the onDisconnect() handler removes the user from some sort of presence system, other clients can see that the user disappears.
the client that disconnected cannot immediately see the result of its own disconnect, because it has already disconnected.
if a client wants to know when it has disconnected, monitor the .info/connected value.
Related
I have the following use case that is working as expected:
new user arrives on website
user is given an user.uid from anonymous sign in from firebase
user creates a document having as userId reference the aforementioned user.uid
in the very same page user is invited to sign in otherwise the document will be lost
user logs in and finds in is account the document
WIN!
Now I have a use case that is not working as expected:
returning user with session expired or from a different browser arrives on website
user is given an user.uid from anonymous sign in from firebase
user creates a document having as userId reference the aforementioned user.uid
in the very same page user is invited to sign in otherwise the document will be lost
user logs in and doens't find in is account the document
DIDN'T WIN THIS TIME :(
I configured firebase auth with the following configuration:
const uiConfig = {
signInFlow: 'popup',
autoUpgradeAnonymousUsers: true,
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
],
callbacks: {
signInFailure: (error: any) => {
if (error.code != 'firebaseui/anonymous-upgrade-merge-conflict') {
return Promise.resolve();
}
var cred = error.credential;
return firebase.auth().SignInAndRetrieveDataWithCredential(cred);
}
},
};
So, as you can see, the issue is that, the first time, autoUpgradeAnonymousUsers create a new userId with the anonymous user id and everything is fine, but of course the second time doesn't work anymore.
How should I solve this problem given that in my security rules I want to create a check that userId cannot be updated AND only a request with the same userId can see the document?
security rules:
allow create: if request.auth.uid != null
allow read: if request.auth.uid == resource.data.userId
&& request.auth.uid != null
allow update: if request.auth.uid == request.resource.data.userId && resource.data.userId == request.resource.data.userId && request.auth.uid != null
Thank you.
The problem is you can not create a new user with the same credentials. If the user logs in he looses the data from the anonymous sign in.
You have to save the data from the anonymous user locally and after the user logs in you have to copy the data to the current user. You should also deleting the anonymous account.
I have found this example which uses the Firebase realtime database to save the user data.
https://github.com/firebase/firebaseui-web#upgrading-anonymous-users
// signInFailure callback must be provided to handle merge conflicts which
// occur when an existing credential is linked to an anonymous user.
signInFailure: function(error) {
// For merge conflicts, the error.code will be
// 'firebaseui/anonymous-upgrade-merge-conflict'.
if (error.code != 'firebaseui/anonymous-upgrade-merge-conflict') {
return Promise.resolve();
}
// The credential the user tried to sign in with.
var cred = error.credential;
// If using Firebase Realtime Database. The anonymous user data has to be
// copied to the non-anonymous user.
var app = firebase.app();
// Save anonymous user data first.
return app.database().ref('users/' + firebase.auth().currentUser.uid)
.once('value')
.then(function(snapshot) {
data = snapshot.val();
// This will trigger onAuthStateChanged listener which
// could trigger a redirect to another page.
// Ensure the upgrade flow is not interrupted by that callback
// and that this is given enough time to complete before
// redirection.
return firebase.auth().signInWithCredential(cred);
})
.then(function(user) {
// Original Anonymous Auth instance now has the new user.
return app.database().ref('users/' + user.uid).set(data);
})
.then(function() {
// Delete anonymnous user.
return anonymousUser.delete();
}).then(function() {
// Clear data in case a new user signs in, and the state change
// triggers.
data = null;
// FirebaseUI will reset and the UI cleared when this promise
// resolves.
// signInSuccessWithAuthResult will not run. Successful sign-in
// logic has to be run explicitly.
window.location.assign('<url-to-redirect-to-on-success>');
});
}
I have a project which uses firebase auth with firebaseUI to authenticate users. I have enabled Google, Facebook and email providers. What I need is to remotely logout or disable some of the users.
I want the users to logout from the app on doing so. I tried disabling the user in the firebase console and also used the firebase admin SDK (https://firebase.google.com/docs/auth/admin/manage-sessions) to revoke the refresh tokens.
I waited for more than 2 days and still noticed that the user was logged in and could access the firestore data.
I have also gone through and tried
Firebase still retrieving authData after deletion
Can anyone point to what I am doing wrong ?
You also cannot remotely force a user to be signed out. Any sign out will have to happen from the device that the user is signed in on.
There is no way to revoke an access token once that is minted. This means that even if you disable the user's account, they may continue to have access for up to an hour.
If that is too long, the trick (as also mentioned in my answer to the question you linked) is to maintain a list of blocked users in your database (or elsewhere) and then check against that in your security rules (or other authorization layer).
For example in the realtime database, you could create a list of blocked user's UIDs:
banned_uids: {
"uid1": true
"uid2": true
}
And then check against that in your security rules with:
".read": "auth.uid !== null && !root.child('banned_uids').child(auth.uid).exists()"
You can send a message data with FCM to force to log out.
For example, if the users use android application.
Save the FCM token in a collection in firebase Realtime.
configure the Android client app, in the service. LINK You have to make when receive a message with especial string, force to log out.
make the trigger you need in cloud functions, to send the data LINK when you need the user log out.
SUCCESS!
As per your scenarios, i assume that you need to make user logout when user is disabled.
Use One global variable to store TokenNo (might be in shared preference or sqlite):
Add following code to your manifest:
<service android:name=".YourFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Add following code in your
public class LogoutOntokenchange extends FirebaseMessagingService{
#Override
public void onNewToken (String token){
if(TokenNo=>1){ //if tokenNo >=1 means he already logged in
TokenNo=0;
FirebaseAuth.getInstance().signOut(); //Then call signout method
}
else{
TokenNo=1; //store token no in db
}
}
}
What Happens here:
When user logged in first time onNewToken is called then It goes into else then TokenNo is updated to 1 from 0.
When You disable any user then automatically token is refreshed.Then OnNewToken is called then TokenNo>=1 so user will be logged out.
NOTE: When user log in for first time i.e if TokenNo variable is not stored then store it as 0.
For reference: https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/FirebaseMessagingService
The only way I can think about is adding a if-else block in your starting activity.
Store the that status of user (verified/banned/deleted) in Firebase Real-time database. Then retrieve the status of user at start of application and add the code:
if (currentUserStatus.equals("banned"))
{
currentUser.logout();
}
What I've done is I created for each user upon registration a Firestore document with the UID as document ID. In this document I store an array which stores all fcm tokens the individual user receives when logging into a new device. That way I always keep track where the user is logged in. When the user logs out manually the fcm token will be deleted from the document in Firestore as well as on the device.
In order to be able to log out the user everywhere they are signed in I did the following. When starting the app and once the user is logged in I start a snapshot listener that listens to all changes in the users document. As soon as there is a change I retrieve the new array of fcm tokens, search inside the array for the local current device fcm token. If found, I do nothing. If the fcm token is no longer in the array I will call the local logout method and go back to the login screen.
Here are the methods I used in swift on iOS. The closures (passOnMethod) will just trigger an unwind segue to the login view controller.
import Foundation
import Firebase
class FB_Auth_Methods {
let db = Firestore.firestore()
var listener: ListenerRegistration?
func trackLoginStatus(passOnMethod: #escaping () -> () ) {
listener?.remove()
if let loggedInUserA_UID = Auth.auth().currentUser?.uid {
listener = db.collection(K.FStore.collectionOf_RegisteredUsers_Name)
.document(loggedInUserA_UID)
.addSnapshotListener { (snapshotDocument, error) in
if let error = error {
print(error)
} else {
if let document = snapshotDocument {
if let data = document.data() {
if let fcmTokens = data[K.FStore.Users.fcmTokens] as? [String] {
print("Found the following tokens: \(fcmTokens)")
self.compareTokensAgainstCurrentDeviceToken(fcmTokens: fcmTokens, passOnMethod: { () in
passOnMethod()
})
}
}
}
}
}
}
}
func compareTokensAgainstCurrentDeviceToken(fcmTokens: [String], passOnMethod: #escaping () -> () ) {
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print(error)
} else if let result = result {
if fcmTokens.contains(result.token) {
print("Token found, doing nothing")
} else {
print("Token no longer found, logout user")
do {
try Auth.auth().signOut()
InstanceID.instanceID().deleteID { error in
if let error = error {
print(error)
} else {
passOnMethod()
}
}
} catch let signOutError as NSError {
print (signOutError)
}
}
}
}
}
}
And here is the method I use when logging out the user everywhere but at the current device.
func deleteAllFcmTokensExceptCurrent(loggedInUserA: User, passOnMethod: #escaping () -> () ) {
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print(error)
} else if let result = result {
let batch = self.db.batch()
let deleteAllFcmRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
batch.updateData([K.FStore.Users.fcmTokens: FieldValue.delete()], forDocument: deleteAllFcmRef)
let updateFcmTokenRef = self.db.collection(K.FStore.collectionOf_RegisteredUsers_Name).document(loggedInUserA.uid)
batch.updateData([K.FStore.Users.fcmTokens: FieldValue.arrayUnion([result.token])], forDocument: updateFcmTokenRef)
batch.commit { (error) in
if let error = error {
print(error)
} else {
passOnMethod()
}
}
}
}
}
Not tested yet, as our backend programmer, who is in charge of setting up Firestore rules was gone for the day, but in theory this should work: (and it's something I'll test tomorrow)
Having a FirebaseAuth.AuthStateListener in charge of serving UI based on the status of the user
This combined with rules in firestore
match /collection
allow read: if isAuth();
Where isAuth is:
function isAuth() {
return request.auth.uid != null;
}
If the user is then disabled, while being logged in, whenever the user tries to read data from the collection, he should be denied, and a signOut() call should be made.
The AuthStateListener will then detect it, and sign the user out.
I have a MySQL application and would like to moved it gradually to Firebase. As a first step I am "dumping" JSON objects made from queries (simple queries over one or two tables) to Firebase. I want to do this on a regular basis, just calling a page that produces an object (from the MySQL using Coldfusion) and as a security rule I should be logged in with a specific Twitter account.
This is the page READING data Firebase http://www.redesign.mobi/fbse/
This is the page in WRITE mode http://www.redesign.mobi/fbse/?up=aktuell
Presently I cannot write because I set write to false in "Security and Rules":
".write": false
Just for testing I wanted to set the rule that anyone logged in can write
".write": "auth !== null && auth.provider === 'twitter'"
The Twitter auth works properly but it does not prevent a not-logged-in user from writing.
What did I overlook?
Here is the code for auth and writing:
<!--- Auth --->
var ref = new Firebase("https://shining-fire-8148.firebaseio.com");
ref.authWithOAuthPopup("twitter", function(error, authData) {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
});
var qaktuell = new Firebase("https://XXXXXXXXX.firebaseio.com/angebote3");
qaktuell.set($scope.items);
I'd like to secure my presence logic so that the app automatically sets the username/userid from the current auth token/session and has permission to only set the logged in user's presence.
1. How can I get currently logged in user's info from the Firebase class when we are outside of the scope of the auth method?
myConnectionsRef.childByAppendingPath(Fierbase.currentUserId) //there is no such thing as Firebase.currentUserId
2. How can I set up the permissions so that a user only updates their own child node and not others - is the following the right way:
{
"rules": {
"members":
{
"$room_id": {
".read": "auth.uid !== null"
"$member_id" : {
".write": "auth.uid === $member_id"
}
}
},
}
You can read about this in the docs, under the section titled Monitoring Authentication State. What is here is just a re-iteration of the existing docs.
Use the onAuth() method to listen for changes in user authentication state.
// Create a callback which logs the current auth state
function authDataCallback(authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out");
}
}
// Register the callback to be fired every time auth state changes
var ref = new Firebase("https://<your-firebase>.firebaseio.com");
ref.onAuth(authDataCallback);
Additionally, you can use the getAuth() method to synchronously check authentication state.
var ref = new Firebase("https://<your-firebase>.firebaseio.com");
var authData = ref.getAuth();
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out");
}
Firebase Simple login provides an email/password option, how do I use it? Starting from from creating a user, storing data for that user, to logging them in and out.
There are three distinct steps to be performed (let's assume you have jQuery):
1. Set up your callback
var ref = new Firebase("https://demo.firebaseio-demo.com");
var authClient = new FirebaseAuthClient(ref, function(error, user) {
if (error) {
alert(error);
return;
}
if (user) {
// User is already logged in.
doLogin(user);
} else {
// User is logged out.
showLoginBox();
}
});
2. User registration
function showLoginBox() {
...
// Do whatever DOM operations you need to show the login/registration box.
$("#registerButton").on("click", function() {
var email = $("#email").val();
var password = $("#password").val();
authClient.createUser(email, password, function(error, user) {
if (!error) {
doLogin(user);
} else {
alert(error);
}
});
});
}
3. User login
function showLoginBox() {
...
// Do whatever DOM operations you need to show the login/registration box.
$("#loginButton").on("click", function() {
authClient.login("password", {
email: $("#email").val(),
password: $("#password").val(),
rememberMe: $("#rememberCheckbox").val()
});
});
}
When the login completes successfully, the call you registered in step 1 will be called with the correct user object, at which point we call doLogin(user) which is a method you will have to implement.
The structure of the user data is very simple. It is an object containing the following properties:
email: Email address of the user
id: Unique numeric (auto-incrementing) ID for the user
FirebaseAuthClient will automatically authenticate your firebsae for you, not further action is required. You can now use something like the following in your security rules:
{
"rules": {
"users": {
"$userid": {
".read": "auth.uid == $userid",
".write": "auth.uid == $userid"
}
}
}
}
This means, if my User ID is 42, only I can write or read at example.firebaseio-demo.com/users/42 - when I am logged in - and no-one else.
Note that Simple Login does not store any additional information about the user other than their ID and email. If you want to store additional data about the user, you must do so yourself (probably in the success callback for createUser). You can store this data as you normally would store any data in Firebase - just be careful about who can read or write to this data!
Just incase someone is reached to this thread and looking for some example application using the firebase authentication. Here are two examples
var rootRef = new Firebase('https://docs-sandbox.firebaseio.com/web/uauth');
......
.....
....
http://jsfiddle.net/firebase/a221m6pb/embedded/result,js/
http://www.42id.com/articles/firebase-authentication-and-angular-js/