如何在服务器端对Nuxt进行身份验证?

时间:2020-10-11 12:12:27

标签: javascript vue.js authentication jwt nuxt.js

我整夜都在寻找解决方案,似乎很多人都知道了,最好的建议通常是“仅切换到SPA模式”,这对我来说不是一个选择。

我使用JWT进行身份验证,使用JWTSessions gem for Rails。

在前端,我具有使用自定义方案的nuxt-auth和Nuxt,以及以下授权中间件:

export default function ({ $auth, route, redirect }) {
  const role = $auth.user && $auth.user.role

  if (route.meta[0].requiredRole !== role) {
    redirect('/login')
  }
}

我的症状如下:如果我登录并浏览受限页面,一切都会按预期进行。我甚至有fetchOnServer: false用于受限制的页面,因为我只需要对公共页面进行SSR。

但是,一旦刷新页面或直接导航到受限制的URL,中间件就会立即将我重定向到登录页面。显然,在客户端进行身份验证的用户在服务器端也没有进行身份验证。

我有以下相关文件。

nuxt.config.js

...
  plugins: [
    // ...
    { src: '~/plugins/axios' },
    // ...
  ],

  // ...

  modules: [
    'cookie-universal-nuxt',
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],

  // ...

  axios: {
    baseURL: process.env.NODE_ENV === 'production' ? 'https://api.example.com/v1' : 'http://localhost:3000/v1',
    credentials: true
  },
  auth: {
    strategies: {
      jwtSessions: {
        _scheme: '~/plugins/auth-jwt-scheme.js',
        endpoints: {
          login: { url: '/signin', method: 'post', propertyName: 'csrf' },
          logout: { url: '/signin', method: 'delete' },
          user: { url: '/users/active', method: 'get', propertyName: false }
        },
        tokenRequired: true,
        tokenType: false
      }
    },
    cookie: {
      options: {
        maxAge: 64800,
        secure: process.env.NODE_ENV === 'production'
      }
    }
  },

auth-jwt-scheme.js

const tokenOptions = {
  tokenRequired: true,
  tokenType: false,
  globalToken: true,
  tokenName: 'X-CSRF-TOKEN'
}

export default class LocalScheme {
  constructor (auth, options) {
    this.$auth = auth
    this.name = options._name
    this.options = Object.assign({}, tokenOptions, options)
  }

  _setToken (token) {
    if (this.options.globalToken) {
      this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, token)
    }
  }

  _clearToken () {
    if (this.options.globalToken) {
      this.$auth.ctx.app.$axios.setHeader(this.options.tokenName, false)
      this.$auth.ctx.app.$axios.setHeader('Authorization', false)
    }
  }

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

    return this.$auth.fetchUserOnce()
  }

  async login (endpoint) {
    if (!this.options.endpoints.login) {
      return
    }

    await this._logoutLocally()

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

    if (this.options.tokenRequired) {
      const token = this.options.tokenType
        ? this.options.tokenType + ' ' + result
        : result

      this.$auth.setToken(this.name, token)
      this._setToken(token)
    }

    return this.fetchUser()
  }

  async setUserToken (tokenValue) {
    await this._logoutLocally()

    if (this.options.tokenRequired) {
      const token = this.options.tokenType
        ? this.options.tokenType + ' ' + tokenValue
        : tokenValue

      this.$auth.setToken(this.name, token)
      this._setToken(token)
    }

    return this.fetchUser()
  }

  async fetchUser (endpoint) {
    if (this.options.tokenRequired && !this.$auth.getToken(this.name)) {
      return
    }

    if (!this.options.endpoints.user) {
      this.$auth.setUser({})
      return
    }

    const user = await this.$auth.requestWith(
      this.name,
      endpoint,
      this.options.endpoints.user
    )
    this.$auth.setUser(user)
  }

  async logout (endpoint) {
    if (this.options.endpoints.logout) {
      await this.$auth
        .requestWith(this.name, endpoint, this.options.endpoints.logout)
        .catch(() => {})
    }

    return this._logoutLocally()
  }

  async _logoutLocally () {
    if (this.options.tokenRequired) {
      this._clearToken()
    }

    return await this.$auth.reset()
  }
}

axios.js

export default function (context) {
  const { app, $axios, redirect } = context

  $axios.onResponseError(async (error) => {
    const response = error.response
    const originalRequest = response.config

    const access = app.$cookies.get('jwt_access')
    const csrf = originalRequest.headers['X-CSRF-TOKEN']

    const credentialed = (process.client && csrf) || (process.server && access)

    if (credentialed && response.status === 401 && !originalRequest.headers.REFRESH) {
      if (process.server) {
        $axios.setHeader('X-CSRF-TOKEN', csrf)
        $axios.setHeader('Authorization', access)
      }

      const newToken = await $axios.post('/refresh', {}, { headers: { REFRESH: true } })

      if (newToken.data.csrf) {
        $axios.setHeader('X-CSRF-TOKEN', newToken.data.csrf)
        $axios.setHeader('Authorization', newToken.data.access)

        if (app.$auth) {
          app.$auth.setToken('jwt_access', newToken.data.csrf)
          app.$auth.syncToken('jwt_access')
        }

        originalRequest.headers['X-CSRF-TOKEN'] = newToken.data.csrf
        originalRequest.headers.Authorization = newToken.data.access

        if (process.server) {
          app.$cookies.set('jwt_access', newToken.data.access, { path: '/', httpOnly: true, maxAge: 64800, secure: false, overwrite: true })
        }

        return $axios(originalRequest)
      } else {
        if (app.$auth) {
          app.$auth.logout()
        }
        redirect(301, '/login')
      }
    } else {
      return Promise.reject(error)
    }
  })
}

该解决方案已经受到其他线程下可用材料的启发,目前,我对如何在Nuxt上对用户进行通用身份验证一无所知。非常感谢任何帮助和指导。

0 个答案:

没有答案