DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on • Edited on

Protegendo uma aplicação com Refresh Tokens.

Índice

1 - Backend - Conhecendo o Backend

Voltar ao Topo

Antes de começamos a proteger a nossa aplicação, precisamos conhecer o Backend com o qual iremos conectar, assim como descobrir quais escolhas o mesmo adotou, uma vez que existe diferentes formas de criar e gerenciar os refresh tokens.

Para este artigo, estarei usando como base uma API feita com NestJS que usa um esquema de Refresh definido por mim, porém que cobre a maioria dos modelos e/ou estrategias de refresh token existentes.

O código fonte da API por ser encontrado no seguinte repositorio.:
https://github.com/TobyMosque/ws-auth-samples-backend/tree/refresh-after-spa

porém, não se preocupe com o código em si, mas com o comportamento da API, primeiro devemos analisar todos os endpoints utilizados para a autenticação.

Backend Methods

2 - Backend - Endpoint de Login

Voltar ao Topo

O primeiro endpoint que iremos visitar é o de login:

Endpoint Login

2.1 - Parâmetros do Login

Voltar ao Topo

Note que além do body com o username e o password, temos dois parâmetros que são passados na query string, estes parâmetros são utilizados para instruir o backend de como iremos gerenciar o refresh token.

rotation: valor boleano (true/false) que define se iremos usar rotação de tokens ou não

  • true: o refresh_token poderá ser usado apenas uma vez, então a cada /refresh a API irá retornar um novo refresh_token além de um access_token renovado.
  • false: o refresh_token poderá ser utilizado multiplas vezes.

flow: enumerador que define quem irá gerenciar o refresh_token

  • server: a API não irá devolver o refresh_token ao cliente, ficando a API responsável pela segurança do refresh_token, nesta caso o refresh_token é persistido em um cookie seguro.
  • client: a API irá devolver o refresh_token ao cliente, neste caso a API não irá persistir e/ou gerenciar o refresh_token, ficando a cargo do cliente a segurança do mesmo.

2.2 - Analisando o Login

Voltar ao Topo

Agora vamos fazer algumas chamadas a API para que possamos observar os diferentes comportamentos.

Lembrando que pode ser usada qual quer combinação de flow e rotation, porém irei utilizar apenas duas combinações nos exemplos.

2.2.1 - Primeiro Cenário - flow=server e rotation=false

Voltar ao Topo

Login com flow=server e rotation=false

Veja, que a API retornou a seguinte resposta:

{ "accessToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMTgwY2E2My0yYjUyLTRlYjAtOTJlNi1mMWNlMzYxM2IxNjYiLCJuYW1lIjoiVG9iaWFzIE1lc3F1aXRhIiwiZW1haWwiOiJtZUB0b2JpYXNtZXNxdWl0YS5kZXYiLCJyb2xlcyI6WyJkZXZlbG9wZXIiLCJhZG1pbiIsInVzZXIiXSwiaWF0IjoxNjc4MDQ4NjQ0LCJleHAiOjE2NzgwNDg2NzQsImF1ZCI6Imh0dHBzOi8vand0LXNhbXBsZS50b2JpYXNtZXNxdWl0YS5kZXYiLCJpc3MiOiJodHRwczovL2p3dC1zYW1wbGUudG9iaWFzbWVzcXVpdGEuZGV2IiwianRpIjoiODBmMDQxMDctMGE4MS00ZWNhLWE1ODItNWM3NzViNDQ4MWJmIn0.GIAeo5sfrA5ksKp5msAJCAxLo4ivHxoHrTz6j9JBWyVgg3BmHN0veF24V27PG_K8yqjMiCKRONuNwkdZHENdQg" } 
Enter fullscreen mode Exit fullscreen mode

então, podemos fazer o decode do token em https://jwt.io

[{ "alg": "HS512", "typ": "JWT" }, { "sub": "0180ca63-2b52-4eb0-92e6-f1ce3613b166", "name": "Tobias Mesquita", "email": "me@tobiasmesquita.dev", "roles": [ "developer", "admin", "user" ], "iat": 1678048644, "exp": 1678048674, "aud": "https://jwt-sample.tobiasmesquita.dev", "iss": "https://jwt-sample.tobiasmesquita.dev", "jti": "80f04107-0a81-4eca-a582-5c775b4481bf" }] 
Enter fullscreen mode Exit fullscreen mode

Se analisamos o iat e o exp, veremos que o token tem validade de 30 segundos, ou seja, se trata de um token de curta duração:

{ iat: '05/03/2023 17:37:24' exp: '05/03/2023 17:37:54' } 
Enter fullscreen mode Exit fullscreen mode

Também podemos verificar que a API não retornou o refresh_token, porém o salvou como um cookie.

Set-Cookie: REFRESH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzgwNDg2NDQsImV4cCI6MTY3ODEzNTA0NCwiYXVkIjoiaHR0cHM6Ly9qd3Qtc2FtcGxlLnRvYmlhc21lc3F1aXRhLmRldiIsImlzcyI6Imh0dHBzOi8vand0LXNhbXBsZS50b2JpYXNtZXNxdWl0YS5kZXYiLCJqdGkiOiI4MGYwNDEwNy0wYTgxLTRlY2EtYTU4Mi01Yzc3NWI0NDgxYmYifQ.GW3o37wLXY6eg4_ns9xuDKb8UOwlw1SF2CHdtWaXdXg; Path=api/auth/refresh; Expires=Mon, 06 Mar 2023 20:37:24 GMT; HttpOnly; Secure; SameSite=Lax 
Enter fullscreen mode Exit fullscreen mode

E no caso desta API, o refresh_token também é token assinado, assim como o access_token, então também podemos fazer o decode do mesmo em https://jwt.io.

Apesar de termos usado um token assinado para o refresh_token, é bastante comum a utilização de objetos, sendo que estes objetos podem mudar drasticamente de uma API para outra, porém a finalidade e a utilização deles continua a mesma.

Segue um exemplo de refresh_token como objeto

{ grant_type: 'refresh_token', refresh_token: `${refreshToken}`, client_id: `${clientId}`, client_secret: `${clientSecret}`, scope: '', } 
[{ "alg": "HS256", "typ": "JWT" }, { "iat": 1678048644, "exp": 1678135044, "aud": "https://jwt-sample.tobiasmesquita.dev", "iss": "https://jwt-sample.tobiasmesquita.dev", "jti": "80f04107-0a81-4eca-a582-5c775b4481bf" }] 
Enter fullscreen mode Exit fullscreen mode

E novamente poderemos analisar as datas de criação e expiração, e veremos que se trata de um token de longa duração, com validade de 24 horas:

{ iat: '05/03/2023 17:37:24' exp: '06/03/2023 17:37:24' } 
Enter fullscreen mode Exit fullscreen mode

2.2.2 - Segundo Cenario - flow=client e rotation=true

Voltar ao Topo

Login com flow=client e rotation=true

Veja, que a API retornou a seguinte resposta:

