Skip to main content

How authentication works

The Mesa de Ayuda API uses JWT (JSON Web Tokens) with the Bearer scheme. After you log in via POST /api/auth/login, you receive a signed token. Include that token in the Authorization header of every subsequent request. Tokens are signed using HMAC-SHA256 (HS256 by default) with a server-side secret key. The server validates the signature and expiry on every request — no session state is stored.

Obtaining a token

Send your email and password to the login endpoint:
curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "your-password"
  }'
The response contains the token and the authenticated user’s profile:
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "user": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "email": "[email protected]",
    "rol": "USUARIO",
    "area": "Soporte",
    "nombre": "Carlos López"
  }
}

Including the token in requests

Pass the token as a Bearer token in the Authorization header:
curl http://localhost:8000/api/tickets/dashboard \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Do not include the word Bearer more than once, and do not omit it. The header must be in the exact format: Authorization: Bearer <token>.

Token expiry

Tokens expire after 120 minutes by default. You can change this by setting ACCESS_TOKEN_EXPIRE_MINUTES in your .env file:
.env
ACCESS_TOKEN_EXPIRE_MINUTES=60
Each token encodes an iat (issued at) and exp (expiry) claim as UTC Unix timestamps. Once expired, the token is rejected and you must log in again to obtain a new one.
The API does not currently support token refresh. When your token expires, call POST /api/auth/login again with your credentials.

Roles and permissions

Every user has a rol field embedded in their JWT payload. The API uses this role to authorize access to specific endpoints.
RoleDescription
ADMINFull access to all endpoints. Manages users, tickets, and system configuration.
MESAHelp desk staff. Can view, assign, and update all tickets.
AREAArea supervisor. Can view and manage tickets assigned to their area.
USUARIOTicket requester. Uses the public endpoints (/crear, /consultar) without authentication — no JWT token required.
Role checks are enforced by the require_roles dependency in app/core/deps.py. Endpoints declare which roles are allowed, and the server rejects requests from users with insufficient privileges.

Error responses

401 — Invalid or expired token

You receive a 401 response when:
  • The Authorization header is missing
  • The token signature is invalid
  • The token has expired
{
  "detail": "Token inválido o expirado"
}
Resolution: Log in again via POST /api/auth/login to obtain a fresh token.

403 — Insufficient role

You receive a 403 response when your token is valid but your role does not have permission to access the endpoint:
{
  "detail": "No autorizado"
}
Resolution: Use an account with the required role. Contact your administrator if you believe you should have access.

Implementation reference

The authentication logic is implemented across three files:
from passlib.context import CryptContext
from jose import jwt
from datetime import datetime, timezone, timedelta

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def create_access_token(payload: dict, secret: str, alg: str, expires_delta: timedelta):
    now = datetime.now(timezone.utc)
    exp = now + expires_delta
    to_encode = {**payload, "iat": int(now.timestamp()), "exp": int(exp.timestamp())}
    return jwt.encode(to_encode, secret, algorithm=alg)

def decode_token(token: str, secret: str, alg: str) -> dict:
    return jwt.decode(token, secret, algorithms=[alg])

Build docs developers (and LLMs) love