Vue 3 – Autenticando usuário nas rotas via JWT
Introdução Olá, pessoal! Sejam muito bem vindos a mais um artigo do nosso HackTheTask, hoje vou falar um pouco com vocês sobre uma forma de lidar com Guards nas rotas do Vue.js. A ideia principal desse post é explicar como relacionar o resultado da API ao redirecionamento de uma rota…
Introdução
Olá, pessoal! Sejam muito bem vindos a mais um artigo do nosso HackTheTask, hoje vou falar um pouco com vocês sobre uma forma de lidar com Guards nas rotas do Vue.js. A ideia principal desse post é explicar como relacionar o resultado da API ao redirecionamento de uma rota de guarda, impedindo que um usuário sem permissão ou não autenticado acesse uma rota que não deveria.
Para esse artigo utilizaremos a biblioteca vue-router, biblioteca padrão para o gerenciamento de rotas do vue, ou seja, ela se responsabiliza por qual componente será exibido na Single Page Application. E a biblioteca axios para cuidar das requisições http.
Configurando Guards no Vue
configurando nosso arquivo de rotas:
Neste projeto, optei por fazer as rotas como filhas de uma rota principal “home”, dessa forma eu podia ter alguns componentes globais presentes em todas as minhas páginas, como uma barra de navegação por exemplo.
//routes.js
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home,
children: [
{
path: '/minha-rota-autenticada',
name: 'MeuComponenteAuntenticado',
component: () => import(/*import do componente autenticado */)
},
]
},
{
path: '/unauthorized',
name: 'ErroAutenticacao',
component: () => import(/*componente para exibir uma pagina de unauthorized */)
},
{
path: '/login',
name: 'Login',
component: Login
}
]
Vocês devem ter percebido que a rota de erro de autenticação foi feita fora da home, isso é apenas para causar o efeito ao usuário de uma nova tela, sem acesso ao menu (navbar) da aplicação e com uma mensagem para direcionar ao link do login.
Então, após declarar nossas rotas, vamos inicializar o Vue-router e em seguida separar uma variável para as rotas que não pretendo que sejam verificadas. Dessa forma, podemos definir por exemplo uma página com uma mensagem indicando o usuário a fazer o login.
//routes.js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes //'routes' da no mesmo que 'routes: routes' pela versão do js
})
const withoutAuthorization = [
"ErroAutenticacao",
"Login"
]
Por fim, após criarmos as configurações do router, vamos utilizar um método disponibilizado pelo vue-router chamado ‘beforeEach’. Este método permite realizar ações entre as mudanças de cada rota. Ao acontecer o evento da troca de rota o beforeEach recebe 3 parâmetros:
- to: para qual rota o router está enviando o usuário
- from: de qual rota o usuário veio
- next: método que vazio faz a aplicação seguir para a rota do ‘to’, mas se executado com uma rota como parâmetro permite o redirecionamento da ação.
//routes.js
router.beforeEach((to, from, next) => {
let token = window.localStorage.getItem("auth_token")
if( withoutAuthorization.includes(to.name) || token ) {
next()
} else {
next({ name: "ErroAutenticacao" })
}
})
export default router
Portanto, temos nossas condições para a rota seguir, ou o token do usuário está presente no localStorage, ou a rota não tem necessidade de autenticar ele e segue em frente, senão, ele redireciona o usuário para o login. Então exportamos nosso router configurado.
A base para termos nossas rotas configuradas e com guard já está aqui agora eu gostaria de convidá-los a aprofundar esse ponto, até aqui podíamos ver o uso do vue-router pela própria documentação. Minha ideia é aprofundar a junção da autenticação entre as guards do nosso cliente e a autenticação do token pela própria API que será consumida.
Para isso vamos utilizar o axios para cuidar das requisições http da nossa API. É importante ressaltar que todas as ações do usuário precisam ser válidas pelo lado do servidor também, pela própria segurança da aplicação. Dito isto, o próximo passo é configurar o axios, basta configurar ele como um plugin para ser usado pela aplicação, dessa forma o axios pode ser utilizado globalmente, isso evita bastante retrabalho.
Configurando Axios
Primeiro vamos configurar os interceptadores do axios que são uma forma de você tratar a resposta da sua requisição antes da execução do seu bloco de then ou de catch. Nesse caso utilizaremos dessa configuração para verificar se a resposta da api na requisição, explícita que o token foi expirado, indicando o fim da sessão daquele usuário.
//api.js
function interceptors() {
const config = setting => setting
const error = erro => {
const invalidToken = "Token expirado" (mensagem que vier da api)
const extractMessage = erro?.response?.data?.message || null
const message = extractMessage
if( invalidToken.match(message) ) {
window.localStorage.removeItem("auth_token")
// Este evento será usado para redirecionar a rota no caso de falta de autorização por token expirado em qualquer chamada de requisição do usuário
window.postMessage("login-expirado")
}
return Promise.reject(erro.response)
}
return {config, error}
}
Pronto, configuramos para que ao receber uma mensagem específica, dentro do objeto da resposta do erro, indicando que a sessão expirou, ou seja, o token do usuário não é mais válido, removemos o token inválido do localStorage e emitimos um evento de mensagem “login-expirado“. Vou pedir que guardem o evento em mente, pois voltaremos nele no futuro para receber ações sem autorização do usuário e interagir com isso no lado do cliente, através do vue-router.
Agora que temos definido nossas configurações para os interceptadores do axios. precisamos configurar o próprio axios neste nosso arquivo de api, para importá-lo na aplicação.
//api.js
import axios from 'axios';
const api = () => {
const token = window.localStorage.getItem("auth_token")
//inicializamos nosso axios e suas configurações
const configAxios = axios.create({
baseURL: urlApi,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
... token ? { 'Authorization' : `Bearer ${token}` } : {},
},
})
//Alteramos as configurações customizadas dos interceptadores
const { config, error } = interceptors()
configAxios.interceptors.response.use(config, error)
return configAxios
}
Por fim, para usarmos de forma global o axios personalizado que foi construído, iremos inicializar ele no app através de um plugin do vue que criaremos agora.
//api.js
export const apiPlugin = {
install(app, options) {
app.config.globalProperties.$api = api
}
}
Aplicando as configurações
Finalmente, agora temos a faca e o queijo, nosso axios e vue-router configurados, o que precisamos fazer agora é utilizar eles em nossa aplicação. Este passo é bem simples, vamos inicializar nossa aplicação e utilizar o ‘use’ para atribuir nossas configurações ao app.
//main.js
import { createApp } from 'vue';
import App from './App.vue'; //Import do componente principal da Single Page Application
import router from './router';
import { apiPlugin } from './api';
const app = createApp(App)
.use(router)
.use(apiPlugin)
.mount('#app')
Para fechar com chave de ouro voltaremos ao nosso evento com a mensagem login-expirado, podemos utilizar ele para redirecionar o usuário, isto porque no momento em que configuramos o axios, o router ainda não estava conectado na aplicação. Então podemos utilizar do evento para definir o eventListener que verifica se esse evento de mensagem foi disparado, e se for, redireciona o usuário para a rota de alerta para a re-autenticação.
//main.js
window.addEventListener("message", (ev)=> {
if (ev.data == "login-expirado") {
app.$router.push({ name: "ErroAutenticacao" })
}
});
Conclusão
Nesse artigo vimos um pouco sobre como configurar o vue-router e utilizar seu método before-each para criar guards personalizadas em suas rotas. Em seguida configuramos o axios de maneira personalizada e com interceptadores para tratar a resposta que indica que a sessão do usuário expirou. Por fim aplicamos nossas configurações na nossa aplicação tornando ela uma configuração global que funciona em toda nossa aplicação.
Para finalizar queria fazer algumas considerações. Primeiramente, não foquei muito na estrutura de diretórios nesse artigo, pois não era o foco, sinta-se livre para adaptar de acordo com as necessidades e arquitetura do seu projeto.
Segundamente, espero que o artigo tenha ajudado a desenvolver uma ideia de como configurações do axios e vue-router funcionam e podem ser úteis na criação de guards personalizadas, qualquer sugestão sobre o artigo é muito bem vinda nos comentários, quem sabe até outras formas de lidar com a sessão do usuário no lado do cliente via token.
Obrigado pela atenção, até a próxima.