{ "accessToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMTgwY2E2My0yYjUyLTRlYjAtOTJlNi1mMWNlMzYxM2IxNjYiLCJuYW1lIjoiVG9iaWFzIE1lc3F1aXRhIiwiZW1haWwiOiJtZUB0b2JpYXNtZXNxdWl0YS5kZXYiLCJyb2xlcyI6WyJkZXZlbG9wZXIiLCJhZG1pbiIsInVzZXIiXSwiaWF0IjoxNjc4MDQ5MDcwLCJleHAiOjE2NzgwNDkxMDAsImF1ZCI6Imh0dHBzOi8vand0LXNhbXBsZS50b2JpYXNtZXNxdWl0YS5kZXYiLCJpc3MiOiJodHRwczovL2p3dC1zYW1wbGUudG9iaWFzbWVzcXVpdGEuZGV2IiwianRpIjoiMTViYzJlMjctMjkxYy00MzE5LTgxNWYtYTc1NDJkNWZmZTAyIn0.Xz--Ep6eTUGl_YG6VejwJFqaR-WKCPzFDywvT57KnU9eZ3lj9Con6GDZK5EEjFDTdBNvcfc3xsI5xtnuITLIOg", "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZWZyZXNoIjoiZjQzZmE0MDMtNTE3Yi00YmEzLTljNTktNDRjYzAxZGMzOWVjIiwiaWF0IjoxNjc4MDQ5MDcwLCJleHAiOjE2NzgxMzU0NzAsImF1ZCI6Imh0dHBzOi8vand0LXNhbXBsZS50b2JpYXNtZXNxdWl0YS5kZXYiLCJpc3MiOiJodHRwczovL2p3dC1zYW1wbGUudG9iaWFzbWVzcXVpdGEuZGV2IiwianRpIjoiMTViYzJlMjctMjkxYy00MzE5LTgxNWYtYTc1NDJkNWZmZTAyIn0.WtFmPuhQMrDvxkxoyc6rAFtr4wXErnMhnqRa4_VZ6Ts" } 
Enter fullscreen mode Exit fullscreen mode

Desta vez, a API retornou o refresh_token como parte da resposta (body) e não como um cookie, ficando a critério da API decidir qual é a melhor estratégia para proteger o refresh_token.

Como o access_token, terá o mesmo formato independente do flow e do rotation que forem passados, não é necessário decompor ele novamente, porém o refresh_token terá uma claim a mais.

[{ "alg": "HS256", "typ": "JWT" }, { "refresh": "f43fa403-517b-4ba3-9c59-44cc01dc39ec", "iat": 1678049070, "exp": 1678135470, "aud": "https://jwt-sample.tobiasmesquita.dev", "iss": "https://jwt-sample.tobiasmesquita.dev", "jti": "15bc2e27-291c-4319-815f-a7542d5ffe02" }] 
Enter fullscreen mode Exit fullscreen mode

A API irá utilizar a claim refresh para identificar que o refresh_token só pode ser utilizado uma vez, e se o mesmo já foi utilizado.

3 - Backend - Endpoint de Refresh

Voltar ao Topo

Agora que o login já foi bem dissecado, porém voltar a nossa atenção para o refresh

Endpoint de refresh

3.1 - Parâmetros do Refresh

Voltar ao Topo

Assim como no login, temos que passar alguns argumentos na query string, porém devemos passar apenas o flow, que deve ser igual ao que passamos durante o login.

flow: enumerador que define como o refresh_token será passado para a API.

  • server: a API escreveu um cookie, então a API deve ler deste cookie.
  • client: a aplicação Cliente deve passar o refresh_token no header RefreshToken.

3.2 - Analisando o Refresh

Voltar ao Topo

Agora, vamos dá continuidade aos cenários apresentados no login.

3.2.1 - Primeiro Cenário - flow=server e rotation=false

Voltar ao Topo

Refresh com flow=server e rotation=false

Neste caso, o refresh_token será lido do cookie, como não instruímos a API para utilizar o rotation do refresh_token, um novo refresh_token não foi gerado, e o cookie não será sobescrito.

3.2.2 - Segundo Cenário - flow=server e rotation=false

Voltar ao Topo

Refresh com flow=server e rotation=false

Neste exemplo, como o refresh_token foi criado usando o flow=client e o rotation=false, ele precisou ser enviado no header, assim como ele foi invalidado e um novo refresh_token, este estando disponível da resposta (body) da requisição feita à API.

Lembrando que, o token antigo foi invalidado, não podendo ser utilizado novamente.

4 - Backend - Endpoint de Logout

Voltar ao Topo

E por fim temos o endpoint /logout:

Endpoint de Logout

Neste caso só há uma coisa a ser dita, caso o flow=server, estamos instruindo a API a apagar o cookie gerenciado pela API.

Lembrando que o objeto desta API é disponibilizar a maior quantidade de fluxos e configurações possíveis, para que possamos aplicar os mesmos ao nosso frontend.

5 - Frontend - Entendendo as Vulnerabilidades

Voltar ao Topo

Quando falamos em proteger uma aplicação, temos de pensar nas principais vulnerabilidades que podem ocorrer ao se optar por utilizar tokens JWT para realizar a Autenticação e/ou Autorização.

Neste ponto, as duas principais vulnerabilidades são a XSS (Cross-Site Scripting) ou a CSRF (Cross-Site Request Forgery).

  • XSS: O ataque se dá pela injeção de um script que será capaz de ler o LocalStorage, SessionStorage, Cookies não seguros, ou qual quer outra informação que esteja disponível globalmente. Evitamos este problema ao não disponibilizar dados sensiveis globalmente.
  • CSRF: O cookie com o Token JWT é enviado para outro servidor, podendo assim ser lido por uma aplicação maliciosa. Evitamos este problema ao marcar o nosso cookie com HttpOnly; Secure; SameSite=Lax.

6 - Frontend - Protegendo um App SPA

Voltar ao Topo

Agora vamos falar sobre como proteger uma Aplicação SPA, porém isto também se aplica à aplicações PWA, Cordova, Capacitor e Electron. Ou seja, aplicações cujo o build possui apenas arquivos estáticos, não contando com a possibilidade de possuir um servidor/backend proprio.

Neste caso, para mantemos a aplicação segura, teremos de contar com uma API/Backend que disponibilize o flow=server ou o rotation=true.

6.1 - Realizando o Refresh

Voltar ao Topo

Mas vale ressaltar que independente do fluxo que iremos adotar, o boot do axios irá ser o mesmo, sendo que todos os ajustes serão feitos apenas no composable responsável pela autenticação.

No boot do Axios/API iremos injetar o Access Token no header e realizar o refresh caso o Access Token expire.

src/boot/axios.ts

import { boot } from 'quasar/wrappers'; import axios from 'axios'; import { InjectionKey } from 'vue'; import { useAuth } from 'src/composables/auth'; export const apiKey: InjectionKey<AxiosInstance> = Symbol('api-key'); export default boot(async ({ app, store, router }) => { const url = process.env.API_URL; const api = axios.create({ baseURL: url }); const { token, refresh } = useAuth(api); api.interceptors.request.use( function (config) { if (token.value) { config.headers ||= {} config.headers.Authorization = `Bearer ${token.value}`; } return config; }, function (error) { return Promise.reject(error); } ); api.interceptors.response.use( function (response) { return response; }, async function (error) { const res = error.response; switch (res.status) { case 401: if (token.value) { await refresh() if (token.value) { return api.request(error.config); } } break; } return Promise.reject(error); } ); await refresh(); }) 
Enter fullscreen mode Exit fullscreen mode

Agora algumas explicações, como podemos ver nos destaques na imagem abaixo, o token é injetado no request, e caso haja um erro do tipo 401 no response, o refresh é acionado e a requisição é refeita.

Boot Axios - SPA

6.2 - Integrando a uma API com flow=server

Voltar ao Topo

Ao optamos por confiamos a segurança do Access Token à API, e caso a API persista o Refresh Token em um Cookie Seguro, a aplicação estará completamente imune a ataques do tipo XSS e/ou CSRF.

Agora irei mostrar um exemplo de implementação com o flow=server

src/composables/auth.ts

