- Move 12+ outdated documentation files to docs-archive/ - Keep main directory clean with only essential files - Add archive README explaining the move - Main README.md is now the single source of truth for installation - Focus on Docker deployment as primary method 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
12 KiB
12 KiB
Authentication Implementation Guide
Overview
This guide provides a comprehensive approach to implementing authentication in web applications, covering frontend login components, backend authentication, route protection, and session management.
Frontend Implementation
1. Login Component Structure
// components/LoginForm.jsx
import React, { useState } from 'react';
import { useAuth } from '../context/AuthContext';
const LoginForm = () => {
const [credentials, setCredentials] = useState({ username: '', password: '' });
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const { login } = useAuth();
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
try {
await login(credentials);
} catch (err) {
setError(err.message || 'Login failed');
} finally {
setLoading(false);
}
};
return (
<div className="login-container">
<form onSubmit={handleSubmit} className="login-form">
<h2>Login</h2>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
<input
type="text"
placeholder="Username"
value={credentials.username}
onChange={(e) => setCredentials({...credentials, username: e.target.value})}
required
/>
</div>
<div className="form-group">
<input
type="password"
placeholder="Password"
value={credentials.password}
onChange={(e) => setCredentials({...credentials, password: e.target.value})}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Login'}
</button>
</form>
</div>
);
};
export default LoginForm;
2. Authentication Context
// context/AuthContext.jsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { authAPI } from '../services/authService';
const AuthContext = createContext();
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [token, setToken] = useState(localStorage.getItem('token'));
useEffect(() => {
if (token) {
validateToken();
} else {
setLoading(false);
}
}, [token]);
const validateToken = async () => {
try {
const userData = await authAPI.validateToken(token);
setUser(userData);
} catch (error) {
logout();
} finally {
setLoading(false);
}
};
const login = async (credentials) => {
const response = await authAPI.login(credentials);
const { user: userData, token: authToken } = response;
setUser(userData);
setToken(authToken);
localStorage.setItem('token', authToken);
};
const logout = () => {
setUser(null);
setToken(null);
localStorage.removeItem('token');
};
const value = {
user,
login,
logout,
loading,
isAuthenticated: !!user
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
3. Protected Route Component
// components/ProtectedRoute.jsx
import React from 'react';
import { useAuth } from '../context/AuthContext';
import LoginForm from './LoginForm';
import LoadingSpinner from './LoadingSpinner';
const ProtectedRoute = ({ children }) => {
const { isAuthenticated, loading } = useAuth();
if (loading) {
return <LoadingSpinner />;
}
if (!isAuthenticated) {
return <LoginForm />;
}
return children;
};
export default ProtectedRoute;
4. App Structure with Authentication
// App.jsx
import React from 'react';
import { AuthProvider } from './context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
import MainApp from './components/MainApp';
function App() {
return (
<AuthProvider>
<div className="App">
<ProtectedRoute>
<MainApp />
</ProtectedRoute>
</div>
</AuthProvider>
);
}
export default App;
Backend Implementation
5. Authentication Service
// services/authService.js
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:3001/api';
export const authAPI = {
login: async (credentials) => {
const response = await fetch(`${API_BASE_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Login failed');
}
return response.json();
},
validateToken: async (token) => {
const response = await fetch(`${API_BASE_URL}/auth/validate`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error('Token validation failed');
}
return response.json();
},
logout: async (token) => {
await fetch(`${API_BASE_URL}/auth/logout`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});
}
};
6. API Interceptor for Authenticated Requests
// utils/apiClient.js
import { useAuth } from '../context/AuthContext';
const createAPIClient = () => {
const baseURL = process.env.REACT_APP_API_URL || 'http://localhost:3001/api';
const apiClient = async (endpoint, options = {}) => {
const token = localStorage.getItem('token');
const config = {
headers: {
'Content-Type': 'application/json',
...(token && { 'Authorization': `Bearer ${token}` }),
...options.headers,
},
...options,
};
const response = await fetch(`${baseURL}${endpoint}`, config);
if (response.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
return;
}
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Request failed');
}
return response.json();
};
return apiClient;
};
export default createAPIClient();
Backend Server Implementation (Node.js/Express)
7. Authentication Middleware
// middleware/auth.js
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Access token required' });
}
jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ message: 'Invalid or expired token' });
}
req.user = user;
next();
});
};
module.exports = { authenticateToken };
8. Auth Routes
// routes/auth.js
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { authenticateToken } = require('../middleware/auth');
const router = express.Router();
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Mock user database (replace with your database)
const users = [
{
id: 1,
username: 'admin',
password: '$2b$10$hash', // bcrypt hash of 'password'
email: 'admin@example.com'
}
];
// Login endpoint
router.post('/login', async (req, res) => {
const { username, password } = req.body;
try {
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = jwt.sign(
{ id: user.id, username: user.username },
JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({
user: { id: user.id, username: user.username, email: user.email },
token
});
} catch (error) {
res.status(500).json({ message: 'Server error' });
}
});
// Token validation endpoint
router.get('/validate', authenticateToken, (req, res) => {
const user = users.find(u => u.id === req.user.id);
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.json({
id: user.id,
username: user.username,
email: user.email
});
});
// Logout endpoint
router.post('/logout', authenticateToken, (req, res) => {
// In a real application, you might want to blacklist the token
res.json({ message: 'Logged out successfully' });
});
module.exports = router;
CSS Styles
9. Login Form Styles
/* styles/login.css */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f5f5f5;
}
.login-form {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
}
.login-form h2 {
text-align: center;
margin-bottom: 2rem;
color: #333;
}
.form-group {
margin-bottom: 1rem;
}
.form-group input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.form-group input:focus {
outline: none;
border-color: #007bff;
}
.login-form button {
width: 100%;
padding: 0.75rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}
.login-form button:hover {
background-color: #0056b3;
}
.login-form button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 0.75rem;
border-radius: 4px;
margin-bottom: 1rem;
border: 1px solid #f5c6cb;
}
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
Implementation Checklist
Frontend Setup
- Create AuthContext for state management
- Implement LoginForm component
- Add ProtectedRoute wrapper
- Set up API client with token management
- Handle token persistence in localStorage
- Add loading states and error handling
Backend Setup
- Create authentication middleware
- Implement login/validate/logout endpoints
- Set up JWT token generation and verification
- Add password hashing (bcrypt)
- Secure routes with authentication middleware
Security Considerations
- Use HTTPS in production
- Implement proper CORS policies
- Add rate limiting to login endpoints
- Use secure JWT secrets
- Implement token refresh mechanism
- Add logout functionality that invalidates tokens
Testing
- Test login/logout flow
- Verify protected routes work correctly
- Test token expiration handling
- Validate error states and user feedback
Environment Variables
# Frontend (.env)
REACT_APP_API_URL=http://localhost:3001/api
# Backend (.env)
JWT_SECRET=your-super-secure-secret-key
PORT=3001
DB_CONNECTION_STRING=your-database-url
Usage Instructions
- Setup: Wrap your app with
AuthProvider - Protection: Wrap protected content with
ProtectedRoute - Authentication: Use
useAuth()hook to access auth state - API Calls: Use the configured API client for authenticated requests
This guide provides a complete authentication system that can be adapted to any React application with a Node.js backend.