authentik en AWS: tu propio IdP con CloudFormation, IAM Identity Center y ALB

Tu propio proveedor de identidad en AWS, paso a paso y con los errores reales: authentik desplegado con CloudFormation, login a la consola de AWS y una app protegida por el ALB sin escribir código de login.

authentik en AWS: tu propio IdP con CloudFormation, IAM Identity Center y ALB

En este post vas a desplegar authentik, un proveedor de identidad open source, en tu propia cuenta de AWS, y lo vas a usar para entrar a la consola de AWS y para proteger una app que no tiene ni una línea de código de login. Todo con servicios administrados: ECS Fargate, RDS, un Application Load Balancer e IAM Identity Center.

Es el material de apoyo de mi charla en el AWS Community Day Paraguay 2026, pero está escrito para que puedas seguir sin haberla visto. Incluye los templates, los comandos y, sobre todo, los errores que me encontré armando la demo, que son los que no aparecen en los tutoriales.

Antes de arrancar, una aclaración: si solo querés probar authentik o correrlo para un equipo chico, Docker Compose es probablemente el camino más sencillo, y también el más barato: en una t4g.medium (2 vCPU, 4 GB) sale unos US$ 30 por mes en us-east-1, contando disco e IP pública. A cambio, los backups, el TLS y la redundancia quedan a tu cargo. Si ya tenés un cluster de Kubernetes, el Helm chart también es una buena opción.

El template oficial de CloudFormation está armado con otra escala en mente: servicios administrados, redes privadas y redundancia, con un costo que ronda los US$ 400 por mes. Para mí apunta a organizaciones medianas o grandes que ya viven en AWS. En este post uso una versión recortada de ese template, más barata, para mostrar cómo encaja authentik con IAM Identity Center y el ALB, y al final explico qué volver a agregar si lo querés llevar a producción.

Al final vas a tener:

  • authentik corriendo en https://auth.tudominio.com, desplegado con CloudFormation.
  • SSO a la consola de AWS con IAM Identity Center y SAML.
  • Usuarios y grupos que se crean y se borran solos en AWS cuando los cambiás en authentik, con SCIM.
  • Una app interna detrás del ALB que solo deja pasar a quien se logueó en authentik, con OIDC.

Asumo que sabés moverte en la consola de AWS y en una terminal. No hace falta experiencia previa con CloudFormation ni con authentik.

Qué vamos a armar

Arquitectura: el ALB recibe el tráfico, authentik corre en ECS con RDS y EFS, y se conecta con IAM Identity Center por SAML y SCIM
Arquitectura de la demo

El ALB recibe todo el tráfico y lo separa por host. Lo que va a auth llega a authentik, que guarda su estado en RDS y EFS. Lo que va a app pasa primero por la autenticación OIDC del ALB contra authentik. Por otro lado, authentik le manda a IAM Identity Center los usuarios y grupos por SCIM y le resuelve el login por SAML, y con eso se entra a la consola de AWS.

Antes de empezar

Necesitás una cuenta de AWS con permisos de administrador, la AWS CLI configurada y un dominio en el que puedas crear registros DNS. En los ejemplos uso auth.tudominio.com para authentik y app.tudominio.com para la app protegida; en las capturas vas a ver los míos, demo.melizeche.com y whoami.melizeche.com. Todo va en us-east-1: el certificado tiene que estar en la misma región que el stack.

El certificado de ACM

El ALB necesita un certificado de AWS Certificate Manager. Pedilo en ACM como certificado público, con los dos nombres (auth y app) y validación por DNS. ACM te da un registro CNAME por nombre: crealo en tu DNS y esperá a que el certificado pase a Issued. Si tu DNS está en Cloudflare, dejá esos registros en DNS only.

Certificado de ACM: los dos dominios validados pero el certificado sigue en Pending validation

