ideas-generator/docs-archive/AUTHENTICATION_GUIDE.md
DJP b909d7e19a Clean up repository structure and archive legacy docs
- 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>
2025-09-10 16:24:39 -04:00

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

  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.