Skip to main content

Endpoint

POST /api/login
Authenticates a user with email and password credentials. Returns a JWT token valid for 24 hours upon successful authentication.
This is a public endpoint that does not require prior authentication.

Request Body

email
string
required
The user’s registered email address
password
string
required
The user’s password (will be compared against the bcrypt hash)

Authentication Flow

1

Validate Required Fields

The API checks that both email and password are provided.
2

Lookup User

Query the database to find a user with the provided email address.
3

Verify Password

Compare the provided password against the stored bcrypt hash using bcrypt.compare().
4

Generate JWT Token

Create a signed JWT token containing the user’s ID and email, valid for 1 day.
5

Return Token and User Data

Send the JWT token and user information (excluding password) in the response.

Request Example

curl -X POST http://localhost:3001/api/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "securePassword123"
  }'

Response

Success Response (200 OK)

message
string
Success message confirming login
token
string
JWT token valid for 24 hours. Use this token in the Authorization header for protected endpoints.
user
object
User information object (password excluded)
user.id
integer
The user’s unique ID
user.name
string
The user’s full name
user.email
string
The user’s email address
{
  "message": "Login exitoso",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwiaWF0IjoxNjQwOTk1MjAwLCJleHAiOjE2NDEwODE2MDB9.abc123xyz",
  "user": {
    "id": 123,
    "name": "John Doe",
    "email": "[email protected]"
  }
}
The JWT token expires after 24 hours (1 day). Store this token securely and include it in the Authorization header for all protected API requests.

Error Responses

Returned when required fields are not provided.
{
  "message": "Todos los campos son obligatorios"
}
Cause: Email or password field is missing from the request body.
Returned when the email doesn’t exist or the password is incorrect.
{
  "message": "Credenciales inválidas"
}
Causes:
  • No user found with the provided email address
  • Password does not match the stored bcrypt hash
For security reasons, the API does not distinguish between “user not found” and “wrong password” to prevent email enumeration attacks.
Returned when an unexpected server error occurs.
{
  "message": "Error en el servidor"
}
Cause: Database connection issues or other server-side errors.

Implementation Details

The login endpoint is implemented in the authController.js file:
import bcrypt from "bcrypt";
import pool from "../db/db.js";
import jwt from "jsonwebtoken";
import dotenv from "dotenv";

dotenv.config();

export const loginUser = async (req, res) => {
  try {
    const { email, password } = req.body;

    // Verify required fields
    if (!email || !password)
      return res.status(400).json({ message: "Todos los campos son obligatorios" });

    // Find user by email
    const [userResult] = await pool.query("SELECT * FROM users WHERE email = ?", [email]);
    if (userResult.length === 0)
      return res.status(401).json({ message: "Credenciales inválidas" });

    const user = userResult[0];

    // Compare password with bcrypt hash
    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) return res.status(401).json({ message: "Credenciales inválidas" });

    // Create JWT token
    const token = jwt.sign(
      { id: user.id, email: user.email },
      process.env.JWT_SECRET,
      { expiresIn: "1d" } // expires in 1 day
    );

    res.json({
      message: "Login exitoso",
      token,
      user: {
        id: user.id,
        name: user.name,
        email: user.email,
      },
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Error en el servidor" });
  }
};
The implementation is located at src/controllers/authController.js:41 in the source repository.

JWT Token Details

The generated JWT token contains the following payload:
{
  "id": 123,
  "email": "[email protected]",
  "iat": 1640995200,
  "exp": 1641081600
}

Token Signing Process

const token = jwt.sign(
  { id: user.id, email: user.email },    // Payload
  process.env.JWT_SECRET,                // Secret key
  { expiresIn: "1d" }                    // Options: 1 day expiration
);
id
integer
User’s unique identifier from the database
email
string
User’s email address
iat
integer
Token issued at timestamp (automatically added by jwt.sign)
exp
integer
Token expiration timestamp (automatically calculated as iat + 1 day)

Using the JWT Token

After successful login, include the token in all subsequent API requests:
curl -X GET http://localhost:3001/api/transactions \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Token Storage Best Practices:
  • Web apps: Use httpOnly cookies or sessionStorage (NOT localStorage for sensitive apps)
  • Mobile apps: Use secure storage mechanisms (Keychain on iOS, Keystore on Android)
  • Never: Store tokens in URL parameters or expose them in client-side code

Password Verification

The login process uses bcrypt to securely compare passwords:
// Compare plain text password with stored hash
const isMatch = await bcrypt.compare(password, user.password);

if (!isMatch) {
  return res.status(401).json({ message: "Credenciales inválidas" });
}
Bcrypt comparison features:
  • Secure comparison: Uses the salt embedded in the stored hash
  • Timing-safe: Resistant to timing attacks
  • No decryption needed: One-way comparison without revealing the original password

Token Expiration Handling

When a token expires after 24 hours:
1

Token Expiry Detection

Protected endpoints will return a 403 error with message “Token inválido o expirado”.
2

User Re-authentication

The client application should detect this error and redirect the user to the login page.
3

Obtain New Token

User logs in again to receive a fresh JWT token valid for another 24 hours.
Always implement proper token expiration handling in your client application to provide a smooth user experience.

Security Considerations

Passwords are never decrypted. Instead, bcrypt hashes the provided password and compares it with the stored hash.
const isMatch = await bcrypt.compare(password, user.password);
The JWT_SECRET environment variable must be kept secure and never exposed in client code or version control.
The API returns the same error message (“Credenciales inválidas”) for both non-existent emails and wrong passwords to prevent email enumeration attacks.
Always use HTTPS in production to encrypt credentials during transmission.

Next Steps

Authentication Overview

Learn more about the JWT authentication system

Register New Users

Create new user accounts

Build docs developers (and LLMs) love