feat: migrate authentication storage to use js-cookie for improved token management

This commit is contained in:
estifanos
2026-07-22 09:51:00 +00:00
parent e862d5cd8e
commit a76549b018
5 changed files with 25 additions and 38 deletions

View File

@@ -1,3 +1,4 @@
import Cookies from 'js-cookie';
import { useCallback, useEffect, useRef } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { useNavigate } from 'react-router-dom';
@@ -138,12 +139,8 @@ export default function UserManagementPage() {
style.textContent = UM_OVERRIDES;
document.head.appendChild(style);
const readCookie = (name: string) => {
const m = document.cookie.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'));
return m ? decodeURIComponent(m[1]) : undefined;
};
const token = readCookie('ema-backoffice-auth-token') ?? '';
const refreshToken = readCookie('ema-backoffice-refresh-token');
const token = Cookies.get('ema-backoffice-auth-token') ?? '';
const refreshToken = Cookies.get('ema-backoffice-refresh-token');
const session: UserManagementSessionOptions = {
initialSession: token

View File

@@ -1,3 +1,5 @@
import Cookies from 'js-cookie';
export const SESSION_HEADER_KEYS = {
tenantId: 'x-tenant-id',
organizationUnitId: 'x-organization-unit-id',
@@ -11,17 +13,10 @@ const TOKEN_STORAGE_KEYS = [
'auth-token',
] as const;
function getCookie(name: string): string | undefined {
const match = document.cookie.match(
new RegExp('(?:^|;\\s*)' + name + '=([^;]*)'),
);
return match ? decodeURIComponent(match[1]) : undefined;
}
export function resolveTokenFromStorage(): string | undefined {
// cookie first (backoffice stores tokens there), then localStorage (portal / legacy)
for (const key of TOKEN_STORAGE_KEYS) {
const cookie = getCookie(key);
const cookie = Cookies.get(key);
if (cookie) return cookie;
const stored = localStorage.getItem(key);
if (stored) return stored;

View File

@@ -1,3 +1,4 @@
import Cookies from 'js-cookie';
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import type { ReactNode } from 'react';
import { authStorage } from '../utils/auth-storage';
@@ -7,14 +8,9 @@ interface ProtectedRouteProps {
loginPath?: string;
}
function getTokenFromCookie(): string | undefined {
const match = document.cookie.match(/(?:^|;\s*)auth-token=([^;]*)/);
return match ? decodeURIComponent(match[1]) : undefined;
}
export function ProtectedRoute({ children, loginPath = '/login' }: ProtectedRouteProps) {
const location = useLocation();
const token = authStorage.getToken() ?? getTokenFromCookie();
const token = authStorage.getToken() ?? Cookies.get('auth-token');
if (!token) {
return <Navigate to={loginPath} state={{ from: location }} replace />;

View File

@@ -1,6 +1,6 @@
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
import type { AuthState, AuthUser, CurrentProfile, LoginPayload } from '../types/auth.types';
import { authStorage } from '../utils/auth-storage';
import { authStorage, clearAllCookies } from '../utils/auth-storage';
const initialState: AuthState = {
user: null,
@@ -37,6 +37,7 @@ const authSlice = createSlice({
state.isAuthenticated = false;
state.currentProfile = null;
authStorage.clear();
clearAllCookies()
},
hydrateAuth(state) {
const token = authStorage.getToken();

View File

@@ -1,4 +1,6 @@
const MAX_AGE = 60 * 60 * 24 * 7; // ponytail: cookie lifetime knob; JWT expiry + refresh govern real auth
import Cookies from 'js-cookie';
const COOKIE_EXPIRES_DAYS = 7; // ponytail: cookie lifetime knob; JWT expiry + refresh govern real auth
interface Backend {
get(k: string): string | null;
@@ -13,27 +15,25 @@ const localBackend: Backend = {
};
const cookieBackend: Backend = {
get: (k) => {
const m = document.cookie.match(new RegExp('(?:^|;\\s*)' + k + '=([^;]*)'));
return m ? decodeURIComponent(m[1]) : null;
},
get: (k) => Cookies.get(k) ?? null,
// ponytail: 4KB/cookie cap — auth-user/current-profile JSON must stay under it
set: (k, v) => {
document.cookie = `${k}=${encodeURIComponent(v)}; path=/; Secure; SameSite=Strict; Max-Age=${MAX_AGE}`;
},
remove: (k) => {
document.cookie = `${k}=; path=/; Secure; SameSite=Strict; Max-Age=0`;
},
set: (k, v) =>
Cookies.set(k, v, { path: '/', secure: true, sameSite: 'strict', expires: COOKIE_EXPIRES_DAYS }),
remove: (k) => Cookies.remove(k, { path: '/' }),
};
import Cookies from "js-cookie";
const AUTH_KEY_NAMES = ['auth-token', 'refresh-token', 'auth-user', 'profile-id', 'current-profile'];
let _prefix = "ema-auth";
let _prefix = 'ema-auth';
let backend: Backend = localBackend;
export function configureAuthStorage(prefix: string, useCookies = false) {
_prefix = prefix;
backend = useCookies ? cookieBackend : localBackend;
if (useCookies) {
// one-time cleanup: drop pre-migration localStorage leftovers so tokens live in cookies only
AUTH_KEY_NAMES.forEach((k) => localStorage.removeItem(`${prefix}-${k}`));
}
}
function key(k: string) {
@@ -68,16 +68,14 @@ export const authStorage = {
[key('auth-token'), key('refresh-token'), key('auth-user'), key('profile-id'), key('current-profile')].forEach((k) =>
backend.remove(k),
);
document.cookie =
"auth-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax";
Cookies.remove('auth-token', { path: '/' });
},
};
export const clearAllCookies = () => {
const cookies = Cookies.get();
Object.keys(cookies).forEach((name) => {
Cookies.remove(name);
Cookies.remove(name, { path: "/" });
Cookies.remove(name, { path: '/' });
});
};