Ojo con esta captura: los dos dominios dicen Success y el certificado sigue en Pending validation. En mi caso terminó en FAILED con motivo CAA_ERROR. Mi dominio tenía registros CAA que autorizaban solo a otras autoridades certificantes, y Amazon no estaba entre ellas. Lo arreglé agregando un registro CAA 0 issue "amazon.com" y pidiendo un certificado nuevo, porque uno en FAILED no se puede reintentar. Si lo pasás por alto, el error aparece recién al crear el stack, en el listener HTTPS del ALB, y con un mensaje que no ayuda: "must have a fully-qualified domain name, a supported signature, and a supported key size".

Para ver el motivo real de un certificado fallido:

aws acm describe-certificate --region us-east-1 --certificate-arn <arn> \
  --query "Certificate.{Status:Status,Reason:FailureReason}"

Cuánto cuesta

Con el template oficial y sus valores por defecto, el stack cuesta alrededor de US$ 0,55 por hora (unos US$ 400 por mes) en us-east-1. La mayor parte es RDS Multi-AZ. Para la demo armé una variante mínima que baja a unos US$ 0,13 por hora, o sea unos US$ 6 si lo dejás prendido 48 horas. En la sección de producción está el detalle y una configuración intermedia. Son precios on-demand, sin tráfico ni impuestos; revisalos en la página de precios de cada servicio antes de decidir.

Paso 1: desplegar authentik con CloudFormation

authentik publica un template oficial de CloudFormation, documentado en la guía de instalación en AWS. Crea una VPC, una base RDS PostgreSQL, un cluster ECS con una tarea para el server y otra para el worker, un ALB con tu certificado, dos volúmenes EFS y los secretos en Secrets Manager. Desde la versión 2025.10 authentik ya no usa Redis, así que no hay ElastiCache.

Para la demo usé una variante mínima del template oficial. Estos son los cambios:

QuéOficialVariante demo
Base de datosdb.m5.large, Multi-AZdb.t4g.small, Single-AZ, sin backups
Tareas server / worker2 / 21 / 1
NAT Gateway1ninguno: las tareas van en subredes públicas, con el security group aceptando solo al ALB
Al borrar el stackRDS deja snapshot, EFS y logs se conservanse borra todo

Los archivos están en https://github.com/melizeche/authentik_aws_demo. Para crear el stack desde la consola: CloudFormation, Create stack, Upload a template file.

Crear el stack subiendo el template

En los parámetros, lo obligatorio es el ARN del certificado. Fijá también la versión de authentik: usá 2026.8.4 o una posterior. Desde esa versión el template oficial se conecta a PostgreSQL con TLS y deja que RDS elija la versión menor de Postgres (PR #26807). Si usás un template anterior, fijá una versión de Postgres que RDS ofrezca hoy (yo usé 17.11), porque el default viejo es 17.1 y RDS va retirando versiones menores. Las versiones disponibles las podés listar con aws rds describe-db-engine-versions --engine postgres.

Parámetros del stack

Al final tenés que tildar que CloudFormation puede crear roles de IAM. Desde la CLI es lo mismo con aws cloudformation deploy ... --capabilities CAPABILITY_IAM. Mientras se crea, la pestaña Events muestra cada recurso; si algo falla, el primer CREATE_FAILED tiene el motivo real.

Eventos del stack mientras se crea

Lo que me trabó en este paso:

  • gp3 con 10 GB. Para ahorrar cambié el disco de la base a gp3 y RDS lo rechazó: para PostgreSQL, gp3 exige al menos 20 GB. Volví a gp2, que es lo que usa el template oficial.
  • no pg_hba.conf entry ... no encryption. RDS con PostgreSQL 15 o más rechaza conexiones sin TLS, y authentik por defecto se conecta con sslmode: disable, así que los contenedores quedan reintentando para siempre. Está corregido en el template oficial desde authentik 2026.8.4 (PR #26807). Si usás un template anterior, la solución es agregar AUTHENTIK_POSTGRESQL__SSLMODE=require al server y al worker.
  • El stack queda en CREATE_IN_PROGRESS horas. El template no activa el circuit breaker de ECS, entonces si el contenedor se cae en loop CloudFormation espera. Para destrabarlo, bajá a 0 las tareas de los dos servicios en ECS, esperá a que el stack termine y después aplicá el arreglo con un update.

Cuando el stack termine, copiá el LoadBalancerDNS de la pestaña Outputs, creá un CNAME auth que apunte ahí y entrá a https://auth.tudominio.com/if/flow/initial-setup/ para crear el usuario akadmin. Hacelo enseguida: hasta ese momento, cualquiera que encuentre la URL puede quedarse con la instancia.

Paso 2: SSO a la consola de AWS con IAM Identity Center

La idea es que nadie tenga usuarios IAM propios: entrás a authentik y desde ahí llegás a la consola de AWS con los permisos de tu grupo. Seguí la guía oficial de la integración; acá van los pasos con lo que no dice.

Antes de empezar. IAM Identity Center tiene que estar habilitado como instancia de organización, en la cuenta de management de AWS Organizations; una instancia de cuenta no da acceso a las cuentas de AWS. Si tu cuenta no está en una organización, creála con aws organizations create-organization --feature-set ALL y habilitá Identity Center. Y tené en cuenta que cambiar el identity source afecta a todos los que hoy entran por el access portal: desde ese momento el login pasa por authentik.

IAM Identity Center con el directorio propio como identity source

1. Bajá la metadata de AWS. En Identity Center, Settings, Identity source, Actions, Change identity source, External identity provider. Bajá la metadata del service provider y dejá la pestaña abierta.

Metadata del service provider en IAM Identity Center

2. Creá la aplicación en authentik. Applications, New Application, y como tipo de provider SAML Provider from Metadata, subiendo el archivo de AWS.

Nueva aplicación en authentik
Provider SAML a partir de la metadata de AWS

En el provider, el NameID tiene que ser authentik default SAML Mapping: Email: AWS cruza el NameID con el username de Identity Center, y vamos a hacer que ese username sea el email.

NameID con el mapping de Email

3. Subí la metadata de authentik a AWS. En authentik, en el provider, Related objects, Metadata, Download. En la pestaña de AWS, subí ese archivo en IdP SAML metadata (no hace falta cargar el certificado aparte, ya viene adentro), escribí ACCEPT y confirmá.

Identity source cambiado a authentik

4. Asigná permisos. En AWS accounts, tildá la cuenta, Assign users or groups, el grupo que va a llegar por SCIM (aws-admins) y un permission set. Para una demo, ReadOnlyAccess alcanza.

Asignar el grupo aws-admins, creado por SCIM, a la cuenta
Elegir el permission set ReadOnlyAccess
La asignación terminada

El error que me encontré: al probar el login, authentik respondió Bad Request: ACS URL of https://us-east-1.sso.signin.aws/... doesn't match Provider ACS URL of https://us-east-1.signin.aws/.... Al importar la metadata, el provider se quedó con una URL de ACS distinta de la que AWS usa en el request. Fijate que en la captura del paso 1 AWS muestra la que tiene sso.. La solución es editar el provider y pegar en ACS URL exactamente la que muestra AWS. El Audience no se toca: es el issuer de Identity Center y no tiene por qué compartir el host con el ACS.

Paso 3: aprovisionamiento automático con SCIM

Con SAML solo, IAM Identity Center no te deja entrar si el usuario no existe ya en su directorio. SCIM resuelve eso: authentik crea, actualiza y borra usuarios y grupos en AWS cada vez que cambian. Es lo que hace que dar de baja a alguien en authentik le corte el acceso a AWS. Como el permission set del paso anterior se asigna a un grupo que llega por SCIM, en la práctica ese paso va después de este.

1. Activá el aprovisionamiento en AWS. En Settings, Automatic provisioning, Enable. Copiá el endpoint de SCIM y el token: el token se muestra una sola vez.

Endpoint y token de SCIM en IAM Identity Center

2. Creá un property mapping. En authentik, Customization, Property Mappings, SCIM Provider Mapping. El nombre tiene que ordenarse después del mapping por defecto, por eso el zz. Saca el atributo photos y usa el email como userName, para que coincida con el NameID de SAML:

return {
    "photos": None,
    "userName": request.user.email,
}
Property mapping para AWS

3. Creá el provider SCIM con la URL y el token de AWS, y Compatibility Mode en AWS.

Provider SCIM apuntando a AWS

En Group Filter elegí solo el grupo que querés mandar a AWS y dejá Exclude service accounts activado. Sin filtro, authentik intenta sincronizar a todos los usuarios, akadmin incluido.

Filtro por grupo en el provider SCIM

4. Asociá el SCIM a la aplicación. En la aplicación de AWS, Backchannel providers, agregá el provider SCIM. Sin esto la tarea de sincronización termina con el warning "No provider found. Is it assigned to an application?".

El provider SCIM como backchannel de la aplicación

5. Probá con dry run. El provider tiene un modo Dry run que no manda nada a AWS y deja en los logs de la tarea el request que habría hecho. Vale la pena mirarlo antes de activar el sync de verdad, porque ahí vi el siguiente problema.

Dry run: el usuario sale con el nombre vacío

El usuario admin1 no tenía nombre en authentik, y el body salía con givenName: "" y displayName: "". IAM Identity Center exige nombre, apellido y display name, así que lo rechazó. Peor: en authentik el error aparecía como "Network error communicating with remote system", que te manda a buscar un problema de red que no existe. Mirando el código, el mensaje real de AWS queda escondido cuando la respuesta es un 4xx; el detalle está en los logs del worker, en la línea Failed to send SCIM request. La solución es simple: que todo usuario que vaya a AWS tenga nombre y apellido.

El error real de AWS aparece como "Network error communicating with remote system"

Con eso, desactivá el dry run, lanzá el sync y fijate que el usuario y el grupo aparezcan en Users y Groups de Identity Center. Después asigná el permission set (paso 4 de la sección anterior) y probá entrar desde el dashboard de authentik: tenés que llegar a la consola sin otro login.

El grupo aws-admins en Identity Center, creado por SCIM

Paso 4: un ALB con OIDC delante de una app sin código de login

El Application Load Balancer puede autenticar usuarios antes de mandarle el request a la app, con una acción authenticate-oidc en la regla del listener. La app no implementa ningún login: el ALB redirige a authentik, canjea el código por los tokens y le pasa a la app la identidad del usuario en headers. Para cualquier herramienta interna que hoy está protegida con una contraseña compartida, esto es oro.

La configuración está en la guía oficial de la integración con AWS ALB. Acá van los pasos tal como los hice, con los errores que me encontré y que la guía no cubre.

1. Creá la aplicación en authentik con un provider OAuth2/OpenID:

  • Client type: Confidential.
  • Redirect URI de tipo Authorization, en modo Strict: https://app.tudominio.com/oauth2/idpresponse. Esa ruta la maneja el ALB, no tu app. En versiones anteriores a 2026.5 no hay tipo: cargá solo esa URI.
  • Signing key: el certificado autofirmado de authentik, para que el ID token salga firmado con RS256.
  • En Advanced protocol settings, desmarcá Include claims in id_token. El ALB lee los claims del userinfo, y si quedan también en el token suman al límite de tamaño del ALB.
  • En Bindings, el grupo que puede entrar.

Copiá el client ID y el client secret.

2. Desplegá la app y la regla del ALB. Armé un template aparte que reusa el cluster, la VPC y el ALB del stack de authentik y crea el servicio ECS de la app, su target group y la regla del listener 443 con dos acciones: primero authenticate-oidc y después forward. La parte importante de la regla:

Actions:
  - Type: authenticate-oidc
    Order: 1
    AuthenticateOidcConfig:
      Issuer: https://auth.tudominio.com/application/o/<slug>/
      AuthorizationEndpoint: https://auth.tudominio.com/application/o/authorize/
      TokenEndpoint: https://auth.tudominio.com/application/o/token/
      UserInfoEndpoint: https://auth.tudominio.com/application/o/userinfo/
      ClientId: <client id>
      ClientSecret: <client secret>
      Scope: openid email profile
      OnUnauthenticatedRequest: authenticate
  - Type: forward
    Order: 2
    TargetGroupArn: <target group de la app>

Después creá el CNAME app apuntando al mismo DNS del ALB. Los dos nombres van al mismo balanceador; la regla separa el tráfico por host.

3. Lo que tuve que arreglar, en el orden en que apareció:

  • El target nunca quedaba healthy (Target.Timeout). El security group del ALB del template de authentik solo permite salida al puerto 9000 de las tareas de authentik. Hay que agregarle salida al puerto 80 del security group de la app. Mientras tanto, el circuit breaker de ECS hacía fallar el stack y el rollback borraba la evidencia.
  • Redirect URI Error en authentik: el redirect URI cargado en el provider no coincidía exactamente con el que manda el ALB. Revisá el redirect_uri en la URL de esa pantalla y que el client_id sea el del provider que estás editando.
Redirect URI Error en authentik
  • 500 en /oauth2/idpresponse. Después del login, es el ALB el que llama al token endpoint de authentik, no el navegador. Acá eso significa llamarse a sí mismo por su IP pública, y el security group no permitía salida al 443. Agregar esa regla lo resolvió.
  • 561 Authentication Error. En los logs del server de authentik, el POST /application/o/token/ del user agent AWSELB/2.0 volvía con 400, y un warning justo antes decía Invalid client secret. El secret de la regla no era el del provider. Se corrige desde la consola de EC2 editando la regla, sin redesplegar.

4. Qué recibe la app. Con todo andando, la app recibe estos headers del ALB:

  • X-Amzn-Oidc-Data: un JWT firmado por el ALB con ES256, con los claims del userinfo de authentik (nombre, email, grupos).
  • X-Amzn-Oidc-Identity: el sub del usuario, en texto plano y sin firma.
  • X-Amzn-Oidc-Accesstoken: el access token que emitió authentik, también sin firma del ALB. Ni este ni el anterior sirven para identificar al usuario.
Headers que recibe la app detrás del ALB (tokens tapados)

Una app real tiene que verificar la firma de X-Amzn-Oidc-Data con la clave pública del ALB para la región (https://public-keys.auth.elb.<región>.amazonaws.com/<kid>) y después chequear que el campo signer del header sea el ARN de tu ALB, que exp no haya pasado, que iss sea el issuer de authentik y que client sea tu client ID. Ojo con un detalle: el ALB codifica el JWT en base64url con padding =, y la firma cubre el header y el payload tal como llegan, padding incluido. Muchas librerías de JWT rechazan ese padding, pero si se lo sacás antes de verificar cambiás los bytes firmados, y la firma falla cada vez que el payload tiene padding. Me pasó: la app anduvo un tiempo y después empezó a responder “Signature verification failed”. Hay que verificar la firma sobre los segmentos originales y quitar el padding solo para decodificarlos. Y todo esto solo es seguro si la app acepta tráfico únicamente desde el ALB: si alguien llega directo al contenedor, puede inventarse los headers. Para la demo escribí una app chica en Python que hace exactamente eso y muestra quién está logueado; está en el repo.

El header X-Amzn-Oidc-Data decodificado: firmado por el ALB con ES256, con los claims del usuario y sus grupos

Los errores, en una tabla

Si llegaste acá buscando un mensaje de error, esta es la versión corta de todo lo anterior.

SíntomaCausaArreglo
Certificado de ACM en FAILED, motivo CAA_ERROREl dominio tiene registros CAA que no autorizan a AmazonAgregar 0 issue "amazon.com" y pedir un certificado nuevo
Listener HTTPS falla: "must have a fully-qualified domain name, a supported signature..."El certificado no está emitidoEsperar a Issued o revisar el motivo del fallo
RDS: "Invalid storage size for engine name postgres and storage type gp3: 10"gp3 exige al menos 20 GB en PostgreSQLUsar gp2 o subir a 20 GB
no pg_hba.conf entry ... no encryptionRDS con PostgreSQL 15+ exige TLS y el template oficial anterior a 2026.8.4 no lo configuraAUTHENTIK_POSTGRESQL__SSLMODE=require en server y worker (corregido en 2026.8.4, #26807)
Stack en CREATE_IN_PROGRESS por horasTareas ECS en loop y sin circuit breakerBajar las tareas a 0, dejar terminar el stack y corregir con un update
Bad Request: ACS URL ... doesn't match Provider ACS URLEl provider SAML guardó otra URL de ACS al importar la metadataPegar en el provider la ACS URL que muestra AWS
SCIM: "Network error communicating with remote system"En realidad es un 4xx de AWS, por ejemplo un usuario sin nombreVer Failed to send SCIM request en los logs del worker y completar el nombre
SCIM: "No provider found. Is it assigned to an application?"El provider SCIM no está como backchannel de la aplicaciónAgregarlo en Backchannel providers
ECS: "ECS Deployment Circuit Breaker was triggered" y target Target.TimeoutEl security group del ALB no tiene salida hacia la appEgress del ALB al puerto de la app
authentik: Redirect URI ErrorEl redirect URI del provider no coincide con el que manda el ALBhttps://<host>/oauth2/idpresponse en modo Strict
500 en /oauth2/idpresponseEl ALB no puede llegar al token endpointEgress del ALB al 443
500 en /oauth2/idpresponse con usuarios de muchos gruposLos claims más el access token superan 11 KBUn scope mapping profile propio que devuelva menos grupos
561 Authentication Errorauthentik rechazó el canje del código; en mi caso, Invalid client secretCorregir el secret en la regla del listener
La app: Signature verification failed después de andar bienSe le sacó el padding = al JWT antes de verificar, y la firma lo incluyeVerificar la firma sobre los segmentos tal como llegan

De demo a producción

La variante demo está pensada para prender, mostrar y borrar. Un proveedor de identidad en producción es otra cosa: si se cae, nadie entra a nada. Esta es la configuración mínima que yo usaría para usuarios reales. Es una propuesta, no un template probado.

Template oficialVariante demoMínimo para producción
Base de datosdb.m5.large Multi-AZdb.t4g.small Single-AZdb.t4g.small Multi-AZ
Backups de RDSretención por defectodesactivados7 días, con point-in-time recovery
Deletion protectionnonosí
TLS a Postgresrequire desde 2026.8.4requireverify-full con el CA de RDS
Tareas server / worker2 / 21 / 12 / 1
Red de las tareasprivada, con NATpública, sin NATprivada, con NAT
Circuit breaker de ECSnonosí, con rollback
Backups de EFSnonosí
Logssin vencimiento3 días30 días
Costo aproximado (us-east-1)~US$ 400/mes~US$ 97/mes~US$ 172/mes

Lo que no entra en la tabla:

  • AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS. Desde la 2026.8, authentik solo confía en los headers X-Forwarded-* si vienen de una red de proxies confiables. Detrás del ALB conviene setearla con el CIDR de la VPC para tener las IPs reales en los logs y las políticas.
  • Un SessionCookieName por regla. Si varias apps comparten el ALB, cada regla con authenticate-oidc necesita su propio nombre de cookie de sesión. Y revisá el SessionTimeout: por defecto son 7 días; en la demo usé una hora.
  • La configuración de authentik también es infraestructura. Exportá las aplicaciones, providers y mappings como blueprints, o manejalos con el provider de Terraform goauthentik/authentik. Si tenés que recrear el stack, la recuperás en minutos.

Los costos salen de las tarifas on-demand de cada servicio en us-east-1, sin tráfico, NAT por GB, logs ni impuestos.

Limpieza y recursos

Cuando termines, borrá primero el stack de la app y después el de authentik: la regla de la app cuelga del listener del ALB y traba el borrado si va al revés. Si agregaste reglas de security group a mano mientras depurabas, sacalas antes. En la variante demo nada tiene Retain ni deja snapshot, así que con eso la factura vuelve a cero; con el template oficial, revisá que no queden EFS, log groups ni el snapshot de RDS.

Si cambiaste el identity source de IAM Identity Center a authentik, volvelo al directorio de Identity Center antes de borrar el stack. Si no, nadie va a poder entrar por el access portal.

Recursos