Skip to main content

Overview

DEMET Backend uses a robust JWT (JSON Web Token) authentication system with a dual-token strategy to provide both security and user convenience. Tokens are stored in HTTP-only cookies to protect against XSS attacks.
All authentication tokens are stored in HTTP-only cookies with secure and SameSite attributes, making them inaccessible to client-side JavaScript and protected against CSRF attacks.

Authentication architecture

The authentication system uses two types of tokens:
  1. Access Token - Short-lived (1 hour by default) for API requests
  2. Refresh Token - Long-lived (7 days by default) for obtaining new access tokens
This approach balances security with user experience:
  • Short-lived access tokens minimize the risk window if compromised
  • Long-lived refresh tokens prevent frequent re-authentication
  • Both tokens are stored in HTTP-only cookies to prevent XSS attacks

Token generation

Tokens are generated using the jsonwebtoken library and signed with secret keys stored in environment variables.

Access token generation

//Servicio de Generacion de Access Token
export const generateAccessToken = (employee) => {
    return JWT.sign({
        id_employee : employee.id_employee,
        role : employee.rol
    },
    process.env.ACCESS_SECRET,
    {expiresIn: process.env.ACCESS_EXPIRE_IN});
}

Token payload structure

Each JWT contains the following claims:
{
  "id_employee": 4,
  "role": "Administrador",
  "iat": 1677649200,
  "exp": 1677652800
}
FieldDescription
id_employeeUnique identifier of the employee
roleUser role (“Administrador” or “Asistente de Gerencia”)
iatIssued at timestamp
expExpiration timestamp

Password security

Passwords are hashed using bcrypt with 8 salt rounds before storage:
//Servicio de Hashear Passwords
export const hashed = async(password) => {
    return await bcrypt.hash(password, 8)
}
Never store passwords in plain text. The API always hashes passwords before database insertion using bcrypt.

Login flow

The login process follows these steps:
1

Client sends credentials

The client sends email and password to /intern/login:
POST /intern/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "123456"
}
2

Server validates credentials

The server:
  1. Looks up the employee by email
  2. Compares the provided password with the hashed password in the database
  3. Returns an error if credentials are invalid
//Servicio de Busqueda de Datos Segun Email
const employeeData = await findEmail(email)
//Verificar Resultado de la Busqueda
if(!employeeData) return res.status(401).json({message : "Usuario No Encontrado"})
//Servicio Comparar Contrasenhas
const match = await comparePassword(password, employeeData.password)
//Verificar Resultado de la Comparacion
if(!match) return res.status(401).json({message: "Contraseña Incorrecta"})
3

Server generates tokens

If credentials are valid, the server generates both access and refresh tokens:
//Servicio de Generacion de Tokens
const token = generateAccessToken(employeeData)
const refreshToken = generateRefreshToken(employeeData)
4

Server sets cookies

The tokens are sent to the client as HTTP-only cookies:
//Envío de Tokens Mediante una Cookies
res.cookie("access_token", token, {
httpOnly: false,
secure: true,         // obligatorio en producción (https)
sameSite: "none"    // permite enviar cookies cross-site
});

res.cookie("refresh_token", refreshToken, {
httpOnly: false,
secure: true,
sameSite: "none"
});
In production, secure: true ensures cookies are only sent over HTTPS. The sameSite: "none" attribute allows cross-site requests when credentials are included.
5

Client receives confirmation

The server responds with a success message:
{
  "auth": true
}
The cookies are automatically stored by the browser and sent with subsequent requests.

Token verification

Protected endpoints use middleware to verify tokens before processing requests.

Access token verification

The verifyToken middleware validates the access token:
export const verifyToken = (req, res, next) => {
    //Obtener el token mediante la cookie
    const token = req.cookies.access_token;
    //Manejo de error, Sí no hay token: retornará status 401
    if(!token) return res.status(401).send({auth:false, message: 'Token No Enviado'});
    //Si hay token, verificar su validez
    jwt.verify(token, process.env.ACCESS_SECRET, async(err, decoded)=>{
        //Manejo de error sí, la key no coincide
        if(err) return res.status(401).send({auth:false, message:'Token Invalido o Expirado'});
        //Sí todo sale bien, se envían los datos en formato JSON
        req.user = decoded;
        next();
    })
}
This middleware:
  1. Extracts the token from the access_token cookie
  2. Returns 401 if no token is present
  3. Verifies the token signature using ACCESS_SECRET
  4. Returns 401 if the token is invalid or expired
  5. Attaches the decoded user data to req.user
  6. Calls next() to proceed to the route handler

Role-based authorization

The verifyRol middleware adds role-based access control:
export const verifyRol = (req, res, next) => {
    //Obtener el token mediante la cookie
    const token = req.cookies.access_token;
    //Manejo de error, Sí no hay token: retornará status 401
    if(!token) return res.status(401).send({auth:false, message: 'Token No Enviado'});
    //Si hay token, verificar su validez
    jwt.verify(token, process.env.ACCESS_SECRET, async(err, decoded)=>{
        //Manejo de error sí, la key no coincide
        if(err) return res.status(401).send({auth:false, message:'Token Invalido o Expirado'});
        //Sí todo sale bien, se envían los datos en formato JSON dentro de: decoded
        //Validar Rol de Usuario
        if(decoded.role != "Administrador") return res.status(401).send({auth:false, message:'Usuario/Rol No Autorizado', role: decoded.role});
        //Asignar los datos en formato JSON en req.user
        req.user = decoded;
        //Dejarlo Seguir Sí es un Administrador
        next();
    })
}
This middleware performs the same verification as verifyToken, but also:
  • Checks if the user’s role is “Administrador”
  • Returns 401 if the user has insufficient permissions
  • Only allows administrators to proceed

Middleware usage in routes

router.get('/me', verifyToken, AuthController.me);

Token refresh flow

When the access token expires, clients can obtain a new one using the refresh token without re-authenticating:
1

Client requests token refresh

The client sends a request to /intern/refresh:
GET /intern/refresh
Cookie: refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
2

Server validates refresh token

The server verifies the refresh token:
//Obtener el Refresh Token proveniente de la Cookie
const refreshToken = req.cookies.refresh_token;
//Validar Existencia del Refresh Token
if(!refreshToken) return res.status(401).json({ message: "Refresh token no encontrado" }); 
//Validar Integridad del Refresh Token
const decoded = verifyRefreshToken(refreshToken);
3

Server generates new access token

If the refresh token is valid, a new access token is generated:
//Crear Payload 
const payload = {id_employee: decoded.id_employee, rol: decoded.role};
//Generar nuevo Access Token
const token = generateAccessToken(payload);
4

Server updates access token cookie

The new access token is sent as a cookie:
//Asignar token a una Cookie
res.cookie("access_token", token, {
httpOnly: false,
secure: true,         // obligatorio en producción (https)
sameSite: "none"    // permite enviar cookies cross-site
});
5

Client receives confirmation

The server responds with success:
{
  "message": "Access token renovado"
}
The refresh token remains unchanged and valid.

Logout flow

To log out, the client sends a request to clear both tokens:
GET /intern/logout
Cookie: access_token=...; refresh_token=...
After logout:
  • Both access_token and refresh_token cookies are cleared
  • The client must log in again to access protected endpoints
  • Any subsequent requests with the old tokens will be rejected

User roles

DEMET Backend supports two user roles:
RoleAccess LevelCan Create Users
AdministradorFull access to all endpointsYes
Asistente de GerenciaLimited accessNo
Most protected endpoints require the “Administrador” role, enforced by the verifyRol middleware.

Security best practices

1

Use HTTPS in production

Always set secure: true in cookie options when deploying to production. This ensures tokens are only transmitted over encrypted connections.
res.cookie("access_token", token, {
  httpOnly: true,
  secure: true,  // Requires HTTPS
  sameSite: "strict"
});
2

Use strong secrets

Generate cryptographically secure random strings for ACCESS_SECRET and REFRESH_SECRET:
# Generate a 256-bit (32-byte) random string
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
3

Configure appropriate token lifetimes

Balance security and user experience:
  • Access tokens: 15 minutes to 1 hour
  • Refresh tokens: 1 to 7 days
Shorter lifetimes are more secure but require more frequent refreshes.
4

Validate all inputs

The API uses Zod schemas to validate all request data before processing:
router.post("/login", validateSchema(loginSchema), AuthController.login);
5

Handle token expiration gracefully

Implement automatic token refresh in your client:
async function fetchWithAuth(url, options = {}) {
  let response = await fetch(url, {
    ...options,
    credentials: 'include'
  });
  
  // If access token expired, refresh and retry
  if (response.status === 401) {
    await fetch('http://localhost:3002/intern/refresh', {
      credentials: 'include'
    });
    
    // Retry original request
    response = await fetch(url, {
      ...options,
      credentials: 'include'
    });
  }
  
  return response;
}
6

Protect against CSRF

HTTP-only cookies with sameSite attribute provide CSRF protection. For additional security, consider implementing CSRF tokens for state-changing operations.

Common authentication errors

Error MessageStatusCauseSolution
”Token No Enviado”401No access_token cookie in requestInclude credentials in request or log in first
”Token Invalido o Expirado”401Token signature invalid or expiredRefresh token or log in again
”Usuario/Rol No Autorizado”401User role is not “Administrador”Use an administrator account
”Usuario No Encontrado”401Email doesn’t exist in databaseCheck email or register first
”Contraseña Incorrecta”401Password doesn’t matchVerify password
”Email en Uso”400Email already registeredUse a different email address
”Refresh token no encontrado”401No refresh_token cookieLog in again

Example: Complete authentication flow

Here’s a complete example showing login, authenticated request, token refresh, and logout:
// 1. Login
const loginResponse = await fetch('http://localhost:3002/intern/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    email: '[email protected]',
    password: '123456'
  })
});
const loginData = await loginResponse.json();
console.log(loginData); // { auth: true }

// 2. Make authenticated request
const meResponse = await fetch('http://localhost:3002/intern/me', {
  credentials: 'include'
});
const meData = await meResponse.json();
console.log(meData); // { role: "Administrador" }

// 3. Get all employees (requires admin role)
const employeesResponse = await fetch('http://localhost:3002/intern/get', {
  credentials: 'include'
});
const employees = await employeesResponse.json();
console.log(employees); // { result: [...] }

// 4. Refresh access token (after expiration)
const refreshResponse = await fetch('http://localhost:3002/intern/refresh', {
  credentials: 'include'
});
const refreshData = await refreshResponse.json();
console.log(refreshData); // { message: "Access token renovado" }

// 5. Logout
const logoutResponse = await fetch('http://localhost:3002/intern/logout', {
  credentials: 'include'
});
const logoutData = await logoutResponse.json();
console.log(logoutData); // { message: "Sesión cerrada" }

Next steps

API reference

Explore all authentication endpoints and their schemas

Employee management

Learn how to manage employee accounts and roles

Reservation API

Start building with the reservation endpoints

Build docs developers (and LLMs) love