Angular · Nginx · FastAPI · Seguridad

Configuración recomendada para que una API no quede expuesta al frontend publico

Cuando una aplicación Angular habla directamente con un backend, cualquier credencial que viaje al navegador termina expuesta. La solución correcta no es esconder mejor la clave, sino mover la confianza al lado servidor.

Esta guía resume el patrón aplicado: Angular llama al dominio principal de la web, Nginx reenvia la petición a una API privada y FastAPI solo acepta peticiones que lleguen con una cabecera interna inyectada por el proxy.

La idea central

Un secreto solo es secreto si nunca llega al navegador. Si Angular lo envia, el secreto ya se ha perdido.

1. Por que el login directo desde frontend es mala idea

Una cuenta técnica compartida dentro del bundle del frontend puede extraerse del JavaScript compilado, del panel de red o del propio código fuente. Eso significa que el backend queda publico de facto aunque exija token.

Este problema aparece mucho cuando el frontend pide un token con usuario y contraseña fijos y luego reutiliza ese bearer en todas las peticiones.

2. Arquitectura recomendada

La solución practica consiste en separar el flujo entre frontend publico, reverse proxy confiable y API protegida.

Diagrama del flujo recomendado: navegador al dominio principal, proxy Nginx a la API privada y FastAPI validando una cabecera interna.
Frontend publico

Angular solo habla con el dominio principal del sitio. No guarda credenciales privadas del backend.

Reverse proxy confiable

Nginx añade una cabecera interna que solo existe en el lado servidor.

API protegida

FastAPI rechaza llamadas directas salvo que la cabecera interna coincida con el secreto esperado.

Resultado

La API sigue sirviendo a la web, pero ya no puede reutilizarse directamente por terceros solo con abrir DevTools.

3. Flujo recomendado en Nginx

Una forma limpia de exponer la funcionalidad es publicar una ruta dedicada como /api/servicio-interno/ en el dominio principal y redirigirla al host privado de la API.

server {
    server_name example.com;

    location ^~ /api/internal-service/ {
        proxy_pass https://api-interna.example.com/;
        proxy_http_version 1.1;

        proxy_set_header Host api-interna.example.com;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Internal-Api-Key example.internal.key.please.change;
    }

    location / {
        proxy_pass http://frontend_ssr;
    }
}

4. Validacion en FastAPI

El backend debe validar una cabecera interna contra una variable de entorno. El navegador nunca conoce ese valor; solo Nginx.

import os
import secrets
from fastapi import Depends, Header, HTTPException, status

INTERNAL_API_KEY = os.getenv("INTERNAL_API_KEY", "")

def require_internal_api_key(x_internal_api_key: str | None = Header(default=None)) -> None:
    if not INTERNAL_API_KEY:
        raise HTTPException(status_code=500, detail="INTERNAL_API_KEY is missing")

    if not x_internal_api_key or not secrets.compare_digest(x_internal_api_key, INTERNAL_API_KEY):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")

@app.get("/api/data")
def get_data(_: None = Depends(require_internal_api_key)):
    return {"status": "ok"}

5. Que debe hacer Angular

Angular debe consumir la ruta proxy del dominio publico y eliminar cualquier usuario, contraseña, bearer bootstrap o intercambio técnico de tokens.

Dicho de otra forma, Angular usa el servicio, pero no forma parte del perimetro de confianza.

export const environment = {
  production: true,
  backendApiBaseUrl: 'https://example.com/api/internal-service'
};

this.http.get(`${environment.backendApiBaseUrl}/api/data`).subscribe({
  next: (response) => {
    this.data = response;
  },
  error: (error) => {
    this.errorMessage = 'The request could not be completed.';
  }
});

6. Como verificar que la configuración es correcta

La comprobación más útil es probar las dos rutas: la ruta publica proxy debe funcionar y la ruta directa de la API privada debe devolver 403.

curl -i https://example.com/api/internal-service/api/data
# Expected: 200

curl -i https://api-interna.example.com/api/data
# Expected: 403

7. Checklist de despliegue

Usar una INTERNAL_API_KEY larga y aleatoria en el entorno del backend.

Configurar Nginx en el dominio publico para inyectar esa misma cabecera.

Mantener el dominio privado de la API sin esa cabecera inyectada.

Apuntar Angular en producción a la ruta publica proxy, no al host privado de la API.

Verificar que la ruta publica proxy responde 200 y la ruta directa privada responde 403.

Errores habituales

Inyectar el mismo secreto desde Angular o guardarlo en ficheros de entorno que llegan al navegador.

Reutilizar un /api ya ocupado en vez de crear un namespace dedicado como /api/crypto/.

Olvidar los file replacements y enviar URLs localhost a produccion.

Creer que CORS o esconder botones equivale a proteccion backend.

Cuando es recomendable este patron

Usalo cuando la web necesite la API pero no quieras publicar credenciales reutilizables ni permitir el uso directo del endpoint por terceros.

Si la API debe ser publica por definición, abrirla con buen rate limiting puede ser más simple. Si los datos dependen del usuario, entonces lo correcto es autenticación real por usuario.