- 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>
526 lines
No EOL
12 KiB
Markdown
526 lines
No EOL
12 KiB
Markdown
# 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
|
|
|
|
```jsx
|
|
// 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
|
|
|
|
```jsx
|
|
// 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
|
|
|
|
```jsx
|
|
// 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
|
|
|
|
```jsx
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```css
|
|
/* 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
1. **Setup**: Wrap your app with `AuthProvider`
|
|
2. **Protection**: Wrap protected content with `ProtectedRoute`
|
|
3. **Authentication**: Use `useAuth()` hook to access auth state
|
|
4. **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. |