whythawk/nuxt-for-fastapi

user is logged out after refreshing

diegoquintanav opened this issue · 7 comments

Hey thanks for this! I'm using https://github.com/whythawk/full-stack-fastapi-postgresql and I'm getting problems with the authentication flow

After logging in, I get redirected to the /dashboard. At this moment if I refresh the page I get logged out. Why is this? I was expecting to stay logged in.

I really, really need to update this with some of the things I've learned since refactoring this from Vue.js.

The default way https://github.com/tiangolo/full-stack-fastapi-postgresql is structured is that there is a single token issued, and stored in LocalStorage. That is usually fairly robust, but can sometimes clear. If you want to make it really immune to refreshes you can use something like https://github.com/microcipcip/cookie-universal/tree/master/packages/cookie-universal-nuxt to store as cookies.

The additional challenge is that the token has a relatively short lifespan, and you also need to implement a refresh token. I've done that in my current version and need to push that to here as well.

Alternatively - albeit not safely - you can simply make the single token approach have an indefinite lifespan, or single-use, or ... basically, you need to think through how secure you want to make the authentication process and go from there.

This help? It's not a fix, but it'll take me a little bit to get to updating this repo.

Hi! thanks for replying!

I've seen implementations of the refresh token on the backend side of things with fastapi extensions like https://indominusbyte.github.io/fastapi-jwt-auth/usage/refresh/. In the Oauth implementation in the full stack repo, however, the token lifespan is defined in the config file. It's in https://github.com/whythawk/full-stack-fastapi-postgresql/blob/b0dcc4b67d2cb25f840ecbb74046d3c7e095d103/%7B%7Bcookiecutter.project_slug%7D%7D/backend/app/app/core/config.py#L11

The helpers you mention that store the token are in https://github.com/whythawk/full-stack-fastapi-postgresql/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/frontend/utils.ts and are triggered by the logIn and other methods in https://github.com/whythawk/full-stack-fastapi-postgresql/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/frontend/store/main/actions.ts, which are then used by the authenticated middleware in https://github.com/whythawk/full-stack-fastapi-postgresql/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/frontend/middleware/authenticated.ts. The dashboard view, for example, is protected using this middleware.

If I'm not wrong, a single refresh right after logging in should not expire the token right away. What I don't understand is where is the token expiration date being checked and why am I being logged out after a refresh?

Assuming your token isn't being erased, review the code for the middleware, authenticated.ts:

export default function ({ store, redirect }) {
  if (!store.getters["main/isLoggedIn"]) {
    return redirect("/login")
  }
}

That isn't using the token to reauthenticate, but simply redirecting the user to the login page. Instead you could have something like this:

export default async function ({ store, redirect }) {
  await store.dispatch("main/refreshLoggedIn")
  if (!(await store.getters["main/isLoggedIn"])) {
    return redirect("/login")
  }
}

With an action that checks for the token, then reauthenticates the user. At its most simple, it simply checks that the token is still valid, then sets logged in to true. More complex, get the user data and a more up-to-date token from the server. E.g.

  async refreshLoggedIn({ commit, dispatch }) {
    try {
      let refresh = (this as any).$cookies.get("refresh")
      const response = await api.getRefreshedToken(refresh)
      const token = response.data.access_token
      refresh = response.data.refresh_token
      if (token && refresh) {
        dispatch("setAllTokens", { token, refresh })
        await commit("setLoggedIn", true)
        await commit("setLogInError", false)
        await dispatch("getUserProfile")
      }
    } catch (error) {
      commit("setLogInError", true)
    }
  }

This way, if a page requires auth you will automatically refresh the user status. You can implement a test for token expiry as follows (I created a utilities folder):

function getTimeInSeconds(): number {
  // https://stackoverflow.com/a/3830279/295606
  return Math.floor(new Date().getTime() / 1000)
}

function tokenExpired(token: string) {
  // https://stackoverflow.com/a/60758392/295606
  const expiry = JSON.parse(
    Buffer.from(token.split(".")[1], "base64").toString()
  ).exp
  return getTimeInSeconds() >= expiry
}

All of these together create a workflow to:

  1. Test if a token exists in storage,
  2. Validate that the token is still current,
  3. If the token is not current, use the refresh token to reauthenticate the user.

I'm using cookies here for long-term storage of the refresh token.

This help?

Hi! thanks again for replying. There is a method called checkLoggedIn that does this type of check but it produces an exception with the message LocalStorage is not defined. Looking around I know now that this is related to the localstorage being on the client side and the check is being called from the server-side.

I see that your refreshLoggedIn uses cookies, e.g. let refresh = (this as any).$cookies.get("refresh"). Where would I set the 'refresh` cookie? right after logging in?

You can use process.client to test whether you're on the server or client before checking local storage. Or, you can use cookies. When I log in, I send both an access and refresh cookie. You save the refresh cookie to a long-term store at that time (basically, immediately after validating that you got a response from the API call).

Thanks for your help. I'll play around with this and I'll get back to you. Any reason why you are not using something like https://auth.nuxtjs.org/ ?

A few reasons ... the first is that most of the stack was already implemented in https://github.com/whythawk/full-stack-fastapi-postgresql and there didn't seem much reason to add yet another third-party library. I'm very uncomfortable with Node's dependency hell.

The second is that - while I've gone back and forth over the years on supporting integration of alternative logins (e.g. Google/Facebook) - these days I want to ensure that there is no risk of leakage to third-party trackers. Auth is the most important part of securing the privacy and integrity of users. It's also not that difficult, so I kept it this way.

If you find it easier to slot something else in, go for it.