Implement User Login

The login service verifies user credentials.

The process is:

  1. Find the user by normalized email.
  2. Explicitly select the password field because it is excluded by default.
  3. Compare the submitted password with the stored hash.
  4. Reject invalid credentials.
  5. Generate an access token.
  6. Return safe user information and the token.

Use the same generic error message for unknown email addresses and incorrect passwords to reduce account enumeration.

src/services/auth.service.js
import bcrypt from 'bcryptjs';
import { User } from '../models/user.model.js';
import { AppError } from '../utils/app-error.js';
import { generateAccessToken } from '../utils/jwt.js';

export async function loginUser(email, password) {
  const user = await User.findOne({
    email: email.toLowerCase().trim()
  }).select('+password');

  if (!user) {
    throw new AppError('Invalid email or password', 401);
  }

  const passwordMatches = await bcrypt.compare(
    password,
    user.password
  );

  if (!passwordMatches) {
    throw new AppError('Invalid email or password', 401);
  }

  if (!user.isActive) {
    throw new AppError('Account is disabled', 403);
  }

  const accessToken = generateAccessToken(user);

  return {
    accessToken,
    user: {
      id: user._id,
      name: user.name,
      email: user.email,
      role: user.role
    }
  };
}

Retrieves the user, compares the password hash, checks account status, and returns an access token with safe user information.

Key Takeaways
  • Select the password field explicitly during login.

  • Use bcrypt.compare() to verify passwords.

  • Use generic invalid-credential messages.

  • Return only safe user information.