Authentication Flow & Implementation
Overview
This document provides a detailed technical walkthrough of the StockEase authentication system, including the login process, JWT token handling, and credential validation.
Login Component
(src/pages/LoginPage.tsx)
Component Structure
const LoginPage: React.FC = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { t, i18n } = useTranslation();
const navigate = useNavigate();State Variables:
usernameβ User input for username/emailpasswordβ User input for passwordloadingβ Shows skeleton loader during API callerrorβ Error message to display (401, network, etc.)
Auto-Redirect for Logged-In Users
useEffect(() => {
const role = localStorage.getItem('role');
if (role) {
navigate(role === 'ROLE_ADMIN' ? '/admin' : '/user', { replace: true });
}
}, [navigate]);Logic:
- Check if user already has a role in localStorage
- If logged in β redirect to dashboard (no need to login again)
- If not logged in β show login page
Benefits:
- Prevents users from seeing login page after refresh if session active
- Automatically routes to correct dashboard based on role
replace: trueremoves login page from browser history
Login Submission Handler
const handleLogin = async () => {
// Step 1: Validate input
if (!username || !password) {
setError(t('login.error.emptyFields'));
return;
}
setLoading(true);
setError(null);
try {
// Step 2: Call auth service
const response = await login(username, password);
const { token, role } = response;
// Step 3: Store credentials
localStorage.setItem('token', token);
localStorage.setItem('username', username);
localStorage.setItem('role', role);
// Step 4: Navigate to dashboard
navigate(role === 'ROLE_ADMIN' ? '/admin' : '/user', { replace: true });
} catch (err) {
setLoading(false);
// Step 5: Handle errors
if (axios.isAxiosError(err)) {
setError(
err.response?.status === 401
? t('login.error.invalidCredentials')
: t('login.error.unexpectedError')
);
} else {
setError(t('login.error.unexpectedError'));
}
}
};Execution Flow:
Step 1: Input Validation
ββ Check username and password not empty
ββ If empty, show error and exit
Step 2: API Call to Backend
ββ POST /api/auth/login with { username, password }
ββ Backend validates credentials in database
ββ Backend returns JWT token or 401 error
Step 3: Store Session Data
ββ localStorage.setItem('token', jwt)
ββ localStorage.setItem('role', 'ROLE_ADMIN' or 'ROLE_USER')
ββ localStorage.setItem('username', username)
Step 4: Navigate to Dashboard
ββ If admin β /admin
ββ If user β /user
Step 5: Error Handling
ββ 401 error β "Invalid credentials"
ββ Network error β "Unexpected error"
ββ Show error message to user
Auth Service
(src/api/auth.ts)
Login Function
export const login = async (
username: string,
password: string
): Promise<{ token: string; role: string }> => {
// POST credentials to backend
const response = await apiClient.post<LoginResponse>(
'/api/auth/login',
{ username, password }
);
// Validate response
if (!response.data.success) {
throw new Error(response.data.message || 'Login failed');
}
// Extract JWT token
const token = response.data.data;
// Decode JWT payload to get role
const decodedPayload = JSON.parse(atob(token.split('.')[1]));
const role = decodedPayload.role;
return { token, role };
};Step-by-Step Breakdown
1. API Request
const response = await apiClient.post<LoginResponse>(
'/api/auth/login',
{ username, password }
);What happens:
- Axios sends POST request to
/api/auth/login - Request body:
{ "username": "admin", "password": "SecurePass123!" } - Request headers:
Content-Type: application/json - No Authorization header (not authenticated yet)
Backend expectation:
POST /api/auth/login HTTP/1.1
Host: api.stockease.com
Content-Type: application/json
{
"username": "admin",
"password": "SecurePass123!"
}
2. Response Validation
if (!response.data.success) {
throw new Error(response.data.message || 'Login failed');
}Expected response format:
{
"success": true,
"message": "Login successful",
"data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiUk9MRV9BRE1JTiIsImV4cCI6MTczMjEzOTIwMH0...."
}Error cases:
{
"success": false,
"message": "Invalid credentials"
}Axios Interceptor will also:
- Return
error.response?.status === 401for invalid credentials - Caught in LoginPage and shown as "Invalid credentials"
3. JWT Token Extraction
const token = response.data.data;
// token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiUk9MRV9BRE1JTiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"JWT Structure (3 parts separated by
.):
Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 β Header (algorithm, type)
.
eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiUk9MRV9BRE1JTiJ9 β Payload (claims)
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c β Signature (backend verifies)
4. Payload Decoding
const decodedPayload = JSON.parse(atob(token.split('.')[1]));
// atob() = "Ascii to Binary" = base64 decode
// token.split('.')[1] = the middle part (payload)
// Decoded:
// {
// "user": "admin",
// "role": "ROLE_ADMIN",
// "exp": 1732139200,
// "iat": 1732052800
// }β οΈ Security Note:
- Payload is Base64 encoded, NOT encrypted
- Visible to anyone who has the token
- Never put secrets in JWT payload
- Backend verifies signature using secret key (not done in frontend)
5. Role Extraction
const role = decodedPayload.role;
// role = "ROLE_ADMIN" or "ROLE_USER"
return { token, role };Complete Login Sequence Diagram
Browser Frontend App Backend API
β β β
β User clicks Login β β
ββββββββββββββββββββββββββββββ>β β
β β β
β Show Login Form β β
β<ββββββββββββββββββββββββββββββ€ β
β β β
β User enters credentials β β
β Clicks "Login" button β β
ββββββββββββββββββββββββββββββ>β β
β β β
β β POST /api/auth/login β
β β {username, password} β
β ββββββββββββββββββββββ>β
β β β
β β Database lookup β
β β Verify password β
β β Generate JWT β
β β β
β β 200 OK β
β β {token: "jwt..."} β
β β<ββββββββββββββββββββββ€
β β β
β β Decode JWT β
β β Extract role β
β β Store in localStorageβ
β β β
β Show Dashboard β β
β<ββββββββββββββββββββββββββββββ€ β
β β β
β Request /api/products β β
ββββββββββββββββββββββββββββββ>β β
β β β
β β GET /api/products β
β β Authorization: Bearerβ
β ββββββββββββββββββββββ>β
β β β
β Display products β 200 OK β
β<ββββββββββββββββββββββββββββββ<ββββββββββββββββββββββ€
β β β
Error Scenarios
Scenario 1: Invalid Credentials (401)
// User enters wrong password
POST /api/auth/login
{ "username": "admin", "password": "WrongPassword123!" }
β 401 Unauthorized
{ "success": false, "message": "Invalid credentials" }
// LoginPage catches error:
if (axios.isAxiosError(err) && err.response?.status === 401) {
setError(t('login.error.invalidCredentials')); // "Invalid credentials"
}User sees: β "Invalid credentials or account locked"
Scenario 2: Empty Field Validation
// User doesn't enter password
handleLogin() called
β
Check: !password? β TRUE
β
setError('Fields are required')
β
Return early (don't call API)
// No API request made, immediate feedbackUser sees: β "Please fill in all fields"
Scenario 3: Network Error
// User loses internet connection
POST /api/auth/login
β
Network timeout after 120 seconds
β
error.code === 'ECONNABORTED'
error.response === undefined (no response from server)
β
setError('Unexpected error')User sees: β "An unexpected error occurred"
Password Security & Validation
Frontend Validation (UX)
// In login form, validate password requirements
export const validatePassword = (password: string) => {
const errors = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters');
}
if (!/[A-Z]/.test(password)) {
errors.push('Must contain uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Must contain lowercase letter');
}
if (!/\d/.test(password)) {
errors.push('Must contain digit');
}
if (!/[!@#$%^&*()_+=[\]{};':"\\|,.<>/?]/.test(password)) {
errors.push('Must contain special character');
}
return { valid: errors.length === 0, errors };
};Requirements:
- 8+ characters minimum
- At least 1 UPPERCASE letter
- At least 1 lowercase letter
- At least 1 digit (0-9)
- At least 1 special character (!@#$%^&* etc.)
Backend Validation (Security)
Backend must also validate:
- β Password against hash in database (bcrypt/scrypt)
- β Account not locked (failed login attempts)
- β Account not suspended
- β Password meets complexity requirements
Password Best Practices
β DO:
- β Enforce complexity requirements
- β Hash passwords with bcrypt/scrypt (backend)
- β Never log passwords
- β Use HTTPS for transmission
- β Implement rate limiting (max 5 failed attempts)
- β Implement account lockout (30 min after failures)
β DON'T:
- β Send password in subsequent requests
- β Store password in localStorage
- β Display password requirements on submit (show during input)
- β Transmit over HTTP (use HTTPS only)
- β Use weak hashing (MD5, SHA1)
- β Store plaintext passwords
Token Lifetime
Token Generation
Backend generates JWT with expiration:
Header: { alg: "HS256", typ: "JWT" }
Payload: {
user: "admin",
role: "ROLE_ADMIN",
iat: 1732052800, β Issued at (current time)
exp: 1732139200 β Expires in 24 hours
}
Signature: HMAC-SHA256(header.payload, secret)
Token Expiration
Current Implementation:
- No automatic refresh
- Token expires after backend-defined duration (usually 24 hours)
- Expired token causes 401 error
- User must login again
Process:
User makes request with expired token
β
Backend returns: 401 Unauthorized (token expired)
β
Response interceptor: localStorage.removeItem('token')
β
User redirected to login page
β
User must re-authenticate
Recommended Future Enhancement:
// Implement refresh token mechanism
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Try to refresh token
const newToken = await refreshToken();
if (newToken) {
// Retry original request with new token
error.config.headers.Authorization = `Bearer ${newToken}`;
return apiClient(error.config);
} else {
// Refresh failed, logout
localStorage.removeItem('token');
}
}
throw error;
}
);Security Considerations
Secure:
- β Credentials sent only during login (not stored/reused)
- β Token sent via Authorization header (not in URL)
- β Token transmitted over HTTPS (encrypted in transit)
- β Backend verifies JWT signature
- β 401 errors trigger automatic logout
Improvements Needed:
- β οΈ localStorage accessible to JavaScript (XSS risk)
- β οΈ No HttpOnly cookies (cannot mitigate XSS completely)
- β οΈ No automatic token refresh (user must relogin when expired)
- β οΈ No rate limiting on frontend (backend responsibility)
- β οΈ No account lockout on frontend (backend responsibility)
Recommendations:
- Use HttpOnly Cookies (if possible with backend support)
- Implement Token Refresh (reduce token lifetime)
- Add Rate Limiting (backend, limit failed attempts)
- Implement Account Lockout (backend, after N failed attempts)
- Add CSRF Protection (if using cookies)
Related Files
- Login Component:
src/pages/LoginPage.tsx - Auth Service:
src/api/auth.ts - API Client:
src/services/apiClient.ts - App Routes:
src/App.tsx - Password Validation:
src/__tests__/utils/validation-rules/auth-validation.test.ts
Last Updated: November 13, 2025
Status: Production-Ready Implementation