Skip to content

Instantly share code, notes, and snippets.

@feelchi1star
Created January 26, 2026 01:11
Show Gist options
  • Select an option

  • Save feelchi1star/3211f2c36cab979aba2fc5f5a60760f1 to your computer and use it in GitHub Desktop.

Select an option

Save feelchi1star/3211f2c36cab979aba2fc5f5a60760f1 to your computer and use it in GitHub Desktop.
Professional Axios and SSR authentication in React Router
// ============================================
// API Setup (axios-config.js)
// ============================================
import axios from 'axios';
import { redirect } from 'react-router-dom';
const api = axios.create({
baseURL: 'https://api.something.vercel.app',
withCredentials: true,
});
// Handle 401 and refresh token
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
await api.post('/auth/refresh');
return api(originalRequest);
} catch (refreshError) {
localStorage.removeItem('isLogin');
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
export default api;
// ============================================
// Server-Side Loader - Forwarding Cookies
// ============================================
import type { Route } from "./+types/dashboard";
// Helper to get auth status (server-side)
async function getAuthStatus(request: Request) {
// Get cookies from the incoming browser request
const cookieHeader = request.headers.get('Cookie');
try {
const response = await fetch('https://api.something.vercel.app/auth/status', {
headers: {
'Cookie': cookieHeader || '', // Forward cookies to API
},
credentials: 'include',
});
const data = await response.json();
return data;
} catch (error) {
console.error('Auth status check failed:', error);
return { isLogin: false, permissions: [] };
}
}
// Dashboard loader with auth check
export async function loader({ request, params }: Route.LoaderArgs) {
// Get auth status by forwarding cookies
const auth = await getAuthStatus(request);
// Not logged in - redirect to login
if (!auth.isLogin) {
throw redirect('/login');
}
// Check permissions
if (!auth.permissions.includes('view_dashboard')) {
throw redirect('/unauthorized');
}
// Fetch dashboard data (forward cookies again)
const cookieHeader = request.headers.get('Cookie');
const dashboardData = await fetch('https://api.something.vercel.app/dashboard/stats', {
headers: {
'Cookie': cookieHeader || '',
},
credentials: 'include',
});
const stats = await dashboardData.json();
return {
auth,
stats,
};
}
// ============================================
// Reusable Auth Middleware Pattern
// ============================================
import { redirect } from 'react-router';
interface AuthData {
isLogin: boolean;
permissions: string[];
role?: string;
user?: {
id: string;
email: string;
name: string;
};
}
// Reusable function to check auth
export async function requireAuth(
request: Request,
requiredPermissions: string[] = []
): Promise<AuthData> {
const cookieHeader = request.headers.get('Cookie');
const response = await fetch('https://api.something.vercel.app/auth/status', {
headers: {
'Cookie': cookieHeader || '',
},
credentials: 'include',
});
const auth: AuthData = await response.json();
// Not logged in
if (!auth.isLogin) {
throw redirect('/login');
}
// Check permissions
if (requiredPermissions.length > 0) {
const hasPermission = requiredPermissions.some(perm =>
auth.permissions.includes(perm)
);
if (!hasPermission) {
throw redirect('/unauthorized');
}
}
return auth;
}
// Helper to make authenticated API calls
export async function authenticatedFetch(
request: Request,
url: string,
options: RequestInit = {}
) {
const cookieHeader = request.headers.get('Cookie');
return fetch(url, {
...options,
headers: {
...options.headers,
'Cookie': cookieHeader || '',
},
credentials: 'include',
});
}
// ============================================
// Example Loaders Using Helpers
// ============================================
// Dashboard loader
export async function dashboardLoader({ request }: Route.LoaderArgs) {
// Check auth with required permissions
const auth = await requireAuth(request, ['view_dashboard']);
// Fetch dashboard data
const response = await authenticatedFetch(
request,
'https://api.something.vercel.app/dashboard/stats'
);
const stats = await response.json();
return { auth, stats };
}
// Profile loader (no special permissions needed)
export async function profileLoader({ request }: Route.LoaderArgs) {
const auth = await requireAuth(request);
const response = await authenticatedFetch(
request,
'https://api.something.vercel.app/user/profile'
);
const profile = await response.json();
return { auth, profile };
}
// Admin loader
export async function adminLoader({ request }: Route.LoaderArgs) {
const auth = await requireAuth(request, ['admin_access']);
const response = await authenticatedFetch(
request,
'https://api.something.vercel.app/admin/users'
);
const users = await response.json();
return { auth, users };
}
// Public loader (check auth but don't require it)
export async function homeLoader({ request }: Route.LoaderArgs) {
const cookieHeader = request.headers.get('Cookie');
let auth: AuthData = { isLogin: false, permissions: [] };
try {
const response = await fetch('https://api.something.vercel.app/auth/status', {
headers: { 'Cookie': cookieHeader || '' },
credentials: 'include',
});
auth = await response.json();
} catch (error) {
// Not logged in is fine for public pages
}
return { auth };
}
// Login loader (redirect if already logged in)
export async function loginLoader({ request }: Route.LoaderArgs) {
const cookieHeader = request.headers.get('Cookie');
try {
const response = await fetch('https://api.something.vercel.app/auth/status', {
headers: { 'Cookie': cookieHeader || '' },
credentials: 'include',
});
const auth = await response.json();
if (auth.isLogin) {
throw redirect('/dashboard');
}
} catch (error) {
// Not logged in, continue to login page
}
return null;
}
// ============================================
// Product Loader Example (Your Use Case)
// ============================================
export async function productLoader({ request, params }: Route.LoaderArgs) {
// Check if user is logged in (forward cookies)
const auth = await requireAuth(request);
// Fetch product with authentication
const response = await authenticatedFetch(
request,
`https://api.something.vercel.app/products/${params.pid}`
);
if (!response.ok) {
throw new Response('Product not found', { status: 404 });
}
const product = await response.json();
return { auth, product };
}
// ============================================
// Backend Requirements (api.something.vercel.app)
// ============================================
// Your backend MUST accept forwarded cookies
import express from 'express';
import cookieParser from 'cookie-parser';
import jwt from 'jsonwebtoken';
const app = express();
app.use(cookieParser());
// Auth status endpoint
app.get('/auth/status', async (req, res) => {
// Cookies are automatically parsed from the Cookie header
const { accessToken, refreshToken } = req.cookies;
if (!accessToken && !refreshToken) {
return res.json({ isLogin: false, permissions: [] });
}
try {
const decoded = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET);
const user = await getUserById(decoded.userId);
return res.json({
isLogin: true,
permissions: user.permissions,
role: user.role,
user: { id: user.id, email: user.email, name: user.name },
});
} catch (error) {
// Handle refresh token...
return res.json({ isLogin: false, permissions: [] });
}
});
// ============================================
// IMPORTANT: CORS Configuration for SSR
// ============================================
// Your API needs to allow requests from your React Router server
import cors from 'cors';
app.use(cors({
// Allow your React Router server domain
origin: [
'https://love.vercel.app', // Production
'http://localhost:3000', // Local dev
],
credentials: true, // Important!
}));
// ============================================
// Complete Flow Diagram
// ============================================
/*
1. Browser → React Router Server (love.vercel.app)
Request: GET /dashboard
Headers: Cookie: accessToken=abc123; refreshToken=xyz789
2. React Router Server → API Server (api.something.vercel.app)
Request: GET /auth/status
Headers: Cookie: accessToken=abc123; refreshToken=xyz789
3. API Server → React Router Server
Response: { isLogin: true, permissions: [...] }
4. React Router Server → API Server
Request: GET /dashboard/stats
Headers: Cookie: accessToken=abc123; refreshToken=xyz789
5. API Server → React Router Server
Response: { stats: {...} }
6. React Router Server → Browser
Response: Rendered HTML with data
*/
// ============================================
// Key Points
// ============================================
/*
✅ Use request.headers.get('Cookie') to get cookies from browser
✅ Forward Cookie header to your API in fetch() calls
✅ Your API backend receives cookies normally (req.cookies)
✅ No special cookie handling needed - just forward the header
✅ Works with httpOnly cookies (most secure)
✅ Each loader can check auth independently
✅ Server-to-server communication is secure
SECURITY: ✅✅✅✅✅
- Cookies never exposed to client JavaScript
- Server forwards cookies securely
- httpOnly cookies protected from XSS
- Same security as traditional server-side apps
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment