Skip to content

FR: please add function Auth.getCurrentUser() as a promise that will return current user or null #462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Thaina opened this issue Jan 23, 2018 · 79 comments

Comments

@Thaina
Copy link

Thaina commented Jan 23, 2018

  • Firebase SDK version: 4.9.0
  • Firebase Product: auth

Auth.currentUser object is unreliable to get user from persistence storage. If the authentication system could be pending and don't really have ensure finishing endpoint then you should have a promise for it

So I want to have promise function Auth.getCurrentUser() in addition to onAuthStateChange that would eventually return user but would return null if no user exist in persistence storage or user require sign in from expired token

@google-oss-bot
Copy link
Contributor

Hmmm this issue does not seem to follow the issue template. Make sure you provide all the required information.

@Thaina Thaina changed the title [FR:] please add function Auth.getCurrentUser() as a promise that will return current user or null FR: please add function Auth.getCurrentUser() as a promise that will return current user or null Jan 23, 2018
@bojeil-google
Copy link
Contributor

You can easily implement that on your own with a couple of lines:

function getCurrentUser(auth) {
  return new Promise((resolve, reject) => {
     const unsubscribe = auth.onAuthStateChanged(user => {
        unsubscribe();
        resolve(user);
     }, reject);
  });
}

@Thaina
Copy link
Author

Thaina commented Jan 23, 2018

@bojeil-google If the user does not signed in will that promise would ever fired for null?

And I think this function is really common enough to included in the SDK

@bojeil-google
Copy link
Contributor

Yes that promise will fire. onAuthStateChanged always triggers with the initial state even when it is null. That is what you care for here, the current state. After that is determined, you unsubscribe.

@Thaina
Copy link
Author

Thaina commented Jan 24, 2018

I see but anyway if it really just a couple line and will be the same for everyone so why it not be inside SDK. Did you not consider it useful?

And also that function would fire only one time at first call and will not fire again in second call am I right? What I want from getCurrentUser() function is consistently return null if it already checking in persistence storage, or return the actual current user after that

At least it should have a function like getPreparedAuthStated() or isPreparedAuthFinished

@bojeil-google
Copy link
Contributor

I have no idea what you mean by: " consistently return null if it already checking in persistence storage, or return the actual current user after that".

@Thaina
Copy link
Author

Thaina commented Jan 24, 2018

@bojeil-google If I don't misunderstand, the onAuthStateChanged in promise function you present will work only on the first call. If I call that function on the second time it will never return because the auth state never change again after that

This is not what I want from getCurrentUser(). I want this function to actively checking that user is not really signed in before. I want it to be promise just in case that firebase auth does not finish initializing yet, in that case the user was signed in before but firebase itself are not ready. So return promise is better solution

What I want is

  • If called when firebase auth preparation is not finished : return promise to wait
    • after preparation finished but user was not signed in before : resolve null from the promise
    • after preparation finished and user was signed in before : resolve currentUser from the promise
  • If called after firebase auth preparation is finished
    • but user is not signed in : immediately resolve null from the promise
    • and user is signed in : immediately resolve currentUser from the promise

I want this consistence behavior for getCurrentUser() function

But if I don't misunderstand, the function you present will never resolve if that function was called after firebase auth preparation is finished unless the user try to signed in

@Thaina
Copy link
Author

Thaina commented Jan 24, 2018

If I misunderstand above, which means onAuthStateChanged will always fire once whenever it get called for the current user without the need of signIn or signOut action. Then I would like to request that this behavior should be mentioned in the document

@bojeil-google
Copy link
Contributor

bojeil-google commented Jan 24, 2018

Please read the documentation, you presume some incorrect assumptions.
If we were to implement a promise that returns the current user, it will be done as I described.
onAuthStateChanged will always trigger every time it is set. It is super easy to test that.
If it is called while Auth is still initializing and loading state, it will wait for it to complete before resolving. If you call it after, it will still trigger immediately with the current user.

If you want some custom function or listener that triggers based on your own custom requirements, we provide all the APIs you need to build that.

