GoTrueJS "Remember Me" functionality

I’ve been working with the GoTrue library and Netlify Identity with a lot of success and I have a quick question.

The “setCookie” parameter when initiating instance of GoTrue and “remember” parameter to login indicate that the GoTrue library supports “Remember Me” functionality like most authentication systems (and the Identity widget seems to offer a Remember Me parameter), however the Github issue here (https://github.com/netlify/gotrue-js/issues/71) indicates this functionality may not be working, and I have not been able to figure it out how to renew an expired token or make “Remember Me” work on my own.

Can I get an explicit confirmation that this is functionality the GoTrue library doesn’t support, or am I simply not finding the documentation how to do it properly? How would one renew a token? Even if GoTrueJs functionality for this is broken is this still supported by GoTrue in backend so it can be implemented on front end from scratch or does GoTrue simply not provide token renewal functionality?

Kind Regards,
Carl

Hey Carl,

Thanks for bearing with us.

I’ve had a dig through some issues and use cases. I’ve managed to find this issue which goes in to a bit of detail regarding how we can check and refresh the token. This is the code you’ll be looking for.

So, in short – it has the functionality!

Thanks for following up. I developed a work around that works for me that I will explain for future reference, but unfortunately the solution you provided will not really work.

When the GoTrue JS library is instantiated it creates an internal store for holding the access and refresh tokens. You are correct that the jwt function will use the refresh token to provide a new access token if it is expired, however the function fails to update the instance’s internal store so the old refresh token and old access token are used on subsequent calls of jwt, which will error because refresh tokens can’t be used more than once. The known bug here (https://github.com/netlify/gotrue-js/issues/71) is actually more substantial than the report indicates because not only does it fail to update the access token but it fails to update the refresh token so once the jwt function actually uses the refresh token it will never work again until the user signs out and logs back in again.

To actually keep users logged, an application must maintain the state of the access token and refresh token independently of the GoTrue instance. Unfortunately the jwt function only returns the new access token and not the new refresh token so to actually maintain the state of the refresh token you will have to eschew the GoTrueJS library entirely and make fetch requests to the token endpoint yourself. Here is an example of a Vuex Action I use to validate the current token and update the token store if it is expired:

validate({commit, state, dispatch}) {
        if (!state.currentUser) return Promise.resolve(null);
        const user = state.currentUser;
        if (user.token.expires_at <= Date.now()) {
            // keep users logged in
            const formData = new FormData()
            formData.append('grant_type', 'refresh_token')
            formData.append('refresh_token', user.token.refresh_token)
            fetch('/.netlify/identity/token', {
                method : "POST",
                body : formData
            }).then(x=>x.json()).then(newToken => {
                user.token.access_token=newToken.access_token
                user.token.refresh_token=newToken.refresh_token
                user.token.expires_at = jwt_decode(newToken.access_token).exp * 1000;
                commit("SET_CURRENT_USER", user);
            })
            return null
        }
        return null;
    }

I talked with several folks at Netlify who say this isn’t a critical issue and that GoTrueJS is not a fully supported product, which is fine, and I found my workaround so I am satisfied, but I feel very strongly that the bug in the GoTrueJS library is worth fixing, and documenting the refresh token process better would be better for everyone.

3 Likes

Hi Carl,

First off, thank you so much for your explanation and implementation. I’ve been having very similar problems with my site from day 1 of using GoTrue, and could never figure out what the underlying issue was until now. What you described is exactly what I’m experiencing, and this solution is what I needed to fix that, so thank you!!

Now that I have a working implementation (in vanilla js), I find that my manually retrieved refresh_token isn’t kept on refresh or switching tabs and was planning on using a cookie to store that data. Do you foresee any issue with this, or perhaps a better implementation?

That is pretty much exactly what I am doing using these functions:

function saveState(key, state) {
    window.localStorage.setItem(key, JSON.stringify(state));
}
function getSavedState(key) {
    return JSON.parse(window.localStorage.getItem(key));
}

Which are essentially copy and pasted from https://github.com/shortdiv/gotruejs-in-vue/tree/master/src/state/modules/auth

So using cookies should work perfectly fine

1 Like

Gotcha - one weird behavior I noticed was that my cookie ‘nf_jwt’ wasn’t being overwritten when refreshing the token. Because of this, functions and redirects weren’t working properly as the cookie was storing an older, invalid access_token. It wasn’t until I actually deleted the cookie in my refresh function and set it again that things started working properly. I say this is weird because the cookies to store refresh_token and the expiration date were updated consistently - somehow just the nf_jwt cookie was being left behind. Not sure if you’ve experienced this, or understand why it’s happening, but I thought I’d post here in case anyone in the future does experience this.

Also, is there any concern that as the GoTrue store tokens become increasingly out of date with your custom store solution, there might be side effects? For example, I see that the GoTrue instance’s access_token expiration date is stuck in time as my user store refreshes tokens when necessary. Or perhaps you’ve found a way to go back and update the GoTrue instance after receiving refreshed tokens?

hi @vdadfar, the nf_jwt token has the httpOnly flag so it can’t be updated via client-side scripts which is probably why it wasn’t getting updated.

Some admin actions (like updating a user) can only be done in a secure environment such as a Lambda Function (as mentioned here). Just for reference, in case it wasn’t already mentioned.

I don’t have too much to add, but hopefully someone else might have some insight about your questions.

1 Like

Yeah, I discovered that the httpOnly flag was the issue. Rather than keep track of my own store for refresh tokens, I came up with an alternate solution that only requires a wrapper for the jwt function:

async jwtWrapper(forceRefresh) {
  if (localStorage.getItem('gotrue.user')) {
    let localStorageToken = JSON.parse(localStorage.getItem('gotrue.user')).token;
    if (localStorageToken && localStorageToken.expires_at > this.auth.currentUser().token.expires_at) this.auth.currentUser().token = localStorageToken;
  }
  return await this.auth.currentUser().jwt(forceRefresh);
}

great! thanks for sharing your approach :medal_sports: