Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Manuelfg1985/Proyecto_Final_26/llms.txt

Use this file to discover all available pages before exploring further.

The Agencia de Habilidades para el Futuro API uses the jsonwebtoken library to sign tokens at login and verify them on every protected request. The signing secret is read from the JWT_SECRET environment variable at runtime — it is never hardcoded. Token generation happens in controllers/auth.js and token verification is handled by the reusable authMiddleware in middlewares/authMiddleware.js.
JWT_SECRET must be a strong, unpredictable string and must never be committed to source control. Anyone who obtains this secret can forge valid tokens and gain full admin access to the API. Store it exclusively in your hosting environment’s secrets manager or .env file, and ensure .env is listed in .gitignore.

Token generation

When a client posts credentials to POST /api/auth/login, the login controller in controllers/auth.js performs three steps:
  1. Validates that both email and password fields are present in the request body.
  2. Compares the submitted values directly against the ADMIN_EMAIL and ADMIN_PASSWORD environment variables. There is no database lookup — this is a single-admin model.
  3. Signs a JWT with the payload { email } and returns it to the client if the credentials match.
The token is signed using JWT_SECRET and expires after 1 hour (expiresIn: '1h').
import jwt from 'jsonwebtoken';
import dotenv from "dotenv";
dotenv.config();

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

    try {
        // 1. Validar que llegaron los campos
        if (!email || !password) {
            return res.status(400).json({ message: 'Email y contraseña son requeridos' });
        }

        // 2. Comparar contra las credenciales del entorno
        const validEmail    = process.env.ADMIN_EMAIL;
        const validPassword = process.env.ADMIN_PASSWORD;

        const emailMatch    = email === validEmail;
        const passwordMatch = password === validPassword;

        if (!emailMatch || !passwordMatch) {
            return res.status(401).json({ message: 'Credenciales inválidas' });
        }

        // 3. Generar token
        const payload = { email };

        const token = jwt.sign(payload, process.env.JWT_SECRET, {
            expiresIn: '1h'
        });

        return res.json({
            message: 'Login exitoso',
            token
        });

    } catch (error) {
        console.error('Error en login:', error);
        return res.status(500).json({ message: 'Error en el servidor' });
    }
};
The JWT payload contains only the email field. No role, ID, or additional claims are embedded in the token. The decoded payload is later available as req.user inside protected route handlers.

Auth middleware

The authMiddleware function in middlewares/authMiddleware.js is applied as route-level middleware on every protected endpoint. It runs before the route handler and either allows the request through or rejects it immediately. The middleware does the following in order:
  1. Reads the Authorization header from the incoming request.
  2. Checks that the header exists and starts with 'Bearer '. If not, it returns 401.
  3. Splits the header on the space character and extracts the raw token string.
  4. Calls jwt.verify(token, process.env.JWT_SECRET) to validate the signature and check expiry.
  5. Attaches the decoded payload to req.user so downstream handlers can access it.
  6. Calls next() to pass control to the route handler.
  7. If jwt.verify() throws for any reason (bad signature, expired token, malformed string), it catches the error and returns 403.
import jwt from 'jsonwebtoken';
import dotenv from "dotenv";
dotenv.config();

export const authMiddleware = (req, res, next) => {

    // 1. Obtener el token del header 'Authorization'
    // El formato estándar es: "Bearer <TOKEN>"
    const authHeader = req.header('Authorization');

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ message: 'Acceso denegado. No se proporcionó un token válido.' });
    }

    const token = authHeader.split(' ')[1]; // Separamos "Bearer" del token real

    try {
        // 2. Verificar el token usando la clave secreta
        const decoded = jwt.verify(token, process.env.JWT_SECRET);

        // 3. Inyectar los datos del usuario decodificados en la petición (req)
        req.user = decoded;

        // 4. Continuar a la siguiente función/ruta
        next();
    } catch (error) {
        return res.status(403).json({ message: 'Token inválido o expirado' });
    }
};

Error responses

The following error responses can be returned by the login endpoint or the auth middleware:
HTTP statusMessageCause
400 Bad RequestEmail y contraseña son requeridosThe request body is missing email, password, or both.
401 UnauthorizedCredenciales inválidasThe submitted email or password does not match the environment variable values.
401 UnauthorizedAcceso denegado. No se proporcionó un token válido.The Authorization header is absent or does not start with Bearer .
403 ForbiddenToken inválido o expiradoThe token signature is invalid, the token has expired, or the token string is malformed.
500 Internal Server ErrorError en el servidorAn unexpected error occurred during the login process.

Example: obtaining a token

Send a POST request to /api/auth/login with the admin credentials as a JSON body.
curl -X POST https://your-api-host/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "your-admin-password"
  }'
Successful response (200 OK):
{
  "message": "Login exitoso",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluQGV4YW1wbGUuY29tIiwiaWF0IjoxNzAwMDAwMDAwLCJleHAiOjE3MDAwMDM2MDB9.SIGNATURE"
}
Copy the value of token from the response. This is the Bearer token you will include in all subsequent protected requests.

Example: using a token

Pass the token in the Authorization header with the Bearer prefix. The following example calls the protected GET /api/auth/private endpoint, which echoes back the decoded req.user payload.
curl -X GET https://your-api-host/api/auth/private \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Successful response (200 OK):
{
  "message": "Private content available only for authenticated users",
  "data": {
    "email": "[email protected]",
    "iat": 1700000000,
    "exp": 1700003600
  }
}
The data field contains the decoded JWT payload — the email claim that was embedded at signing time, along with the standard iat (issued at) and exp (expiry) timestamps added automatically by jsonwebtoken.
The same Authorization: Bearer <token> header format applies to every protected route: POST /api/postulantes, PUT /api/postulantes/:id, and DELETE /api/postulantes/:id. See the Authentication Overview for the full list of protected vs public routes.

Build docs developers (and LLMs) love