If you are experiencing issues with the behavior of onAuthStateChanged listener, please file the bugs and we will make sure to address them.

@Thaina
Copy link
Author

Thaina commented Jan 24, 2018

@bojeil-google I know that if I call onAuthStateChanged before initializing and loading state it would be triggered. However I read the document and haven't seen mentioning about what happen if I register onAuthStateChanged after it finished initializing and loading state but the user is not signed in yet

I know that it would be surely be triggered whenever user would sign in by any means. But if user never sign in at all would it trigger that it change from null to null?

Why is that?

Is it changed from null as auth not initialized to null as user not signed in ?

And if I register onAuthStateChanged after finished initialized why it still trigger when user AuthState was not changed at all?

In the document

https://firebase.google.com/docs/reference/js/firebase.auth.Auth

I have seen the explanation only these

Adds an observer for changes to the user's sign-in state.

Prior to 4.0.0, this triggered the observer when users were signed in, signed out, or when the user's ID token changed in situations such as token expiry or password change. After 4.0.0, the observer is only triggered on sign-in or sign-out.

At this line

the observer is only triggered on sign-in or sign-out

is very pinpoint that if user not doing any signin/signout at all it should not be triggered just from registering the observer

Also in here is not mention about that too

https://firebase.google.com/docs/auth/web/manage-users

What document you think I should read?

@Thaina
Copy link
Author

Thaina commented Jan 30, 2018

@bojeil-google Do you have a link to the document you mention about this behavior?

@bojeil-google
Copy link
Contributor

This is the reference for onAuthStateChanged:
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged

This is the guide on how to use it:
https://firebase.google.com/docs/auth/web/manage-users#get_the_currently_signed_in_user

Any time you call this listener after initial state has been determined, it will trigger with the current state (null if no user signed in, and with a user if a user is signed in). If you don't believe, then just try it out yourself.

@Thaina
Copy link
Author

Thaina commented Feb 1, 2018

@bojeil-google It not that I would not belief but it the API name itself is misleading so I got confused. I test it and it seem the real behavior is like you stated. But because it not mentioned in the document I don't know I should rely on it or not

That's why I said if I misunderstand then it should be documented about this behavior. Because the API name is "onAuthStateChanged" but it will also firing one time when the auth state not really changed

@bojeil-google
Copy link
Contributor

Fair enough. We should update the documentation and references to mention that. Thanks for pointing that out.

@chrismcmacken
Copy link

I will 👍 this request, I just wasted a lot of time trying to find a method on the api to load the currently logged in user on page load before I found this issue. It needs to be mentioned that this will also load the user from the persistence if it is persisted.

@atishpatel
Copy link

Here is modified version of @bojeil-google function that can be used anytime to get a promise that resolves into current user or null.

let userLoaded = false;
function getCurrentUser(auth) {
  return new Promise((resolve, reject) => {
     if (userLoaded) {
          resolve(firebase.auth().currentUser);
     }
     const unsubscribe = auth.onAuthStateChanged(user => {
        userLoaded = true;
        unsubscribe();
        resolve(user);
     }, reject);
  });
}

@JesusCuenca
Copy link

JesusCuenca commented Feb 24, 2019

I've 👍 this request, too. As the Thaina, I also think the documentation about onAuthStateChanged is not clear about the discussed behavior, and even the API name itself is misleading. To me, it seems that an action from the user is needed in order to trigger the function, very much like the HTML onClick event.

@SumNeuron
Copy link

@atishpatel I've tried that but it deosnt work for me :/

@holmberd
Copy link

onAuthStateChanged is called when the user auth state changes and when the firebase.auth is first initialized.

Does anyone know if firebase.auth().currentUser caches the user, with or without persistence enabled. Or if requires a new network request API call everytime?

@bojeil-google
Copy link
Contributor

firebase.auth().currentUser caches the user regardless of persistence.

@SumNeuron
Copy link

No idea... I'm using nuxt and have this as a pluggin:

// PLUGIN FILE
import firebaseConfig from '~/firebase'
import firebase from 'firebase/app'