import { ref, computed } from 'vue' import jwtDecode from 'jwt-decode'; import type { AxiosInstance } from 'axios' import type { JwtPayload } from 'jwt-decode'; type Payload = JwtPayload & { roles: string[] } interface LoginRequest { username: string; password: string; } const token = ref('') function useAuth (api: AxiosInstance) { const payload = computed(() => { if (!token.value) { return null; } return jwtDecode(token.value) as Payload; }) async function login (req: LoginRequest) { const { data } = await api.post('/auth/login?flow=server'); token.value = data.accessToken; } async function refresh (req: LoginRequest) { try { const { data } = await api.get('/auth/refresh?flow=server'); token.value = data.accessToken; } catch (error) { const res = error.response; if (res.status === 401) { token.value = ''; } } } async function logout (req: LoginRequest) { await api.delete('/auth/logout?flow=server'); token.value = ''; } return { token, payload, login, refresh, logout } } 
Enter fullscreen mode Exit fullscreen mode

Note que no código acima estamos armazenando o token em memoria, assim como disponibilizamos os métodos de login, refresh e logout. Vale salientar que refresh_token não é accessível pelo frontend.

Integração com flow=server

6.3 - Integrando a uma API com rotation=true

Voltar ao Topo

No caso da API não se responsabilizar pelo Refresh Token, ou de termos algo que esteja a bloquear o Cookie Seguro da API, teremos de persistir o Refresh Token no Local Storage, o que irá abrir a nossa aplicação à um ataque do tipo XSS.

Para mitigamos este problema, devemos adotar o rotation=true, de forma que o token será invalidado a cada uso, ou seja, em 99% dos casos, o Refresh Token será invalidado junto ao Access Token, o que limita o Ataque do tipo XSS e qual quer dano que venha a ser gerado por ele.

Agora iremos abordar o fluxo com flow=client com rotation=true
src/composables/auth.ts

import { ref, computed } from 'vue' import { useLocalStorage } from '@vueuse/core' import jwtDecode from 'jwt-decode'; import type { AxiosInstance } from 'axios' import type { JwtPayload } from 'jwt-decode'; type Payload = JwtPayload & { roles: string[] } interface LoginRequest { username: string; password: string; } const token = ref('') const refresh = useLocalStorage('refresh-token', '') function useAuth (api: AxiosInstance) { async function login (req: LoginRequest) { const { data } = await api.post('/auth/login?flow=client&rotation=true'); token.value = data.accessToken; refresh.value = data.refreshToken; } async function refresh (req: LoginRequest) { try { const { data } = await api.get('/auth/refresh?flow=client', { headers: { RefreshToken: refresh.value } }); token.value = data.accessToken; refresh.value = data.refreshToken; } catch (error) { const res = error.response; if (res.status === 401) { token.value = ''; refresh.value = ''; } } } async function logout (req: LoginRequest) { await api.delete('/auth/logout?flow=client'); token.value = ''; refresh.value = ''; } const payload = computed(() => { if (!token.value) { return null; } return jwtDecode(token.value) as Payload; }) return { token, payload, login, refresh, logout } } 
Enter fullscreen mode Exit fullscreen mode

Neste caso, o refresh token é gerenciado pelo frontend, e por isto podemos ver que o mesmo é lido, escrito e persistido.

Integração com rotation=true

7 - Frontend - Protegendo um App SSR

Voltar ao Topo

Agora que concluímos as explicações sobre aplicações SPA, vamos falar sobre SSR, e neste caso o refresh_token deve ser compartilhado entre o server-side e o client-side, e por isto não podemos usar o flow=server e seremos forçados a armazenar o Refresh Token em um Cookie gerenciado pelo nosso Frontend.

De toda forma, temos duas opções, uma usando rotation=true, onde o armazenando no refresh_token será no client-side. Caso tenhamos que usar o rotation=false, teremos que armazenar no server-side e usar um ssrMiddleware para criar uma API intermediária entre o server-side e o client-side.

7.1 - Integrando a uma API com rotation=true

Voltar ao Topo

