Работа с JWT на Vue 3

29 Января 2022 03:21

Данный раздел предполагает уже установленное и настроенное приложение Vue 3 CLI с router.

Манипулирование Bearer токеном

Допустим, у нас есть вёрстка формы на element ui:

<template>
  <div class="login">
    <el-card>
      <h2>Вход в систему</h2>
      <el-form
        class="login-form"
        :model="model"
        :rules="rules"
        ref="form"
        @submit.native.prevent="login"
      >
        <el-form-item prop="username">
          <el-input
            v-model="model.email"
            placeholder="Email"
            type="email"
            :prefix-icon="User"
          ></el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input
            v-model="model.password"
            placeholder="Password"
            type="password"
            :prefix-icon="Lock"
          ></el-input>
        </el-form-item>
        <el-form-item>
          <el-button
            :loading="loading"
            class="login-button"
            type="primary"
            native-type="submit"
            block
          >Войти</el-button>
        </el-form-item>
        <a class="forgot-password" href="#">Забыли пароль?</a>
      </el-form>
    </el-card>
  </div>
</template>

Пишем data():

data() {
  return {
    model: {
      email: null,
      password: null
    }
  };
},

Пишем метод login()

login() {
  axios.post('api/auth/login', {email: this.model.email, password: this.model.password})
    .then(response => {
      localStorage.access_token = response.data.access_token
    })
},

Таким образом, при авторизации, мы получаем токен и кладем его в localStorage.

На текущем этапе была выполнена авторизация и тут можно перенаправить пользователя на главную страницу и выдать контент. В дальнейшем все ajax-запросы будут сопровождаться Bearer токеном.

Дальше, поскольку со стороны laravel, все наши маршруты на получение каких то данных лежат внутри группы auth то чтобы их получить, нужно каждый раз вместе запросом на получение данных посылать в заголовках токен и в адресе запроса должен быть auth.

Теперь рассмотрим пример метода на получение данных:

loadData() {

...

  const {data} = axios.get('api/auth/tasks', {
    headers: {
      'authorization': `Bearer ${localStorage.access_token}`
    }
  })

...

},

Здесь мы берем Bearer token из localStorage и отправляем его заголовком с запросом на получение данных.

На текущем этапе у нас имеется авторизация и проверка токена при получении данных. Это основной принцип работы JWT-токена.

Refresh Token

Как известно, токен имеет время действия. После истечения этого времени, токен станет невалидным и будет выполнен выход из системы. Чтобы каждый раз не авторизироваться, нужно использовать Refresh token.

Для этого можно сделать проверку на сообщение об ошибке. При истечении тоокена, возвращается ошибка с текстом Token has expired, поэтому делаем условие и повторный запрос на адрес с refresh, таким образом:

if(error.response.data.message === 'Token has expired') {
  axios.post('api/auth/refresh', {}, {
    headers: {
      'authorization': `Bearer ${localStorage.access_token}`
    }
  }).then(response => {
    localStorage.access_token = response.data.access_token

    error.config.headers.authorization = `Bearer ${response.data.access_token}`

    return api.request(error.config)
  })
}

Этоn код с уже модифицированным axios.

Модификация axios

Можно модифицировать axios, чтобы при каждом запросе нам не приходилось постоянно прописывать заголовок с токеном.

Создаём в проекте отдельный каталог для вспомогательных файлов с именем, например, utils. Создаём в этом каталоге файл с названием, например, api.js. Помещаем в этот файл следующий код:

import axios from "axios";
import router from "../router/index"

const api = axios.create()

//start request
api.interceptors.request.use(config => {
  if(localStorage.access_token) {
    config.headers.authorization = `Bearer ${localStorage.access_token}`
  }

  //Надо возвращать конфиг после его модификации
  return config
},error => {
  //Этот блок кода срабатывает только тогда, когда ошибка отправки запроса с фронта
  console.log(error)
})
//end request

//start response
api.interceptors.response.use(config => {
  if(localStorage.access_token) {
    config.headers.authorization = `Bearer ${localStorage.access_token}`
  }

  return config
}, error => {
  //Этот блок кода срабатывает когда прилетает ошибка с бэка

  if(error.response.data.message === 'Token has expired') {
    axios.post('api/auth/refresh', {}, {
      headers: {
        'authorization': `Bearer ${localStorage.access_token}`
      }
    }).then(response => {
      localStorage.access_token = response.data.access_token

      //Делаем повторный запрос на получение данных с новым токеном
      //чтобы вручную не обновлять страницу
      error.config.headers.authorization = `Bearer ${response.data.access_token}`

      return api.request(error.config)
    })
  }

  if(error.response.status === 401) {
    router.push('/login')
  }
})
//end response

export default api

Основные комментарии там подписаны, но тем не менее, вкратце что там происходит: через метод interceptors мы расширяем обработку request и response. Добавляем к заголовкам наш токен, а также проводим проверку на состояние токена и переадрессацию в случае его отсутствия или истечения срока действия.

Благодаря тому, что этот файл экспортируется, его можно подключить в любом компоненте ипроизводить запросы через него, например, вот так:

<script>
  export default {
    import API from './utils/api'

    methods: {
      async loadData() {
        const {data} = await API.get('api/auth/tasks')
      }
    }
  }
</script>

На этом этапе рассмотрены основные принципы работы JWT токена с фронта.