How to persist cookie-based sessions with @nuxtjs/auth-module?

Viewed 2706

I am trying to get Nuxt Auth to work with JWT_Sessions Ruby gem as it's shown here: the /login entrypoint sends back an access token as a non-HttpOnly cookie and a CSRF token in the response body. When the token expires you can send both these tokens to a /refresh endpoint to get a fresh pair.

nuxt.config.js

  auth: {
    plugins: [ '~/plugins/auth.js', '~/plugins/axios' ],
    strategies: {
      jwtSessions: {
         _scheme: '~/plugins/auth-jwt-sessions.js',
         endpoints: {
           login: { url: '/login', method: 'post', propertyName: false },
           logout: { url: '/logout', method: 'post' },
           user: { url: '/me', method: 'get', propertyName: false }
         },
         tokenRequired: false,
         tokenType: false
       }
    },

~/plugins/auth-jwt-sessions.js

  ...
  async login (endpoint) {

    // Ditch any leftover local tokens before attempting to log in
    await this._logoutLocally()

    const result = await this.$auth.request(
      endpoint,
      this.options.endpoints.login
    )

    if (result.csrf){
      this.$auth.$storage.setUniversal("csrf_token", result.csrf)
      console.log("sucessfully set up CSRF token " + this.$auth.$storage.getUniversal("csrf_token"));
    }

    return this.fetchUser()
  }
  ...

~/plugins/axios.js

export default function ({$auth, $axios, redirect}) {
    $axios.onRequest(config => {
        var csrf = $auth.$storage.getUniversal('csrf_token')
        if ($auth.$state.loggedIn && csrf){
          config.headers['X-CSRF-TOKEN'] = csrf
        }
    })

    $axios.onResponseError(async error => {
      var response = error.response

      if($auth.$state.loggedIn && response.status === 401 && response.request._retry != true) {
        const originalRequest = response.config
        originalRequest._retry = true
        const newToken = await $axios.post('/refresh').then(response => {
          console.log(response);
          return response.data.csrf
        }).catch(response => {
          console.log(response);
        })
        if(newToken) {
          $auth.$storage.setUniversal('csrf_token', newToken)
          originalRequest.headers['X-CSRF-TOKEN'] = newToken
          return $axios(originalRequest)
        } else {
          redirect(301, '/login')
        }
      } else {
        return Promise.reject(error)
      }
    });
}

The above code works alright until you need to reload/reopen a page after the access token has expired.

Since for some reason my $axios interceptors don't work during strategy initialization (btw, why?), I had to add a refreshing routine to the strategy's mounted function on the client as shown below.

~/plugins/auth-jwt-sessions.js

...
  mounted () {
    if (this.options.tokenRequired) {
      const token = this.$auth.syncToken(this.name)
      this._setToken(token)
    }

    this.$auth.$storage.syncUniversal('csrf_token')

    if (process.client) {
      let csrf = this.$auth.$storage.getUniversal('csrf_token')
      if (csrf) {
        console.log("recovering CSRF token from Universal Storage");
        this.$auth.ctx.app.$axios.setHeader('X-CSRF-TOKEN', csrf)
        var newToken = null
        this.$auth.ctx.app.$axios.post('/refresh').then(response => {
          console.log(response.data);
          newToken = response.data
          this.$auth.$storage.setUniversal('csrf_token', newToken.csrf)
          this.$auth.ctx.app.$axios.get('/me', {}, {
            headers: {'X-CSRF-TOKEN': newToken.csrf},
            withCredentials: false
          }).then(response => {
            console.log(this.$auth.$state);
            this.$auth.setUser(response.data)
          }).catch(response => {
            console.log(response);
          })
        })
      }
    }

    return this.$auth.fetchUserOnce()
  }

  ...

  async fetchUser (endpoint) {
    // Token is required but not available
    if (this.options.tokenRequired && !this.$auth.getToken(this.name)) {
      return
    }

    // the headers need to be configured here once again, the axios config doesn't get persisted
    this.$auth.ctx.app.$axios.setHeader('X-CSRF-TOKEN', this.$auth.$storage.getUniversal("csrf_token"))

    // User endpoint is disabled.
    if (!this.options.endpoints.user) {
      this.$auth.setUser({})
      return
    }

    // Try to fetch user and then set
    const user = await this.$auth.requestWith(
      this.name,
      endpoint,
      this.options.endpoints.user
    )
    this.$auth.setUser(user)
  }
  ...

It seems to be working, but when I let my access token expire and then reload the application, I get redirected to /login for a few moments, and then the session gets restored and I get redirected to the initial route.

1) Is this the right way to achieve cookie-based refresh flow in Nuxt?

2) How can I avoid multiple redirects while the session is being restored during mounted()?

0 Answers
Related