No caso do rotation=true a implementação é a mesma que usamos no SPA mode, mas iremos usar um CookieStorage ao invés do LocalStorage.

src/composables/auth.ts

import { ref, computed, Ref } from 'vue' import { useStorage, StorageLike } from '@vueuse/core' import jwtDecode from 'jwt-decode'; import { Cookies } from 'quasar'; import type { AxiosInstance } from 'axios' import type { JwtPayload } from 'jwt-decode'; type Payload = JwtPayload & { roles: string[] } interface LoginRequest { username: string; password: string; } const token = ref('') let refresh: Ref<string>; function useAuth (api: AxiosInstance, cookies: Cookies) { if (!refresh) { const opts = { path: '/', sameSite: 'Lax', secure: true } const storage: StorageLike = { getItem(key: string) { return JSON.stringify(cookies.get(key)); }, setItem(key: string, value: string) { const obj = JSON.parse(value); cookies.set(key, obj, opts); }, removeItem: function (key: string): void { cookies.remove(key, opts); } } refresh = useStorage('refresh-token', '', storage) } async function login (req: LoginRequest) { const { data } = await api.post('/auth/login?flow=client&rotation=true'); token.value = data.accessToken; refresh.value = data.refreshToken; } async function refresh (req: LoginRequest) { try { const { data } = await api.get('/auth/refresh?flow=client', { headers: { RefreshToken: refresh.value } }); token.value = data.accessToken; refresh.value = data.refreshToken; } catch (error) { const res = error.response; if (res.status === 401) { token.value = ''; refresh.value = ''; } } } async function logout (req: LoginRequest) { await api.delete('/auth/logout?flow=client'); token.value = ''; refresh.value = ''; } const payload = computed(() => { if (!token.value) { return null; } return jwtDecode(token.value) as Payload; }) return { token, payload, login, refresh, logout } } 
Enter fullscreen mode Exit fullscreen mode

E na imagem abaixo, esta em destaque as alterações no composable:

Integração com rotation=true - SSR

E claro, como o composable agora está esperando os Cookies como argumento, nós precisamos fazer uma modificação no boot.

src/boot/axios.ts

import { boot } from 'quasar/wrappers'; import axios from 'axios'; import { InjectionKey } from 'vue'; import { useAuth } from 'src/composables/auth'; import { Cookies } from 'quasar'; export const apiKey: InjectionKey<AxiosInstance> = Symbol('api-key'); export default boot(async ({ app, store, router, ssrContext }) => { const cookies = process.env.SERVER ? Cookies.parseSSR(ssrContext) : Cookies; const url = process.env.API_URL; const api = axios.create({ baseURL: url }); const { token, refresh } = useAuth(api, cookies); api.interceptors.request.use( function (config) { if (token.value) { config.headers ||= {} config.headers.Authorization = `Bearer ${token.value}`; } return config; }, function (error) { return Promise.reject(error); } ); api.interceptors.response.use( function (response) { return response; }, async function (error) { const res = error.response; switch (res.status) { case 401: if (token.value) { await refresh() if (token.value) { return api.request(error.config); } } break; } return Promise.reject(error); } ); await refresh(); }) 
Enter fullscreen mode Exit fullscreen mode

E mais uma vez, estou colocando em destaque as alterações relevantes.

Boot utilizado na Integração com rotation=true - SSR

7.2 - Integrando a uma API com rotation=false

Voltar ao Topo

Agora iremos falar sobre como utilizar o rotation=false com o modo SSR.

7.2.1 - Preparando o Server Side

Voltar ao Topo

No Server Side, teremos de extender a instancia do webserver que serve a aplicação SSR com uma mini API, podemos utilizar um SSRMiddleware para esta tarefa.:

quasar new ssrmiddleware auth 
Enter fullscreen mode Exit fullscreen mode

importante: O ssrmiddleware deve ser adicionado ao quasar.config

Então iremos adicionar os endpoints /login, /refresh, /logout no nosso middleware

