Índice
- Source - Backend
- 1 - Backend - Conhecendo o Backend
- 2 - Backend - Endpoint de Login
- 3 - Backend - Endpoint de Refresh
- 4 - Backend - Endpoint de Logout
- 5 - Frontend - Entendendo as Vulnerabilidades
- 6 - Frontend - Protegendo um App SPA
- 7 - Frontend - Protegendo um App SSR
1 - Backend - Conhecendo o Backend
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.
2 - Backend - Endpoint de Login
O primeiro endpoint que iremos visitar é o de login:
2.1 - Parâmetros do Login
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 novorefresh_token
além de umaccess_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 dorefresh_token
, nesta caso orefresh_token
é persistido em umcookie
seguro. -
client: a API irá devolver o
refresh_token
ao cliente, neste caso a API não irá persistir e/ou gerenciar orefresh_token
, ficando a cargo do cliente a segurança do mesmo.
2.2 - Analisando o Login
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
Veja, que a API retornou a seguinte resposta:
{
"accessToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwMTgwY2E2My0yYjUyLTRlYjAtOTJlNi1mMWNlMzYxM2IxNjYiLCJuYW1lIjoiVG9iaWFzIE1lc3F1aXRhIiwiZW1haWwiOiJtZUB0b2JpYXNtZXNxdWl0YS5kZXYiLCJyb2xlcyI6WyJkZXZlbG9wZXIiLCJhZG1pbiIsInVzZXIiXSwiaWF0IjoxNjc4MDQ4NjQ0LCJleHAiOjE2NzgwNDg2NzQsImF1ZCI6Imh0dHBzOi8vand0LXNhbXBsZS50b2JpYXNtZXNxdWl0YS5kZXYiLCJpc3MiOiJodHRwczovL2p3dC1zYW1wbGUudG9iaWFzbWVzcXVpdGEuZGV2IiwianRpIjoiODBmMDQxMDctMGE4MS00ZWNhLWE1ODItNWM3NzViNDQ4MWJmIn0.GIAeo5sfrA5ksKp5msAJCAxLo4ivHxoHrTz6j9JBWyVgg3BmHN0veF24V27PG_K8yqjMiCKRONuNwkdZHENdQg"
}
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"
}]
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'
}
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
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"
}]
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'
}
2.2.2 - Segundo Cenario - 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"
}
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"
}]
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
Agora que o login
já foi bem dissecado, porém voltar a nossa atenção para o refresh
3.1 - Parâmetros do Refresh
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 headerRefreshToken
.
3.2 - Analisando o Refresh
Agora, vamos dá continuidade aos cenários apresentados no login.
3.2.1 - Primeiro Cenário - 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
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
E por fim temos o endpoint /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
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
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
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();
})
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.
6.2 - Integrando a uma API com flow=server
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
}
}
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.
6.3 - Integrando a uma API com rotation=true
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
}
}
Neste caso, o refresh token é gerenciado pelo frontend, e por isto podemos ver que o mesmo é lido, escrito e persistido.
7 - Frontend - Protegendo um App SSR
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
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
}
}
E na imagem abaixo, esta em destaque as alterações no composable:
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();
})
E mais uma vez, estou colocando em destaque as alterações relevantes.
7.2 - Integrando a uma API com rotation=false
Agora iremos falar sobre como utilizar o rotation=false
com o modo SSR
.
7.2.1 - Preparando o Server Side
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
importante: O
ssrmiddleware
deve ser adicionado aoquasar.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,
};
}
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)
})
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
.
7.2.2 - Comunicação entre o Server Side e o Client Side
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
}
}
E por fim os pontos de interrese:
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)
Excelente Tobias!