if (!firebaseConfig) {
  throw new Error('missing firebase configuration file')
}

export default ({store, redirect}) => {
  if (!firebase.apps.length) {
    firebase.initializeApp(firebaseConfig)
  }
  return firebase.auth().onAuthStateChanged((user) => {
    if (user) {
      store.commit('firebase/setUser', user)
    }
  })
}





// VUEX STORE MODULE 

...


setUser(state, user) {
    state.user = user
    return this.dispatch('firebase/setProfileRef', state.user.uid)
  }


setProfileRef: firestoreAction(({ bindFirestoreRef }, uid) => {
    return bindFirestoreRef('profile', firebase.firestore().collection('profiles').doc(uid))
  }),
...

@SumNeuron
Copy link

sometimes causes inf recursion 🙃

@jhoughjr
Copy link

bump. I'm finding many hours wasted on the auth state. I vote for a simple API the consistently gives us the user or null when we need it.

@zerobytes
Copy link

See, authStateChanged has a purpose, and therefore it can be used to workaround the fact that .auth().currentUser is not a promise and it's value is not trustable (because it depends on asynchronous tasks), it's purpose is to watch changes at the auth state. getCurrentUser, method that doesn't exists currently, has a different purpose: to simply get the current user despite how many changes happened at the application until now.

Dont take this wrong, please, but having methods implicates in having closed scopes of use, it's a basic pattern rule.

Either making it possible to use async .auth.currentUser or .auth().getCurrentUser().then would be desired implementation.

@holmberd
Copy link

@zerobytes I just wrap it in a promise before calling it from other services.

