Auth provider added to the project
This commit is contained in:
119
src/providers/AuthProvider.tsx
Normal file
119
src/providers/AuthProvider.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
// useAuth.tsx
|
||||
import { AuthContext } from '@/contexts/AuthContext';
|
||||
import type { GenerateTokenResponse } from '@/features/authorization/api/identityAPI';
|
||||
import axios from 'axios';
|
||||
import { useEffect, useState, type ReactNode } from 'react';
|
||||
|
||||
export const ACCESS_TOKEN_KEY: 'access_token' = 'access_token' as const;
|
||||
export const REFRESH_TOKEN_KEY: 'refresh_token' = 'refresh_token' as const;
|
||||
export const EXPIRES_IN_KEY: 'expires_in' = 'expires_in' as const;
|
||||
|
||||
let inMemoryToken: string | null = null;
|
||||
let expiresAt = 0;
|
||||
|
||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [accessToken, setAccessToken] = useState<string | null>(
|
||||
sessionStorage.getItem(ACCESS_TOKEN_KEY),
|
||||
);
|
||||
|
||||
// Initialize from sessionStorage (page reload)
|
||||
useEffect(() => {
|
||||
const token = sessionStorage.getItem(ACCESS_TOKEN_KEY);
|
||||
const exp = Number(sessionStorage.getItem(EXPIRES_IN_KEY) || 0);
|
||||
|
||||
if (token && Date.now() < exp) {
|
||||
inMemoryToken = token;
|
||||
expiresAt = exp;
|
||||
setAccessToken(token);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Background refresh
|
||||
useEffect(() => {
|
||||
const handleRefreshTokenIfNeeded = async () => {
|
||||
if (!inMemoryToken) return;
|
||||
if (Date.now() < expiresAt - 60_000) return; // still valid (buffer)
|
||||
|
||||
await refreshAccessToken();
|
||||
setAccessToken(inMemoryToken);
|
||||
};
|
||||
|
||||
handleRefreshTokenIfNeeded();
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
await handleRefreshTokenIfNeeded();
|
||||
}, 30_000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
function login(tokens: {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
expires_in: number;
|
||||
}) {
|
||||
inMemoryToken = tokens.access_token;
|
||||
expiresAt = Date.now() + tokens.expires_in * 1000;
|
||||
|
||||
sessionStorage.setItem(ACCESS_TOKEN_KEY, tokens.access_token);
|
||||
sessionStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh_token);
|
||||
sessionStorage.setItem(EXPIRES_IN_KEY, String(expiresAt));
|
||||
|
||||
setAccessToken(tokens.access_token);
|
||||
}
|
||||
|
||||
function logout() {
|
||||
inMemoryToken = null;
|
||||
expiresAt = 0;
|
||||
sessionStorage.clear();
|
||||
setAccessToken(null);
|
||||
}
|
||||
|
||||
function getToken() {
|
||||
return inMemoryToken;
|
||||
}
|
||||
|
||||
async function refreshAccessToken() {
|
||||
const refreshToken = sessionStorage.getItem(REFRESH_TOKEN_KEY);
|
||||
if (!refreshToken) {
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await axios.post<GenerateTokenResponse>(
|
||||
import.meta.env.VITE_IDENTITY_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_id: import.meta.env.VITE_IDENTITY_CLIENT_ID, // from your token payload
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (result.data.access_token) {
|
||||
inMemoryToken = result.data.access_token;
|
||||
expiresAt = Date.now() + result.data.expires_in * 1000;
|
||||
|
||||
sessionStorage.setItem(ACCESS_TOKEN_KEY, inMemoryToken as string);
|
||||
sessionStorage.setItem(EXPIRES_IN_KEY, String(expiresAt));
|
||||
|
||||
setAccessToken(inMemoryToken);
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
} catch {
|
||||
logout();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ accessToken, getToken, login, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user