API Security & Configuration
Axios API Client Setup
The StockEase Frontend uses Axios as the
HTTP client with a centralized configuration in
src/services/apiClient.ts. This ensures consistent
security practices across all API calls.
Configuration
Base Setup
/**
* Configured Axios instance for API requests
* @type {import('axios').AxiosInstance}
*/
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8081/api',
timeout: 120000, // 2 minutes
});Key Settings:
| Setting | Value | Purpose |
|---|---|---|
baseURL |
Environment variable or localhost | Backend API endpoint |
timeout |
120,000 ms (2 min) | Prevent hanging requests |
headers |
(set by interceptor) | Authorization and content-type |
Request Interceptor
The request interceptor automatically attaches JWT tokens to outgoing requests.
apiClient.interceptors.request.use(
(config) => {
// 1. Retrieve token from localStorage
const token = localStorage.getItem('token');
// 2. Attach Bearer token if available
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// 3. Log request for debugging
console.log('API Request:', config);
return config;
},
(error) => {
console.error('Request Error:', error);
throw error;
}
);How It Works
Step 1: Token Retrieval
- Checks
localStorage.getItem('token') - Returns
nullif no token stored (guest requests fail) - Returns JWT string if token available
Step 2: Bearer Token Attachment
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Step 3: Logging
- Logs full request config to console
- Useful for debugging API issues
- β οΈ Warning: Includes headers with token in dev tools
Response Interceptor
The response interceptor handles successful responses, logs them, and manages error cases (especially 401 Unauthorized).
apiClient.interceptors.response.use(
(response) => {
// Successful response (2xx status)
console.log('API Response:', response);
return response;
},
(error) => {
// Failed response (4xx, 5xx) or network error
console.error('API Error:', error.response?.data || error.message);
// Clear token on unauthorized access
if (error.response?.status === 401) {
localStorage.removeItem('token');
console.warn('Unauthorized access - redirecting to login');
}
throw error; // Propagate to component
}
);Error Handling Flow
Case 1: 401 Unauthorized
Backend Response: 401 (token expired or invalid)
β
Response Interceptor catches error
β
Check: error.response?.status === 401? β YES
β
localStorage.removeItem('token') β Session cleared
β
Console log: "Unauthorized access - redirecting to login"
β
Throw error to component
β
Component shows login redirect
Case 2: Other 4xx/5xx Errors
Backend Response: 400, 403, 404, 500, etc.
β
Response Interceptor catches error
β
Check: status === 401? β NO
β
Log error details to console
β
Throw error to component (component handles specific error)
Case 3: Network Error
Network Failure: No connection, timeout, DNS failure
β
Axios catches error
β
error.response is undefined (no response from server)
β
Log: error.message (e.g., "Network Error", "Timeout of 120000ms exceeded")
β
Throw error to component
Security Features
β Automatic Bearer Token Attachment
Benefit: Developers don't need to manually add headers to each request
// Developer writes:
await apiClient.get('/api/products')
// Axios automatically sends:
GET /api/products HTTP/1.1
Authorization: Bearer <token>β 401 Error Handling
Benefit: Automatic session cleanup on token expiration
// When token expires:
1. Request sent with expired token
2. Backend rejects with 401
3. Interceptor removes token from localStorage
4. User redirected to login page
5. No stale token in memoryβ Request Timeout
Benefit: Prevents infinite hanging requests
timeout: 120000 // 2 minutes
// If request takes > 2 min:
// β Automatically aborted
// β Error thrown to component
// β User can retryβ Centralized Configuration
Benefit: All API calls use same security settings
// Instead of:
fetch('https://api.stockease.com/products', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
})
// Use:
apiClient.get('/products') // β
All security applied automaticallyUsage Examples
Authentication (Login)
// src/api/auth.ts
import apiClient from '../services/apiClient';
export const login = async (username: string, password: string) => {
// Request is sent without token (login endpoint)
const response = await apiClient.post('/api/auth/login', {
username,
password,
});
return response.data;
};HTTP Request:
POST /api/auth/login HTTP/1.1
Host: api.stockease.com
Content-Type: application/json
(No Authorization header - not logged in yet)
Body:
{
"username": "admin",
"password": "SecurePass123!"
}
Protected Endpoints (After Login)
// src/api/ProductService.ts
export const fetchProducts = async () => {
// Request interceptor automatically adds token
const response = await apiClient.get('/api/products');
return response.data;
};HTTP Request:
GET /api/products HTTP/1.1
Host: api.stockease.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
(Automatically attached by interceptor)
Token Management
Token Storage
Current Implementation: localStorage
// In LoginPage.tsx
localStorage.setItem('token', token);
localStorage.setItem('username', username);
localStorage.setItem('role', role);Pros:
- Simple to implement
- Works across page refreshes
- Works with Axios interceptor
Cons:
- β οΈ Accessible to JavaScript (XSS attack risk)
- No built-in expiration
- No HttpOnly flag (browser feature limitation)
Recommended: HttpOnly Cookies (Backend)
// Better approach: Backend sets HttpOnly cookie
Set-Cookie: token=<jwt>; HttpOnly; Secure; SameSite=Strict
// Frontend accesses token from cookie (automatic with Axios)
// No JavaScript access needed
// Protected from XSS attacksToken Retrieval
// Request interceptor retrieves token
const token = localStorage.getItem('token');
// If token exists, attach it
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}Token Removal
// Response interceptor removes token on 401
if (error.response?.status === 401) {
localStorage.removeItem('token');
}Environment Configuration
Development
(VITE_API_BASE_URL=http://localhost:8081)
// vite.config.ts proxy setup
server: {
proxy: {
'/api': {
target: 'http://localhost:8081',
changeOrigin: true, // β
Changes Host header to backend
secure: false, // β
Allows self-signed certificates
},
},
},Request Flow:
Browser Request
β
http://localhost:5173/api/products (dev server)
β
Proxy Rule: /api β http://localhost:8081
β
http://localhost:8081/api/products (backend)
Production
(VITE_API_BASE_URL=https://api.stockease.com)
// No proxy needed
baseURL: 'https://api.stockease.com'Request Flow:
Browser Request
β
https://frontend.stockease.com/ (frontend domain)
β
No proxy, direct HTTPS request
β
https://api.stockease.com/api/products (backend domain)
β
CORS validated by backend
CORS (Cross-Origin Resource Sharing)
Frontend Responsibility
StockEase Frontend sends requests with standard headers. CORS is enforced by the backend, not the frontend.
// Frontend sends standard request
await apiClient.get('https://api.stockease.com/api/products');
// Browser automatically adds:
Origin: https://frontend.stockease.com
// Backend validates origin and responds with:
Access-Control-Allow-Origin: https://frontend.stockease.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-TypeBackend Responsibility
The backend must:
- β
Validate
Originheader - β
Return
Access-Control-Allow-Originheader - β Specify allowed HTTP methods
- β
Specify allowed headers (especially
Authorization)
Example Backend CORS Configuration (Spring Boot):
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://frontend.stockease.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(3600);
}
}Testing CORS
# Preflight request (browser does automatically)
curl -i -X OPTIONS https://api.stockease.com/api/products \
-H "Origin: https://frontend.stockease.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization"
# Expected response headers:
# Access-Control-Allow-Origin: https://frontend.stockease.com
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE
# Access-Control-Allow-Headers: Authorization, Content-TypeError Responses
Handling Specific Error Types
// In components or services
try {
const products = await apiClient.get('/api/products');
} catch (error) {
if (axios.isAxiosError(error)) {
// Axios error
if (error.response?.status === 401) {
// Unauthorized - token expired or invalid
redirect('/login');
} else if (error.response?.status === 403) {
// Forbidden - user lacks permission
show('You do not have permission to access this resource');
} else if (error.response?.status === 404) {
// Not found - endpoint doesn't exist
show('Resource not found');
} else if (error.code === 'ECONNABORTED') {
// Timeout
show('Request timeout - please try again');
} else if (!error.response) {
// Network error
show('Network error - check your connection');
}
} else {
// Non-Axios error
show('Unexpected error occurred');
}
}Security Checklist
- β Bearer token automatically attached to requests
- β Token stored in localStorage (accessible after login)
- β 401 errors clear token and redirect to login
- β Request timeout prevents hanging (2 minutes)
- β All requests go through interceptor (centralized)
- β οΈ Sensitive data logged to console (should filter in production)
- β οΈ localStorage vulnerable to XSS (use HttpOnly cookies when possible)
- β οΈ CORS enforced by backend (no frontend control)
Related Files
- API Client:
src/services/apiClient.ts - Auth Service:
src/api/auth.ts - Product Service:
src/api/ProductService.ts - Configuration:
vite.config.ts - Error Handling: See Error Logging & Monitoring
Last Updated: November 13, 2025
Status: Enterprise-Grade Security
Configuration