Do I need to call the offAuth Method on logging out? - firebase

I have created a function which was previously working to store user details in the User node in my Firebase database. However upon trying it today it is now returning an error message saying
TypeError: Cannot read property 'uid' of null
The code I have written simply stores the auth.uid as a child of user and then within that node it stores all data of that user. As of Friday this worked for a few users but now it is now producing the above error message.
To fix this do I need to call the offAuth method upon logging out? If so, How would I go about this?
My code so far is below.
ref.onAuth(function(authData) {
ref.child('user').child(authData.uid).set(authData).then(function(auth) {
console.log('Data Saved Successfully!');
}).catch(function(error) {
console.log(error);
})
})

The onAuth() method is called whenever the authentication state of the user changes. This means that it is called both when the user is authenticated and when they get unauthenticated. In the latter case, the authData argument will be null and you have to handle that in your code.
From the Firebase documentation:
Use the onAuth() method to listen for changes in user authentication state.
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(function(authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider);
} else {
console.log("User is logged out");
}
});
You're missing that if in your code.

Related

When does creationTime or lastSignInTime of firebase.auth.UserMetadata returned as null/undefined?

I bumped into this issue while trying to create user document in Firestore after a new user is signed up using Firebase Authentication.
In user document, I want to include creationTime and lastSignInTime fields but found out that those fields are optional. That means I would have to make those fields in my user document optional as well but I am not quite convinced why they should be optional in the first place. I just cannot think of a case where those fields in metadata of User instance should be set as undefined when it is returned after successful sign up/in using Firebase Auth.
If there's no specific case where they're returned as undefined, I'm planning to make those fields in my user document as required fields, and throw error just in case they are returned as undefined.
For example using react-native-firebase:
try {
const { user } = await auth().signInAnonymously();
const {
metadata: { creationTime, lastSignInTime },
uid,
} = user;
if (creationTime == null) {
throw new TypeError('creationTime is returned as undefined');
}
if (lastSignInTime == null) {
throw new TypeError('lastSignInTime is returned as undefined');
}
await firestore().collection('user').doc(uid).set({
creationTime,
lastSignInTime,
// other fields
});
} catch (error) {
// handle error
}
But if not, then no choice but to have them as optional as well.
So wrapping up the question, is there any case where creationTime or lastSignInTime is set to undefined/null? Is it safe to just treat them as required fields?
(A similar issue is posted here but it's closed with no answer.)
I'm not sure about creationTime but lastSignInTime can be undefined/null if the sign-up has occurred but the user has not signed in yet. For example the user might have to complete email verification or something along those lines.

How to force logout firebase auth user from app remotely

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.

How to ensure Anonymous login with Firebase?

I'm working on a React Native application which use Firebase Auth uid to identify individual user instances.
My initial implementation called signInAnonymously on every starting up and it returned non-persistent uid.
Referring to Anonymous user in Firebase, my code became like this. it works as expected but still unclear why this code is correct.
static ensureLogin() {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
resolve(user)
} else {
firebase.auth().signInAnonymously()
.catch(function(error) {
reject()
})
}
})
}
To answer this question, let's check details in this official document.
https://firebase.google.com/docs/auth/web/manage-users
It has this example code:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
The latter comment line says "No user is signed in." instead of "User is signed out.".
It implies that it is triggered not only by user's sign in/out actions.
It also says below.
By using an observer, you ensure that the Auth object isn't in an
intermediate state—such as initialization—when you get the current
user.
This means that 2 facts.
Auth object has internal states
onAuthStateChanged observes the state of Auth object
So, we should notice that onAuthStateChanged can be triggered by any status change of Auth object including initialization! And calling signInAnonymously after init is nothing strange.
Once signInAnonymously in else block is called, it re-triggers onAuthStateChanged after signing in.
That's why the code is correct.

Extending the Meteor loginWithPassword method

