Skip to main content

Overview

The MABQ Agent API uses Azure Active Directory (Azure AD) JWT tokens for authentication. All requests must include a valid Bearer token from an authorized @transelec.cl user.

Authentication Flow

Required Headers

All authenticated requests must include an Authorization header:
Authorization: Bearer <AZURE_AD_ACCESS_TOKEN>
Authorization
string
required
Azure AD access token in Bearer format

Example Request

curl -X POST https://mabq-backend.run.app/ \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc..." \
  -H "Content-Type: application/json" \
  -d '{"message": "Show me recent assets"}'

Middleware Implementation

The authentication middleware is implemented in main.py as a FastAPI middleware:
@app.middleware("http")
async def strict_security_middleware(request: Request, call_next):
    # Bypass authentication for specific paths
    if request.method == "OPTIONS" or request.url.path in ["/docs", "/openapi.json", "/health"]:
        return await call_next(request)
    if request.method == "GET" and request.url.path == "/":
        return await call_next(request)

    # Extract and validate token
    auth_header = request.headers.get("Authorization", "")
    
    if auth_header.startswith("Bearer "):
        raw_token = auth_header.split(" ")[1]
        token = raw_token.strip().strip('"').strip("'").rstrip(',')
        
        # Validate JWT signature using Azure AD JWKS
        TENANT_ID = os.environ["AZURE_TENANT_ID"]
        CLIENT_ID = os.environ["AZURE_CLIENT_ID"]
        
        jwks_client = jwt.PyJWKClient(
            f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys"
        )
        signing_key = jwks_client.get_signing_key_from_jwt(token)
        
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            audience=CLIENT_ID,
            issuer=[
                f"https://login.microsoftonline.com/{TENANT_ID}/v2.0",
                f"https://sts.windows.net/{TENANT_ID}/"
            ]
        )

Token Validation

The middleware performs the following validation steps:

1. Token Extraction

The token is extracted from the Authorization header and sanitized:
raw_token = auth_header.split(" ")[1]
token = raw_token.strip().strip('"').strip("'").rstrip(',')

2. JWT Structure Validation

The token must have exactly 3 parts (header.payload.signature):
segmentos = len(token.split('.'))
if segmentos != 3:
    logger.error("EL TOKEN NO ES UN JWT VÁLIDO. No tiene 3 partes.")

3. Signature Verification

The token signature is verified using Azure AD’s public keys (JWKS):
jwks_client = jwt.PyJWKClient(
    f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys"
)
signing_key = jwks_client.get_signing_key_from_jwt(token)

payload = jwt.decode(
    token,
    signing_key.key,
    algorithms=["RS256"],
    audience=CLIENT_ID,
    issuer=[...]
)
algorithms
list[str]
default:"['RS256']"
Only RS256 (RSA) signatures are accepted
audience
string
Must match the AZURE_CLIENT_ID environment variable
issuer
list[str]
Valid Azure AD token issuers for the tenant

4. Domain Validation

The user’s email must end with @transelec.cl:
token_email = payload.get("preferred_username") or payload.get("upn") or payload.get("email")

if token_email and str(token_email).lower().endswith("@transelec.cl"):
    user_profile = {
        "email": token_email,
        "name": token_name,
        "tid": payload.get("tid") 
    }
else:
    denial_reason = f"Dominio no autorizado. Email: {token_email}"

User Profile

Upon successful authentication, the user profile is attached to the request state:
request.state.user = user_profile
email
string
User’s email address from the token (preferred_username, upn, or email claim)
name
string
User’s display name from the name claim
tid
string
Azure AD tenant ID from the token

Error Responses

The middleware returns a 403 Forbidden response for authentication failures:

Missing Token

{
  "error": "Acceso Denegado. No se proporcionó token de autenticación"
}

Expired Token

{
  "error": "Acceso Denegado. El token ha expirado."
}

Invalid Signature

{
  "error": "Acceso Denegado. FIRMA INVÁLIDA."
}

Unauthorized Domain

{
  "error": "Acceso Denegado. Dominio no autorizado. Email: [email protected]"
}

Generic Validation Error

{
  "error": "Acceso Denegado. Error crítico validando token: <error details>"
}
error
string
Detailed error message explaining the authentication failure

Exempt Paths

The following paths bypass authentication:
/docs
GET
Swagger UI documentation (development only)
/openapi.json
GET
OpenAPI schema (development only)
/
GET
Root path GET requests (bypasses auth, connects to ADK agent)
OPTIONS
OPTIONS
All OPTIONS requests (CORS preflight)
The middleware checks request.url.path in ["/docs", "/openapi.json", "/health"] but no /health endpoint is actually defined in the application. The root path / with GET method is exempt for the ADK agent endpoint.
*
OPTIONS
CORS preflight requests

Environment Variables

AZURE_TENANT_ID
string
required
Azure AD tenant ID (found in Azure Portal > Azure Active Directory > Overview)
AZURE_CLIENT_ID
string
required
Application (client) ID from Azure AD app registration

Logging

The middleware logs authentication events for audit purposes:
logger = logging.getLogger("MABQ_Audit")

# Successful authentication
logger.info(f" PERFIL VERIFICADO | Email: {user_profile['email']}")

# Failed authentication
logger.warning(f" BLOQUEO DE ACCESO | Motivo: {denial_reason}")

Debug Logging

For troubleshooting, the middleware logs token details:
logger.info(f" [AUTOPSIA] Token empieza con: {token[:15]} | Termina con: {token[-10:]}")
logger.info(f" [AUTOPSIA] Longitud: {len(token)} | Cantidad de puntos (.): {segmentos - 1}")
logger.info(f" [DEBUG] HEADER DEL TOKEN: {unverified_header}")
logger.info(f" [DEBUG] PAYLOAD DEL TOKEN: {unverified_payload}")
Debug logging includes unverified token contents. Ensure logs are secured in production.

Security Best Practices

  • Always use HTTPS to prevent token interception
  • Store tokens securely (never in localStorage)
  • Refresh tokens before expiration
  • Validate the audience claim matches your application
  • Monitor authentication logs for suspicious activity

Build docs developers (and LLMs) love