From 39bfc4c2f1087e623a9d9a9d203887ee5ead52d7 Mon Sep 17 00:00:00 2001 From: Gwendal Date: Wed, 25 May 2022 20:14:38 +0200 Subject: [PATCH] feat: refresh token --- src/components/Auth/Login.tsx | 5 +++- src/hooks/auth/access/access.ts | 5 ++++ src/hooks/auth/refreshToken.ts | 23 +++++++++++++++++ src/hooks/auth/register.ts | 6 ++--- src/hooks/auth/responses/LoginResponse.ts | 9 +++++++ src/hooks/common/request.ts | 30 ++++++++++++++++++++--- 6 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 src/hooks/auth/refreshToken.ts create mode 100644 src/hooks/auth/responses/LoginResponse.ts diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index 222feea..13e06d0 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -9,12 +9,14 @@ import { useToasts } from 'react-toast-notifications'; import { useTranslation } from 'react-i18next'; import { isAuthorized, logout } from '../../hooks/auth/access/access'; import { Role } from '../../hooks/auth/access/Roles'; +import { useRefreshToken } from '../../hooks/auth/refreshToken'; const Login: React.FC = () => { const { t } = useTranslation(); const { addToast } = useToasts(); const navigate = useNavigate(); const { token, setToken } = useToken(); + const { setRefreshToken } = useRefreshToken(); useEffect(() => { if (token) { @@ -40,7 +42,8 @@ const Login: React.FC = () => { try { const response = await login(request); - setToken(response); + setToken(response.token); + setRefreshToken(response.refreshToken); const isAuthorize = isAuthorized(Role.ADMIN); if (!isAuthorize) { logout(); diff --git a/src/hooks/auth/access/access.ts b/src/hooks/auth/access/access.ts index fd96e99..3fda93f 100644 --- a/src/hooks/auth/access/access.ts +++ b/src/hooks/auth/access/access.ts @@ -16,6 +16,7 @@ export function isAuthorized(role: Role): boolean { export function logout() { localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); } export function parseJwt(token: string): DecodedToken { @@ -24,3 +25,7 @@ export function parseJwt(token: string): DecodedToken { const base64 = base64Url.replace('-', '+').replace('_', '/'); return JSON.parse(window.atob(base64)); } + +export function cleanToken(token: string): string { + return token.slice(7); +} diff --git a/src/hooks/auth/refreshToken.ts b/src/hooks/auth/refreshToken.ts new file mode 100644 index 0000000..1964053 --- /dev/null +++ b/src/hooks/auth/refreshToken.ts @@ -0,0 +1,23 @@ +import { useState } from 'react'; + +function getRefreshToken(): string { + const tokenString = localStorage.getItem('refresh_token'); + if (tokenString) { + return tokenString; + } + return ''; +} + +function useRefreshToken() { + function setRefreshToken(userToken: string) { + localStorage.setItem('refresh_token', userToken); + } + const [refreshToken] = useState(getRefreshToken()); + + return { + setRefreshToken, + refreshToken, + }; +} + +export { getRefreshToken, useRefreshToken }; diff --git a/src/hooks/auth/register.ts b/src/hooks/auth/register.ts index f0ccc4a..cd7556c 100644 --- a/src/hooks/auth/register.ts +++ b/src/hooks/auth/register.ts @@ -1,8 +1,9 @@ import { commonRequest, loginRequest } from '../common/request'; import LoginRequest from './requests/LoginRequest'; +import LoginResponse from './responses/LoginResponse'; -const login = async (request: LoginRequest): Promise => { - const token = await loginRequest({ +const login = async (request: LoginRequest): Promise => { + return await loginRequest({ url: `/auth/login`, method: 'POST', data: { @@ -10,7 +11,6 @@ const login = async (request: LoginRequest): Promise => { password: request.password, }, }); - return token.slice(7); }; const register = async ( diff --git a/src/hooks/auth/responses/LoginResponse.ts b/src/hooks/auth/responses/LoginResponse.ts new file mode 100644 index 0000000..1398cf5 --- /dev/null +++ b/src/hooks/auth/responses/LoginResponse.ts @@ -0,0 +1,9 @@ +export default class LoginResponse { + token: string; + refreshToken: string; + + constructor(token: string, refreshToken: string) { + this.token = token; + this.refreshToken = refreshToken; + } +} diff --git a/src/hooks/common/request.ts b/src/hooks/common/request.ts index 5bd5c6a..03b28e8 100644 --- a/src/hooks/common/request.ts +++ b/src/hooks/common/request.ts @@ -1,5 +1,8 @@ import axios, { AxiosRequestConfig } from 'axios'; import { getToken } from '../auth/token'; +import LoginResponse from '../auth/responses/LoginResponse'; +import { cleanToken, logout } from '../auth/access/access'; +import { getRefreshToken } from '../auth/refreshToken'; const client = axios.create({ baseURL: process.env.REACT_APP_API_ENDPOINT, @@ -12,7 +15,21 @@ const authenticatedRequest = (options: AxiosRequestConfig) => { // commonRequest(options); const onSuccess = (response: any) => response; const onError = (error: any) => { - // optionaly catch errors and add some additional logging here + if (error.response.status === 403) { + client.defaults.headers.common.Authorization = `Bearer ${getRefreshToken()}`; + client({ timeout: 5000, url: `/auth/refresh/token`, method: 'GET' }) + .then((response: any) => { + localStorage.setItem( + 'access_token', + cleanToken(response.headers['authorization']), + ); + }) + .catch((error: any) => { + logout(); + return error; + }); + } + // optionally catch errors and add some additional logging here return error; }; @@ -28,7 +45,7 @@ const commonRequest = (options: AxiosRequestConfig) => { }; const onError = (error: any) => { throw error; - // optionaly catch errors and add some additional logging here + // optionally catch errors and add some additional logging here // return error; }; @@ -38,10 +55,15 @@ const commonRequest = (options: AxiosRequestConfig) => { }; const loginRequest = (options: AxiosRequestConfig) => { - const onSuccess = (response: any) => response.headers['authorization']; + const onSuccess = (response: any) => { + return new LoginResponse( + cleanToken(response.headers['authorization']), + cleanToken(response.headers['refresh-token']), + ); + }; const onError = (error: any) => { throw error; - // optionaly catch errors and add some additional logging here + // optionally catch errors and add some additional logging here // return error; };