src/utils/index.ts

export const cookieName = 'REFRESH_TOKEN'; export function cookieOptions() { const expires = new Date(); expires.setDate(expires.getDate() + 1); return { path: ['api', 'auth', 'refresh'].join('/'), secure: true, sameSite: 'lax', httpOnly: true, expires: expires, }; } 
Enter fullscreen mode Exit fullscreen mode

src-ssr/middlewares/auth.ts

import { ssrMiddleware } from 'quasar/wrappers' import { Router } from 'express'; import axios from 'axios'; import { cookieName, cookieOptions } from '../src/utils'; export default ssrMiddleware(async ({ app /*, resolveUrlPath, publicPath, render */ }) => { const url = process.env.API_URL; const api = axios.create({ baseURL: url }); const router = Router() router.post('login', async function (req, res) { const { data } = await api.post('/auth/login?flow=client&rotation=false'); res.cookie(cookieName, data.refreshToken, cookieOptions()); res.json({ accessToken: data.accessToken }); }) router.get('refresh', async function (req, res) { let accessToken = '' const refresh = req.cookies[cookieName]; if (refresh) { const { data } = await api.get('/auth/refresh?flow=client'); accessToken = data.value; } return { accessToken } }) router.delete('logout', async function (req, res) { await api.get('/auth/logout?flow=client'); res.clearCookie(cookieName) }) app.use('/api/auth', router) }) 
Enter fullscreen mode Exit fullscreen mode

Note que estamos usando o flow=client no nosso backend, mas na API interna do frontend estamos escrevendo um cookie, e este cookie estará disponível apenas no nosso server-side.

API Interna

7.2.2 - Comunicação entre o Server Side e o Client Side

Voltar ao Topo

Agora vamos ao nosso composable, ele vai permanecer praticamente o mesmo, mas quando estivemos no client-side, ao invés de chamar a API externa, ele irá chamar a API interna.

src/composables/auth.ts

import { ref, computed, Ref } from 'vue' import { useStorage, StorageLike } from '@vueuse/core' import jwtDecode from 'jwt-decode'; import { Cookies } from 'quasar'; import axios from 'axios'; import type { AxiosInstance } from 'axios' import type { JwtPayload } from 'jwt-decode'; import { cookieName, cookieOptions } from 'src/utils'; type Payload = JwtPayload & { roles: string[] } interface LoginRequest { username: string; password: string; } const token = ref(''); const internal = axios.create({ baseURL: '/api/auth/' }); function useAuth (api: AxiosInstance, cookies: Cookies) { async function login (req: LoginRequest) { const { data } = await internal.post('login'); token.value = data.accessToken; } async function refresh (req: LoginRequest) { if (process.env.CLIENT) { const { data } = await internal.get('refresh'); token.value = data.accessToken; } if (process.env.SERVER) { var refreshToken = cookies.get('REFRESH_TOKEN') try { const { data } = await api.get('/auth/refresh?flow=client', { headers: { RefreshToken: refreshToken } }); token.value = data.accessToken; cookies.set(cookieName, data.refreshToken, cookieOptions()); } catch (error) { const res = error.response; if (res.status === 401) { token.value = ''; cookies.remove(cookieName, cookieOptions()); } } } } async function logout (req: LoginRequest) { await internal.delete('logout'); token.value = ''; } const payload = computed(() => { if (!token.value) { return null; } return jwtDecode(token.value) as Payload; }) return { token, payload, login, refresh, logout } } 
Enter fullscreen mode Exit fullscreen mode

E por fim os pontos de interrese:

Integração entre API Interna e Externa

Note que estamos sempre a chamar a API interna, com exceção do refresh, que caso seja executado do server-side irá chamar a API externa diretamente e atualizar o Cookie.

Top comments (1)

Collapse
 
helioh3 profile image
Helio Brito • Edited

Excelente Tobias!