Skip to content

Authentication System Example

This example demonstrates how to build a complete authentication system using the response handler.

Complete Authentication Server

javascript
import express from 'express';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { configureResponseHandler } from '@amit-kandar/response-handler';

const app = express();

// Configure response handler
app.use(
  configureResponseHandler({
    logging: {
      enabled: true,
      level: 'info',
    },
    security: {
      enableCors: true,
      rateLimiting: {
        windowMs: 900000, // 15 minutes
        maxRequests: 100,
      },
    },
  }),
);

app.use(express.json());

// Mock user database
const users = [];
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';

// Registration endpoint
app.post('/auth/register', async (req, res) => {
  try {
    const { email, password, name } = req.body;

    // Validation
    if (!email || !password || !name) {
      return res.sendValidationError([
        { field: 'email', message: 'Email is required' },
        { field: 'password', message: 'Password is required' },
        { field: 'name', message: 'Name is required' },
      ]);
    }

    // Check if user exists
    const existingUser = users.find((u) => u.email === email);
    if (existingUser) {
      return res.sendError('USER_EXISTS', 'User with this email already exists');
    }

    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Create user
    const user = {
      id: users.length + 1,
      email,
      password: hashedPassword,
      name,
      createdAt: new Date().toISOString(),
    };

    users.push(user);

    // Generate token
    const token = jwt.sign({ userId: user.id, email: user.email }, JWT_SECRET, { expiresIn: '7d' });

    // Return success response (exclude password)
    const { password: _, ...userResponse } = user;
    res.sendCreated(
      {
        user: userResponse,
        token,
      },
      'User registered successfully',
    );
  } catch (error) {
    res.sendError('REGISTRATION_FAILED', 'Failed to register user', error.message);
  }
});

// Login endpoint
app.post('/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;

    // Validation
    if (!email || !password) {
      return res.sendValidationError([
        { field: 'email', message: 'Email is required' },
        { field: 'password', message: 'Password is required' },
      ]);
    }

    // Find user
    const user = users.find((u) => u.email === email);
    if (!user) {
      return res.sendError('INVALID_CREDENTIALS', 'Invalid email or password');
    }

    // Verify password
    const isValidPassword = await bcrypt.compare(password, user.password);
    if (!isValidPassword) {
      return res.sendError('INVALID_CREDENTIALS', 'Invalid email or password');
    }

    // Generate token
    const token = jwt.sign({ userId: user.id, email: user.email }, JWT_SECRET, { expiresIn: '7d' });

    // Update last login
    user.lastLogin = new Date().toISOString();

    // Return success response
    const { password: _, ...userResponse } = user;
    res.sendSuccess(
      {
        user: userResponse,
        token,
      },
      'Login successful',
    );
  } catch (error) {
    res.sendError('LOGIN_FAILED', 'Failed to login', error.message);
  }
});

// Authentication middleware
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.sendError('UNAUTHORIZED', 'Access token required');
  }

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) {
      return res.sendError('FORBIDDEN', 'Invalid or expired token');
    }

    req.user = user;
    next();
  });
};

// Protected profile endpoint
app.get('/auth/profile', authenticateToken, (req, res) => {
  const user = users.find((u) => u.id === req.user.userId);

  if (!user) {
    return res.sendNotFound('User not found');
  }

  const { password, ...userProfile } = user;
  res.sendSuccess(userProfile, 'Profile retrieved successfully');
});

// Update profile endpoint
app.put('/auth/profile', authenticateToken, async (req, res) => {
  try {
    const { name, email } = req.body;
    const userId = req.user.userId;

    const userIndex = users.findIndex((u) => u.id === userId);
    if (userIndex === -1) {
      return res.sendNotFound('User not found');
    }

    // Check if new email is already taken
    if (email && email !== users[userIndex].email) {
      const emailExists = users.find((u) => u.email === email && u.id !== userId);
      if (emailExists) {
        return res.sendError('EMAIL_EXISTS', 'Email already taken by another user');
      }
    }

    // Update user
    if (name) users[userIndex].name = name;
    if (email) users[userIndex].email = email;
    users[userIndex].updatedAt = new Date().toISOString();

    const { password, ...updatedUser } = users[userIndex];
    res.sendUpdated(updatedUser, 'Profile updated successfully');
  } catch (error) {
    res.sendError('UPDATE_FAILED', 'Failed to update profile', error.message);
  }
});

// Change password endpoint
app.post('/auth/change-password', authenticateToken, async (req, res) => {
  try {
    const { currentPassword, newPassword } = req.body;
    const userId = req.user.userId;

    if (!currentPassword || !newPassword) {
      return res.sendValidationError([
        { field: 'currentPassword', message: 'Current password is required' },
        { field: 'newPassword', message: 'New password is required' },
      ]);
    }

    const userIndex = users.findIndex((u) => u.id === userId);
    if (userIndex === -1) {
      return res.sendNotFound('User not found');
    }

    // Verify current password
    const isValidPassword = await bcrypt.compare(currentPassword, users[userIndex].password);
    if (!isValidPassword) {
      return res.sendError('INVALID_CURRENT_PASSWORD', 'Current password is incorrect');
    }

    // Hash new password
    const hashedNewPassword = await bcrypt.hash(newPassword, 10);
    users[userIndex].password = hashedNewPassword;
    users[userIndex].updatedAt = new Date().toISOString();

    res.sendSuccess(null, 'Password changed successfully');
  } catch (error) {
    res.sendError('PASSWORD_CHANGE_FAILED', 'Failed to change password', error.message);
  }
});

