Skip to main content

Overview

AmbioSys implements a JWT (JSON Web Token) based authentication system that provides secure user authentication, session management, and comprehensive access logging. The system handles user registration, login, profile management, and token-based authorization.

Authentication Flow

1

User Registration

Users are registered through the /api/register endpoint with required personal information, department assignment, and optional license data.
2

JWT Token Generation

Upon successful registration or login, a JWT token is generated with user data and an 8-hour expiration time.
3

Token-Based Requests

Authenticated requests include the JWT token in the Authorization header as Bearer <token>.
4

Token Verification

The authentication middleware validates the token on protected routes and extracts user information.

User Registration

The registration endpoint creates a new user account with comprehensive profile information.

Endpoint

POST /api/register

Request Body

{
  "usuarioLogin": "jdoe",
  "usuarioCorreo": "[email protected]",
  "usuarioPassword": "securepassword123",
  "usuarioNombre": "John",
  "usuarioApellido": "Doe",
  "departamentoId": 1,
  "usuarioFechaNacimiento": "1990-01-15",
  "usuarioCelular": "+502 1234-5678",
  "usuarioDpi": "1234567890101",
  "usuarioTipoDocumento": 1,
  "usuarioNit": "12345678",
  "poseeLicencia": true,
  "licenciaTipo": 2,
  "licenciaNumero": "A-12345",
  "licenciaPrimerAnio": 2015,
  "licenciaFechaVencimiento": "2025-12-31",
  "usuarioRolId": 3
}

Validation Rules

  • usuarioLogin: Required, non-empty username
  • usuarioCorreo: Required, valid email format
  • usuarioPassword: Required, minimum 6 characters
  • usuarioNombre: Required, non-empty first name
  • usuarioApellido: Required, non-empty last name
  • departamentoId: Required, valid department ID (integer)
  • License fields: Required only if poseeLicencia is true
License information is mandatory when poseeLicencia is set to true. The system validates that licenciaTipo and licenciaNumero are provided and valid.

Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "usuario_id": 42,
    "usuario_login": "jdoe",
    "usuario_correo": "[email protected]",
    "usuario_nombre": "John",
    "usuario_apellido": "Doe",
    "departamento_id": 1,
    "usuario_celular": "+502 1234-5678",
    "profile": null
  }
}

Implementation Details

From /Backend/web-ambiotec/src/routes/auth.js:47-173:
router.post('/register',
  [
    check('usuarioLogin').notEmpty(),
    check('usuarioCorreo').isEmail(),
    check('usuarioPassword').isLength({ min: 6 }),
    check('usuarioNombre').notEmpty(),
    check('usuarioApellido').notEmpty(),
    check('departamentoId').isInt(),
  ],
  async (req, res) => {
    // Validation
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ 
        error: 'Datos inválidos', 
        details: errors.array() 
      });
    }

    // License validation logic
    const hasLicense = !!poseeLicencia;
    if (hasLicense) {
      if (!Number.isInteger(licenseTypeId) || licenseTypeId <= 0) {
        return res.status(400).json({ 
          error: 'Tipo de licencia invalido' 
        });
      }
    }

    // Call database function
    const { rows } = await pgPool.query(
      `SELECT * FROM db_ambiotec.fn_register_user($1, $2, ..., $16)`,
      datasend
    );

    // Generate JWT token
    const token = jwt.sign(
      userData,
      JWT_SECRET,
      { expiresIn: JWT_EXPIRES }
    );

    return res.status(201).json({ token, user: userData });
  }
);

User Login

The login endpoint authenticates users and logs access attempts.

Endpoint

POST /api/login

Request Body

{
  "usuario_login": "jdoe",
  "usuario_password": "securepassword123"
}

Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "usuario_id": 42,
    "usuario_login": "jdoe",
    "usuario_correo": "[email protected]",
    "usuario_nombre": "John",
    "usuario_apellido": "Doe",
    "departamento_id": 1,
    "usuario_celular": "+502 1234-5678",
    "access_id": 1234,
    "profile": null
  }
}

Access Logging

Every login attempt is logged with client metadata:
const client = parseClient(req);
const access_id = await logAccess({
  user_id: user.user_id,
  ...client, // ip, platform, browser, etc.
  is_successful: true,
  extra_data: req.headers['x-client-info'] || null
});
The access_id returned in the login response can be used to track the user’s session throughout the application.

Error Handling

From /Backend/web-ambiotec/src/routes/auth.js:255-279:
catch (err) {
  // Log failed access attempt
  await logAccess({ 
    user_id: null, 
    ...client, 
    is_successful: false, 
    event: 'login' 
  });
  
  // Database connection errors
  if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
    return res.status(503).json({ 
      error: 'Servicio no disponible',
      message: 'No se puede conectar con la base de datos.'
    });
  }
  
  // Invalid credentials (P0002 from database function)
  if (err.code === 'P0002') {
    return res.status(401).json({ error: err.message });
  }
  
  return res.status(500).json({ 
    error: 'Error interno al iniciar sesión' 
  });
}

JWT Token Structure

Token Configuration

From /Backend/web-ambiotec/src/routes/auth.js:12-13:
const JWT_SECRET = constants.APP.JWT_SECRET || '...';
const JWT_EXPIRES = constants.APP.JWT_EXPIRES || '8h';

Token Payload

The JWT token contains the following user data:
{
  "usuario_id": 42,
  "usuario_login": "jdoe",
  "usuario_correo": "[email protected]",
  "usuario_nombre": "John",
  "usuario_apellido": "Doe",
  "departamento_id": 1,
  "usuario_celular": "+502 1234-5678",
  "access_id": 1234,
  "profile": null,
  "iat": 1234567890,
  "exp": 1234596690
}
Store JWT tokens securely on the client side. Never expose tokens in URLs or logs. Use secure storage mechanisms like httpOnly cookies or secure local storage.

Authentication Middleware

The requireAuth middleware protects routes that require authentication.

Implementation

From /Backend/web-ambiotec/src/middleware/auth.js:7-20:
export function requireAuth(req, res, next) {
  const auth = req.headers.authorization;
  
  // Check for Bearer token
  if (!auth?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Token no provisto' });
  }
  
  const token = auth.split(' ')[1];
  
  try {
    // Verify and decode token
    const payload = jwt.verify(token, JWT_SECRET);
    req.user = payload; // Attach user data to request
    next();
  } catch {
    return res.status(401).json({ error: 'Token inválido o caducado' });
  }
}

Usage Example

import { requireAuth } from '../middleware/auth.js';

router.get('/protected-route', requireAuth, async (req, res) => {
  const userId = req.user.usuario_id;
  // Access user data from req.user
});

Making Authenticated Requests

curl -X GET https://api.ambiosys.com/api/protected-route \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Profile Management

Get User Profile

Retrieve complete user profile information including roles and license data.
POST /api/profile
{
  "user_id": 42
}

Update User Profile

Update user profile information with role assignments.
POST /api/update-profile
From /Backend/web-ambiotec/src/routes/auth.js:315-403:
router.post('/update-profile',
  [
    check('user_id').isInt(),
    check('username').notEmpty(),
    check('email').isEmail(),
    check('first_name').notEmpty(),
    check('last_name').notEmpty(),
    check('mobile_number').notEmpty(),
    check('department_id').isInt(),
    check('roles').optional().isArray(),
  ],
  async (req, res) => {
    await client.query('BEGIN');
    
    // Update user profile
    const { rows } = await client.query(
      `SELECT * FROM db_ambiotec.fn_update_user_profile(...)`
    );
    
    // Update role assignments
    if (Array.isArray(roles)) {
      await client.query(
        'DELETE FROM db_ambiotec.user_roles WHERE user_id = $1', 
        [user_id]
      );
      
      for (const role of roles) {
        await client.query(
          `INSERT INTO db_ambiotec.user_roles (user_id, role_id) 
           VALUES ($1, $2) ON CONFLICT DO NOTHING`,
          [user_id, roleId]
        );
      }
    }
    
    await client.query('COMMIT');
  }
);
Profile updates are transactional. If any part of the update fails, all changes are rolled back to maintain data consistency.

Session Management

Token Expiration

  • Default expiration: 8 hours
  • Configured via JWT_EXPIRES constant
  • No automatic token refresh (implement token refresh endpoint if needed)

Access Tracking

Each login generates an access_id that can be used for:
  • Session tracking
  • Audit logging
  • Activity monitoring
  • Security analysis

Security Best Practices

1

Use HTTPS Only

Always transmit JWT tokens over secure HTTPS connections to prevent token interception.
2

Implement Token Refresh

Consider implementing a token refresh mechanism for long-lived sessions without requiring re-authentication.
3

Validate on Every Request

The middleware validates tokens on every protected request, ensuring expired or invalid tokens are rejected.
4

Monitor Failed Attempts

All failed login attempts are logged with client metadata for security monitoring and anomaly detection.

Department and License Types

Get Departments

Retrieve available departments for user assignment.
GET /api/departamentos
Response:
{
  "success": true,
  "departamentos": [
    {
      "department_id": 1,
      "department_name": "Operaciones",
      "description": "Personal operativo de campo"
    }
  ]
}

Get License Types

Retrieve available license types for driver licensing.
GET /api/license-types
Response:
{
  "success": true,
  "licenseTypes": [
    {
      "license_type_id": 2,
      "license_type_name": "Tipo A",
      "license_type_description": "Vehículos livianos"
    }
  ]
}
License information triggers automated reminder scheduling for license renewal notifications when expiration dates are set.

Error Responses

400 Bad Request

{
  "error": "Datos inválidos",
  "details": [
    {
      "msg": "Invalid value",
      "param": "usuarioCorreo",
      "location": "body"
    }
  ]
}

401 Unauthorized

{
  "error": "Token inválido o caducado"
}

409 Conflict

{
  "error": "Login o correo ya registrado"
}

503 Service Unavailable

{
  "error": "Servicio no disponible",
  "message": "No se puede conectar con la base de datos. Por favor, contacte a soporte del sistema."
}

Build docs developers (and LLMs) love