I may be totally off line here, but I'm trying to extend the loginWithPassword method in Meteor to handle only returning users with a few parameters set in their profile.
I'm creating the users fine and once created they login as that user type and all is good, but, when I try and login again I hit a wall.
I've tried implementing my own login handler as follows...
Accounts.registerLoginHandler(function(loginRequest) {
console.log("Got to Accounts.registerLoginHandler");
console.log(loginRequest);
var userId = null;
var user = Meteor.loginWithPassword(loginRequest.email, loginRequest.password, function(error){
if(error !== undefined){
setAlert('error', 'Error in processing login. ' + error.reason + '.');
}
});
var userWithType;
if(user){ // we have the right username and password
console.log("Found a user and logged them in");
userWithType = Meteor.users.findOne({'id': user._id, 'profile.type': loginRequest.type});
}
if(userWithType){
console.log("Found User of that type")
userId = user._id;
}
console.log("UserId", userId);
return {
id: userId
}
});
But am getting an error when I get to this code that says
Got to Accounts.registerLoginHandler
{ email: 'blah2#blah', password: 'blha', type: 'user' }
Exception while invoking method 'login' TypeError: Object #<Object> has no method 'loginWithPassword'
at app/server/login.js:8:23
at tryAllLoginHandlers (app/packages/accounts-base/accounts_server.js:53:18)
at Meteor.methods.login (app/packages/accounts-base/accounts_server.js:73:18)
at maybeAuditArgumentChecks (app/packages/livedata/livedata_server.js:1367:12)
at _.extend.protocol_handlers.method.exception (app/packages/livedata/livedata_server.js:596:20)
at _.extend.withValue (app/packages/meteor/dynamics_nodejs.js:31:17)
at app/packages/livedata/livedata_server.js:595:44
at _.extend.withValue (app/packages/meteor/dynamics_nodejs.js:31:17)
at _.extend.protocol_handlers.method (app/packages/livedata/livedata_server.js:594:48)
at _.extend.processMessage.processNext (app/packages/livedata/livedata_server.js:488:43)
I'm obviously missing a this pointer or something like that, but don't know enough about this framework to know if I'm totally off track here even trying to get this to work.
Ta
P.
I am not too familiar with it but from http://docs.meteor.com, Meteor.loginWithPassword () can only be called on the client. You have written it into the server side code from the tutorial.
That is throwing the error you see. If you move it to the client you will also see that it only returns to the callback function so your variable user will remain undefined.
Meteor.user().profile is available on the client so you can just check the type there in the callback of loginWithPassword to check the information at login.

Logging in via Firebase Email/Password

I am trying to build a basic web application w/ user authentication via email/password registration using Firebase.
My setup right now includes a main.js file that consists of the following:
var dbRef = new Firebase('https://url.firebaseIO.com');
var authClient = new FirebaseAuthClient(dbRef, function(error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
// user authenticated with Firebase
console.log('User ID: ' + user.id + ', Provider: ' + user.provider);
} else {
// user is logged out
console.log('logged out!');
}
});
function next(){
window.location = 'index.html';
}
function test(){
authClient.login('password', {
email: email,
password: password,
rememberMe: true
},next());
// window.location = 'index.html';
}
I obtain email/password values from a form and login. That works. But as soon as I include a callback function to then redirect them to a new authenticated page, it no longer works. In fact, most of the time I get an "UNKOWN ERROR" response.
When I get to the next page, I am no longer logged in. If I remove the next() function and stay on the same page, it works - even if I then trigger the next function from the console. Is there a different way you are supposed to proceed to another page?
I'm pretty sure there is some sort of communication issue (possibly the login does not get a return before the page is switched?) because if I add a 1s timeout before the next function, it then works. But surely this is not best practice?
Thanks!
Per https://www.firebase.com/docs/security/simple-login-email-password.html, the authClient.login() method does not actually accept a callback, so the problem you're seeing is likely the result of navigating away from the current page before the callback is returned, as you suggested.
I would recommend doing the redirect in the callback you're passing during the instantiation of the auth client. (new FirebaseAuthClient(ref, callback)) and redirect if you detect a logged-in user. This callback will be invoked once upon instantiation with the current authentication state of the user, and then again any time the user's authentication state changes (such as on login or logout).

Resources