0
votes

I'm currently using React JS with Firebase to handle signIn operations.

Me and the team decided to use the Custom Auth System way for sign in. So from the Back End side we generate a custom token then the Front End executes the sign in via signInWithCustomToken.

Everything works like a charm until here, but once I'm signed in, if I refresh the page the currentUser object becomes null so I don't have the possibility to invoke on it the method getIdToken(true) to refresh the token. The firebase.auth().currentUser.getIdToken(true) method is called every time I make an http request with axios (using an interceptor for adding the Authorization field to the request). If I sign in and make the http request without reloading, it works fine, but if I refresh I get an error because the currentUser object loses its content and becomes null.

I've spent more or less 8 hours surfing on the Internet searching for a solution. I've already seen people saying that the solution is to take the user inside the onAuthStateChanged observer, but the problem is that i cannot enter the observer to take the value of the user when I make an http request because the observer it's not a function that I can call when I need.

Also I've tried to save the user object returned by onAuthStateChanged observer when it triggers (during the sign in or sign out), on localStorage, but the user object it's different from the one returned and it doesn't have inside the proto the getIdToken function. So it's useless for me.

I've also tried to use the firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION), passing to it the user object returned by the onAuthStateChanged observer, but nothing changed.

This is a my project's code:

****** AXIOS REQUEST INTERCEPTOR ******

instanceAXIOS.interceptors.request.use(
  async config => {
    const { refreshedToken } = await refreshUserTokenTEST()
    config.headers.Authorization = `Bearer ${ refreshedToken }`
    return config
  },
  error => Promise.reject(error)
)

****** REFRESH TOKEN ******

const refreshUserTokenTEST = () => {
  try {
    return firebase
      .auth()
      .currentUser
      .getIdToken(true)
      .then(token => {
        return { status: 200, refreshedToken: token }
      })
      .catch(err => {
        console.error(err)
        return { status: 500, refreshedToken: null }
      })
  } catch (exc) {
    console.error("ERROR DURING TOKEN REFRESH")
    return { status: 404, refreshedToken: null }
  }
}

****** firebasePersistenceOption function ******

const firebasePersistenceOption = async (userObject) => {
  return firebase
    .auth()
    .setPersistence(firebase.auth.Auth.Persistence.SESSION)
    .then(function() {
      return userObject
    })
    .catch(function(error) {
      // Handle Errors here.
      var errorCode = error.code;
      var errorMessage = error.message;
    })
}

****** onAuthStateChanged OBSERVER ******

firebase.auth().onAuthStateChanged(async function(user) {
  if (user) {
    console.log("LOGGED IN")
    await firebasePersistenceOption(user)
  } else {
    console.log("LOGGED OUT")
  }
})

Like I said, without reloading the page, everything works fine, so there are no errors in the code (I almost wish it was that).

1
You're thinking about the auth state listener wrong. You should be using that to gate access to API calls that require an authenticated user to be present. It's not intended to be a function to call on demand. Typically what you're supposed to do is wait until the listener indicates whether a user is signed in or out, then build the UI and invoke other functions. Yes, this adds complexity to your page, but it's the right way to do it.Doug Stevenson
@DougStevenson Good point, I didn't think about that, thank you. But what about the refresh of the token? Even if I use the observer to limit the access to the private APIs if the user is not signed in, the problem still remains. Do you have any suggestions?Simone Paglino
What about it? The SDK refreshes it automatically. Just request it each time you make a call.Doug Stevenson
I'm really confused about the refresh of token. I've already seen some people saying that the token is refreshed automatically, but after 1 hour every time I get a 401 status error when the Back End checks if the token is validate. For this reason I need to use the getIdToken to refresh it. Could you please tell me more about that? I've never use Firebase before.Simone Paglino
It sounds like you're not requesting a new token with every request as I suggested. Don't hold on to it after you make the request - they are not "cacheable". Let the SDK determine for you what you should use at any given moment.Doug Stevenson

1 Answers

1
votes

I finally found a solution to the problem. Thanks to the suggestions of @DougStevenson and a discussion found on GitHub, I was able to write a function for getting the refreshed id token.

This is the basic code:

// get the current user inside the observer
// then refresh the token
// and finally unsubscribe the observer

const getIdTokenRefreshed = async () => {
  return new Promise(async (resolve, reject) => {
    const unsubscribe = firebase
      .auth
      .onAuthStateChanged(user => {
        unsubscribe()
        const refreshedToken = await user
          .getIdToken(true)
          .catch(err => console.error(err))
        resolve(refreshedToken)
    }, reject)
  })
}