function getUser() {
  return new Promise((resolve, reject) => {
    let user = this.firebase.auth().currentUser;
    return user ? resolve(user) : reject(new Error('No user signed in')); 
}

@zerobytes
Copy link

zerobytes commented Apr 22, 2019

Hey @holmberd,
I understood what you did, but it has no point in having a promise if your action is not asynchronous,
What we intent to do with getCurrentUser() is having a promise which will await until firebase auth has actually finished the process of identifying the current user. This task happens asynchronously which is the reason why an already authenticated app can fail get firebase.auth().currentUser, as firebase auth has not yet finished the process of setting it.

@atishpatel Answer is more like what we need, and will do the job, but it's still a workaround.
Firebase SDK should have it as a promise.

@holmberd
Copy link

What we intent to do with getCurrentUser() is having a promise which will await until firebase auth has actually finished the process of identifying the current user. This task happens asynchronously which is the reason why an already authenticated app can fail get firebase.auth().currentUser, as firebase auth has not yet finished the process of setting it.

I haven't seen any such failures occur and therefore can't confirm. But it does look like there is a fair share of ambiguity in regards to the API usage in general.

As I see it getCurrentUser is only guaranteed to be set after a firebase.auth().onAuthStateChanged event when a user has signed in, or in the resolved promise of firebase.auth().signIn*(provider). Where the latter is more useful if you are checking if the signed in user is a new user additionalUserInfo.isNewUser.

For me onAuthStateChanged gate-keeps any services relying on the signed in user state and maintain the asynchronous flow.

@mw-ferretti
Copy link

In angular this solution works for me:

import { first } from 'rxjs/operators';

public async getCurrentUser(): Promise<firebase.User> {
    return await this.afAuth.authState.pipe(first()).toPromise();
}

I wait for firebase to complete authentication using authState.

@harshshredding
Copy link

@avolkovi can I attempt to add async to sdk if you busy with other stuff ?

@avolkovi
Copy link
Contributor

@harshshredding yes, you're more than welcome, thanks! Please assign myself and @bojeil-google to review your PR once ready.

@harshshredding
Copy link

harshshredding commented Oct 22, 2020

Simply sharing -- in the hope that someone will find it useful :
this is how I use the promise version of getCurrentUser in my workflow. I import the below file called Firebase.js :

import * as firebase from 'firebase';
let firebaseConfig = {
   ...
};
firebase.initializeApp(firebaseConfig)
export const auth = firebase.auth()
export const getCurrentUser = () => {
    return new Promise((resolve, reject) => {
        if (auth.currentUser) {
            resolve(auth.currentUser);
        }
        const unsubscribe = auth.onAuthStateChanged(user => {
            unsubscribe();
            resolve(user);
        }, reject);
    });
}
export default firebase;

@akauppi
Copy link

akauppi commented Oct 22, 2020

Also for me, the two nulls behaviour of .currentUser was a friction when getting started with Firebase. Something like 2 days lost because of it, altogether, but once one has it solved, the problem kind of fades away. For newcomers, this is certainly worth considering.

Two points:

  1. The .onAuthStateChanged� ’s job is not only to inform once a person has logged in. It also informs about log-outs, and therefore cannot be 1:1 replaced with a promise.

Other than that, I think new JavaScript APIs should be Promise-based.

  1. @Thaina was not seeing a use case for the syncrhronous .currentUser. There kind of is. In my app, for Vue components where I know that the user has logged in, and is not changing, I can use it for getting user info. But also this feels a bit cheating, since it embeds something we just know about the app behaviour in between the lines. I’d likely prefer making it explicit, e.g. passing the user as a property. I certainly won’t cry after the synchronous version.

To me, the main point in this issue is @gamliela ’s comment from Apr 27th about the documentation.

@harshshredding
Copy link

harshshredding commented Oct 24, 2020

@akauppi I am not sure how point 1 relates to the earlier suggestion :

export const getCurrentUser = () => {
    return new Promise((resolve, reject) => {
        if (auth.currentUser) {
            resolve(auth.currentUser);
        }
        const unsubscribe = auth.onAuthStateChanged(user => {
            unsubscribe();
            resolve(user);
        }, reject);
    });
}

The return value makes sense even when the user logs out.

@Thaina
Copy link
Author

Thaina commented Oct 24, 2020

Also for me, the two nulls behaviour of .currentUser was a friction when getting started with Firebase. Something like 2 days lost because of it, altogether, but once one has it solved, the problem kind of fades away. For newcomers, this is certainly worth considering.

Two points:

  1. The .onAuthStateChanged� ’s job is not only to inform once a person has logged in. It also informs about log-outs, and therefore cannot be 1:1 replaced with a promise.

Because that was not the use case needed around getCurrentUser I propose. We just want to know the immediate state of user logged in for current task, not to listening for it to changed. And if user are not logged in then it should return null

  1. @Thaina was not seeing a use case for the syncrhronous .currentUser. There kind of is. In my app, for Vue components where I know that the user has logged in, and is not changing, I can use it for getting user info. But also this feels a bit cheating, since it embeds something we just know about the app behaviour in between the lines. I’d likely prefer making it explicit, e.g. passing the user as a property. I certainly won’t cry after the synchronous version.

I don't think anyone should ever rely on that because you really cannot be ensure that the currentUser was null because user are not really logged in, or user actually logged in but the sdk just not finish initialization

No, you should always rely on promise based. Because that field actually unreliable

@heckad
Copy link

heckad commented Nov 30, 2020

Hello, everybody! How to check if a user can be authorized or not? If the user is not logged in, then render the application immediately, otherwise render the loading window and wait until the onAuthStateChanged event occurs. How to implement this behaviour? Thanks!

@hsubox76
Copy link
Contributor

In general, issues are for bug reporting and feature requests, and we recommend support or Stack Overflow or other forums for help with usage but:

One common pattern is to set some local variable named user to undefined or some other state, then call onAuthStateChanged and in the callback, set user to whatever it returns, so there are 3 states.

If it is:

  • undefined or whatever you initialized it to, that means the check hasn't completed (you might render a loading indicator)
  • a Firebase User - the user is logged in (you might show authorized data)
  • null - that means the check came back and the user isn't logged in (you might show a screen asking them to log in)

@jamesdaniels
Copy link
Member

const getCurrentUser = firebase.auth.getRedirectResult().then(() => firebase.auth.currentUser);

should achieve what you are looking for @Thaina.

@Thaina
Copy link
Author

Thaina commented Dec 24, 2020

const getCurrentUser = firebase.auth.getRedirectResult().then(() => firebase.auth.currentUser);

should achieve what you are looking for @Thaina.

I don't think getRedirectResult would always be activated unless the page is loaded from the redirection login flow

@harshshredding
Copy link

harshshredding commented Jan 13, 2021

I am implementing a new promise-returning method called getSignedInUser as follows, in the auth package. Let me know if this isn't covering any of the major requirements discussed above. Created PR : #4288
@avolkovi @bojeil-google @hsubox76

/**
 * @return {Promise} The fireauth.AuthUser if a user successfully logged in.
 * Null if no user is logged in.
 */
fireauth.Auth.prototype.getSignedInUser = function() {
  return new Promise((resolve, reject) => {
    if (this.currentUser_()) {
      resolve(this.currentUser_());
    }
    const unsubscribe = this.onAuthStateChanged(user => {
      unsubscribe();
      resolve(user);
    }, reject);
  });
}

@raphael-bmec-co
Copy link

This same issue (null meaning pending or signed out) exists in the flutter package as well. Is anyone aware of an issue having been logged that addresses this behaviour across all platforms?

At the moment a delay seems to be the only functional work around and this seems really hacky!

@kandreak
Copy link

Yes, please. +1 for the user as a promise. This 2 year discussion should be enough! :D

@mikebuilds
Copy link

+1 this feature is really important.

For anyone looking for a simple and effective solution for now, here you go

let authStateResolver;

let authReady = new Promise((resolve, reject) => {
    authStateResolver = resolve;
}).then(user => {
    //Use user here.
});

firebase.auth().onAuthStateChanged((user) => {
    authStateResolver(user);
});

Of course be aware that the promise will only call once. You should unsubscribe the onAuthStateChanged call.

@Deliana90
Copy link

+999.999.999 hehehe

@annp87
Copy link

annp87 commented Feb 23, 2023

+100 this feature will save a lot of time.

@Malix-Labs
Copy link

Malix-Labs commented Mar 18, 2023

+1, why isn't it like this already

@Thaina
Copy link
Author

Thaina commented Oct 11, 2023

There seem to have authStateReady() function implemented instead as of 10.1.0,which technically the same feature we need

Are there anyone not satisfied with this approach? If there are not I would close this issue

@spock123
Copy link

spock123 commented Dec 3, 2023

This tread shows what happens when the kids don't use Observables.

@Thaina
Copy link
Author

Thaina commented Dec 3, 2023

@spock123 We don't want to always include 3rd party in every project especially when we need to mockup something fast

I use Observables every time it possible. But it was not native to anything include firebase itself. I too wish that firebase would adopt reactive pattern into its API. But in the meantime we need to wrap everything in RxJS ourselves is not convenient approach

And instead of blaming people not using observable, the one should be blamed is the api that not even using Promise where it should

@finnhvman
Copy link

Hmm, I'm not sure if @spock123 meant the RxJS Observables. A general observer pattern would help here, of course promisifying would also help here.

The blame here is on the maintainers of firebase: the problem is obvious, the solution should be simple enough I think. Our frustration comes from this issue not being addressed for almost 6 years now. This thread really shows what happens when people don't use observable patterns.

@spock123
Copy link

spock123 commented Dec 3, 2023

Sorry @finnhvman @Thaina it was just some light fun.
Actually I don't even think it would solve anything here, as we already have an event emitted with onAuthStateChanged.

Cheers

@prameshj
Copy link
Contributor

prameshj commented Dec 4, 2023

Resolved by #7384

https://firebase.google.com/docs/reference/js/auth.auth.md#authauthstateready

@prameshj prameshj closed this as completed Dec 4, 2023
@philBrown
Copy link

Doesn't appear to be present in the docs yet 😞
https://firebase.google.com/docs/reference/js/auth#functions

@firebase firebase locked and limited conversation to collaborators Jan 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests