Skip to main content

Overview

The Portal Self-Service Backend API uses JWT (JSON Web Token) authentication with Azure Active Directory (Azure AD) to secure all API endpoints. Every request to protected endpoints must include a valid JWT token issued by Azure AD.

Token-Based

Uses industry-standard JWT tokens for stateless authentication

Azure AD Integration

Leverages Microsoft Azure AD for identity management

User Identity

Extracts user information from token claims (oid, upn, name)

Database Validation

Validates users exist in the internal employee database

Authentication Flow

The API implements a two-stage authentication and authorization process:
  1. JWT Validation - Validates the token signature, expiration, issuer, and audience
  2. User Loading - Verifies the user exists in the internal employee database

Obtaining an Access Token

Before making API requests, you must obtain a JWT access token from Azure AD.

Azure AD Configuration

AZURE_ISSUER_BASE_URL
string
required
The base URL for the Azure AD tenant issuing tokensFormat: https://login.microsoftonline.com/{tenant-id}/v2.0
AZURE_AUDIENCE
string
required
The Application ID URI of the backend API registered in Azure ADExample: api://your-backend-api-client-id

Token Request Example

To obtain a token, make a request to Azure AD’s token endpoint:
curl -X POST https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id={your-client-id}" \
  -d "scope=api://{backend-api-client-id}/.default" \
  -d "client_secret={your-client-secret}" \
  -d "grant_type=client_credentials"
Response:
{
  "token_type": "Bearer",
  "expires_in": 3599,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6..."
}

Making Authenticated Requests

Include the access token in the Authorization header of every API request using the Bearer scheme.

Authorization Header Format

Authorization: Bearer {access_token}

Example Request

curl -X GET https://api.example.com/api/empleados/me \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6..."

Token Validation Process

The API validates tokens using the express-oauth2-jwt-bearer library. The validation process checks:

Signature Verification

Verifies the token was signed by Azure AD using JWKS

Issuer Check

Ensures the token was issued by the configured Azure AD tenant

Audience Validation

Confirms the token was issued for this specific API

Expiration Check

Validates the token has not expired

Token Claims Extracted

The API extracts the following claims from validated JWT tokens:
oid
string
required
Object ID - The unique identifier for the user in Azure ADUsed to look up the employee in the internal database via the azure_oid field
upn
string
User Principal Name - The user’s email address (primary identifier)Falls back to preferred_username if upn is not present
preferred_username
string
Preferred Username - Alternative email/username claimUsed when upn is not available in the token
name
string
Display Name - The user’s full name

User Identity Object

After successful JWT validation, the API creates a userIdentity object attached to the request:
req.userIdentity = {
  email: "[email protected]",  // From upn or preferred_username
  azureId: "a1b2c3d4-..."      // From oid claim
}

User Database Validation

After JWT validation, the cargarUsuario middleware performs database validation:
  1. Extracts the azureId from req.userIdentity
  2. Queries the database for an employee with matching azure_oid
  3. Includes related data (role, vacation records)
  4. Attaches the employee object to req.empleado

Database Query

const empleado = await db.Empleado.findOne({
  where: { azure_oid: azureId },
  include: [
    { model: db.Rol_empleado, as: 'rol' },
    { model: db.Vacaciones, as: 'vacaciones' }
  ],
  attributes: ['id_empleado', 'correo', 'nombre']
});

Employee Object

After successful database validation, the employee object is available to controllers:
req.empleado = {
  id_empleado: 123,
  correo: "[email protected]",
  nombre: "John Doe",
  rol: { /* role data */ },
  vacaciones: [ /* vacation records */ ]
}

Error Responses

The authentication system returns specific error codes and messages for different failure scenarios.

401 Unauthorized - Invalid or Missing Token

Returned when the JWT token is invalid, expired, or missing from the request.
message
string
Human-readable error message
code
string
Machine-readable error code: invalid_auth_token
Example Response:
{
  "message": "Acceso Denegado. Token inválido, expirado o faltante.",
  "code": "invalid_auth_token"
}
Example Request:
curl -X GET https://api.example.com/api/empleados/me \
  -H "Authorization: Bearer invalid_or_expired_token"
Response:
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "message": "Acceso Denegado. Token inválido, expirado o faltante.",
  "code": "invalid_auth_token"
}

401 Unauthorized - Missing Azure ID

Returned when the token is valid but missing the required oid claim. Example Response:
{
  "message": "Autenticación requerida."
}

403 Forbidden - User Not Found

Returned when the user’s Azure AD identity (oid) is not found in the employee database. Example Response:
{
  "message": "Usuario no encontrado en el sistema."
}
Example Request:
curl -X GET https://api.example.com/api/empleados/me \
  -H "Authorization: Bearer {valid-token-for-unknown-user}"
Response:
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "message": "Usuario no encontrado en el sistema."
}

500 Internal Server Error - Database Error

Returned when there’s an error querying the employee database. Example Response:
{
  "message": "Error interno del servidor."
}

Error Code Reference

401 - Invalid Token

Token is invalid, expired, or missing

401 - Missing Azure ID

Token lacks required oid claim

403 - User Not Found

User’s Azure AD identity not in database

500 - Database Error

Internal error querying employee data

Complete Example

Here’s a complete example of authenticating and making an API request:

Step 1: Obtain Access Token

curl -X POST https://login.microsoftonline.com/your-tenant-id/oauth2/v2.0/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=your-client-id" \
  -d "scope=api://your-backend-api-client-id/.default" \
  -d "client_secret=your-client-secret" \
  -d "grant_type=client_credentials"

Step 2: Make Authenticated Request

curl -X GET https://api.example.com/api/empleados/me \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6..." \
  -H "Content-Type: application/json"

Step 3: Receive Response

{
  "id_empleado": 123,
  "correo": "[email protected]",
  "nombre": "John Doe",
  "rol": {
    "id_rol": 2,
    "nombre": "Usuario"
  }
}

Security Best Practices

Secure Token Storage

Never store tokens in local storage or cookies accessible to JavaScript

HTTPS Only

Always use HTTPS to prevent token interception

Token Expiration

Implement token refresh logic before expiration

Minimal Scopes

Request only the minimum required scopes

Implementation Reference

The authentication middleware is implemented in two files:
  • JWT Validation: src/middleware/authMiddleware.js - Validates Azure AD tokens
  • User Loading: src/middleware/cargarUsuario.js - Loads employee data from database
Both middlewares must be applied to protected routes in sequence:
import { authenticateAndExtract } from './middleware/authMiddleware.js';
import { cargarUsuario } from './middleware/cargarUsuario.js';

app.get('/api/protected-route', 
  authenticateAndExtract,  // First: Validate JWT
  cargarUsuario,           // Second: Load user from DB
  controllerFunction       // Finally: Handle request
);

Build docs developers (and LLMs) love