// Logout endpoint (token blacklisting would be implemented here)
app.post('/auth/logout', authenticateToken, (req, res) => {
  // In a real application, you would blacklist the token
  // For this example, we'll just return success
  res.sendSuccess(null, 'Logged out successfully');
});

// Refresh token endpoint
app.post('/auth/refresh', authenticateToken, (req, res) => {
  try {
    // Generate new token
    const newToken = jwt.sign({ userId: req.user.userId, email: req.user.email }, JWT_SECRET, {
      expiresIn: '7d',
    });

    res.sendSuccess({ token: newToken }, 'Token refreshed successfully');
  } catch (error) {
    res.sendError('TOKEN_REFRESH_FAILED', 'Failed to refresh token', error.message);
  }
});

// Delete account endpoint
app.delete('/auth/account', authenticateToken, (req, res) => {
  try {
    const userId = req.user.userId;
    const userIndex = users.findIndex((u) => u.id === userId);

    if (userIndex === -1) {
      return res.sendNotFound('User not found');
    }

    users.splice(userIndex, 1);
    res.sendDeleted('Account deleted successfully');
  } catch (error) {
    res.sendError('DELETE_ACCOUNT_FAILED', 'Failed to delete account', error.message);
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Authentication server running on port ${PORT}`);
});

Socket.IO Authentication

javascript
import { Server } from 'socket.io';
import jwt from 'jsonwebtoken';
import { createSocketHandler } from '@amit-kandar/response-handler';

const io = new Server(server, {
  cors: {
    origin: 'http://localhost:3000',
    methods: ['GET', 'POST'],
  },
});

// Apply response handler
io.use(createSocketHandler());

// Authentication middleware
io.use((socket, next) => {
  const token = socket.handshake.auth.token;

  if (!token) {
    socket.sendError('UNAUTHORIZED', 'Authentication required');
    return next(new Error('Authentication required'));
  }

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) {
      socket.sendError('FORBIDDEN', 'Invalid token');
      return next(new Error('Invalid token'));
    }

    socket.user = user;
    next();
  });
});

// Handle connections
io.on('connection', (socket) => {
  console.log(`User ${socket.user.email} connected`);

  // Join user to personal room
  socket.join(`user:${socket.user.userId}`);

  socket.sendSuccess(
    'connection',
    {
      userId: socket.user.userId,
      email: socket.user.email,
    },
    'Connected successfully',
  );

  // Handle profile updates
  socket.on('update_profile', (data) => {
    try {
      // Update user profile logic here
      socket.sendSuccess('profile_updated', data, 'Profile updated');

      // Notify other sessions of the same user
      socket.to(`user:${socket.user.userId}`).emit('profile_changed', data);
    } catch (error) {
      socket.sendError('UPDATE_FAILED', 'Failed to update profile', error.message);
    }
  });

  // Handle logout
  socket.on('logout', () => {
    socket.leave(`user:${socket.user.userId}`);
    socket.sendSuccess('logout', null, 'Logged out successfully');
    socket.disconnect();
  });

  socket.on('disconnect', () => {
    console.log(`User ${socket.user.email} disconnected`);
  });
});

Frontend Integration

javascript
// React hook for authentication
import { useState, useEffect } from 'react';
import axios from 'axios';

const API_BASE_URL = 'http://localhost:3000';

// Axios interceptor for authentication
axios.interceptors.request.use((config) => {
  const token = localStorage.getItem('authToken');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

axios.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('authToken');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  },
);

export const useAuth = () => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = localStorage.getItem('authToken');
    if (token) {
      fetchProfile();
    } else {
      setLoading(false);
    }
  }, []);

  const fetchProfile = async () => {
    try {
      const response = await axios.get(`${API_BASE_URL}/auth/profile`);
      if (response.data.success) {
        setUser(response.data.data);
      }
    } catch (error) {
      console.error('Failed to fetch profile:', error);
      localStorage.removeItem('authToken');
    } finally {
      setLoading(false);
    }
  };

  const login = async (email, password) => {
    const response = await axios.post(`${API_BASE_URL}/auth/login`, {
      email,
      password,
    });

    if (response.data.success) {
      localStorage.setItem('authToken', response.data.data.token);
      setUser(response.data.data.user);
      return { success: true };
    } else {
      return { success: false, error: response.data.error };
    }
  };

  const register = async (email, password, name) => {
    const response = await axios.post(`${API_BASE_URL}/auth/register`, {
      email,
      password,
      name,
    });

    if (response.data.success) {
      localStorage.setItem('authToken', response.data.data.token);
      setUser(response.data.data.user);
      return { success: true };
    } else {
      return { success: false, error: response.data.error };
    }
  };

  const logout = () => {
    localStorage.removeItem('authToken');
    setUser(null);
  };

  return {
    user,
    loading,
    login,
    register,
    logout,
    isAuthenticated: !!user,
  };
};

Key Features

  • Complete authentication flow with registration, login, logout
  • JWT token management with refresh capabilities
  • Password hashing using bcrypt
  • Input validation and error handling
  • Protected routes with middleware
  • Socket.IO authentication for real-time features
  • Frontend integration with React hooks
  • Security best practices implemented throughout

Released under the ISC License.