Saltar a contenido

openidc: Implementación de OpenID Connect Relying Party y OAuth 2.0 Resource Server en Lua para NGINX / nginx-module-lua

Instalación

Si no has configurado la suscripción al repositorio RPM, regístrate. Luego, puedes proceder con los siguientes pasos.

CentOS/RHEL 7 o Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-openidc

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-openidc

Para usar esta biblioteca Lua con NGINX, asegúrate de que nginx-module-lua esté instalado.

Este documento describe lua-resty-openidc v1.9.0 lanzado el 01 de julio de 2026.


Estado CI Certificación OpenID

lua-resty-openidc

lua-resty-openidc es una biblioteca para NGINX que implementa la funcionalidad de Relying Party (RP) de OpenID Connect y/o la funcionalidad de Resource Server (RS) de OAuth 2.0.

Cuando se utiliza como un Relying Party de OpenID Connect, autentica a los usuarios contra un Proveedor de OpenID Connect utilizando OpenID Connect Discovery y el Perfil Básico del Cliente (es decir, el flujo de Código de Autorización). Cuando se utiliza como un Resource Server de OAuth 2.0, puede validar los Tokens de Acceso Bearer de OAuth 2.0 contra un Servidor de Autorización o, en caso de que se utilice un JSON Web Token como Token de Acceso, la verificación puede realizarse contra un secreto/clave preconfigurada.

Mantiene sesiones para usuarios autenticados aprovechando lua-resty-session, ofreciendo así una opción configurable entre almacenar el estado de la sesión en una cookie del navegador del lado del cliente o utilizar uno de los mecanismos de almacenamiento del lado del servidor shared-memory|memcache|redis.

Soporta el almacenamiento en caché a nivel de servidor de documentos de Discovery resueltos y Tokens de Acceso validados.

Puede utilizarse como un proxy inverso que termina OAuth/OpenID Connect frente a un servidor de origen, de modo que los servidores/servicios de origen puedan estar protegidos con los estándares relevantes sin implementar esos estándares en el propio servidor.

Configuración de Ejemplo para Google+ Signin

Configuración de ejemplo nginx.conf para autenticar usuarios contra Google+ Signin, protegiendo una ruta de proxy inverso.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
  lua_ssl_verify_depth 5;

  # caché para documentos de metadatos de discovery
  lua_shared_dict discovery 1m;
  # caché para JWKs
  lua_shared_dict jwks 1m;

  server {
    listen 8080;

    location / {

      access_by_lua_block {

          local opts = {
             -- la URI de redirección completa debe estar protegida por este script
             -- si la URI comienza con un / la URI de redirección completa se convierte en
             -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
             -- a menos que el esquema haya sido anulado utilizando opts.redirect_uri_scheme o un encabezado X-Forwarded-Proto en la solicitud entrante
             redirect_uri = "https://MY_HOST_NAME/redirect_uri",
             -- hasta la versión 1.6.1 debías especificar
             -- redirect_uri_path = "/redirect_uri",
             -- y no podías establecer el nombre de host

             -- El punto final de discovery del OP. Habilitar para obtener la URI de todos los puntos finales (Token, introspección, cierre de sesión...)
             discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- El acceso al punto final Token del OP requiere autenticación. Se admiten varios modos de autenticación:
             --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
             -- o Si token_endpoint_auth_method está configurado como "client_secret_basic", "client_secret_post" o "client_secret_jwt", la autenticación al punto final Token se realiza utilizando client_id y client_secret
             --   Para OPs no conformes con OAuth 2.0 RFC 6749 para la Autenticación del Cliente (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1)
             --   client_id y client_secret DEBEN ser invariantes cuando se codifican en URL
             client_id = "<client_id>",
             client_secret = "<client_secret>",
             -- o Si token_endpoint_auth_method está configurado como "private_key_jwt", la autenticación al punto final Token se realiza utilizando client_id, client_rsa_private_key y client_rsa_private_key_id para calcular un JWT firmado
             --   client_rsa_private_key es la clave privada RSA que se utilizará para firmar el JWT generado por lua-resty-openidc para la autenticación al OP
             --   client_rsa_private_key_id (opcional) es el id de clave que se debe establecer en el encabezado JWT para identificar qué clave pública debe usar el OP para verificar la firma del JWT
             --client_id = "<client_id>",
             --client_rsa_private_key=[[-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAiThmpvXBYdur716D2q7fYKirKxzZIU5QrkBGDvUOwg5izcTv
[...]
h2JHukolz9xf6qN61QMLSd83+kwoBr2drp6xg3eGDLIkQCQLrkY=
-----END RSA PRIVATE KEY-----]],
             --client_rsa_private_key_id="key id#1",
             --   Algoritmo de firma para el JWT de afirmación del cliente. Por defecto es RS256 para private_key_jwt y HS256 para client_secret_jwt.
             --   Cuando token_endpoint_auth_signing_alg_values_supported se anuncia en discovery, el valor configurado debe ser compatible con el OP.
             --client_jwt_assertion_alg = "PS256",
             --   Audiencia para el JWT de afirmación del cliente. Por defecto es la URL del punto final que se está llamando.
             --   Establece esto cuando el punto final se alcanza a través de una URL interna pero el OP espera su URL externa del punto final de token como audiencia.
             --client_jwt_assertion_audience = "https://op.example.com/token",
             --   Duración de vida expresada en segundos del JWT firmado generado por lua-resty-openidc para la autenticación al OP.
             --   (utilizado cuando token_endpoint_auth_method está configurado como "private_key_jwt" o "client_secret_jwt"). Por defecto es 60 segundos.
             --client_jwt_assertion_expires_in = 60,
             -- Al usar https para cualquier punto final del OP, se puede exigir la verificación del certificado SSL ("yes") o no ("no").
             --ssl_verify = "no",
             -- La conexión keepalive con el OP puede habilitarse ("yes") o deshabilitarse ("no").
             --keepalive = "no",

             --response_mode=form_post puede usarse para hacer que lua-resty-openidc utilice el [Modo de Respuesta de Formulario OAuth 2.0](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). *Nota* para navegadores modernos necesitarás establecer [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) en `None` con form_post a menos que tu Proveedor de OpenID Connect y Relying Party compartan el mismo dominio.
             --authorization_params = { hd="zmartzone.eu" },
             -- Cuando use_par está configurado como verdadero, lua-resty-openidc utilizará Solicitudes de Autorización Pushed de OAuth 2.0 (PAR, RFC 9126).
             -- Los parámetros de solicitud de autorización se publican en el pushed_authorization_request_endpoint desde discovery,
             -- y el navegador se redirige al punto final de autorización con el request_uri devuelto.
             --use_par = false,
             -- Anula el punto final PAR de discovery cuando sea necesario.
             --pushed_authorization_request_endpoint = "https://MY_OP/pushed_authorization_request",
             -- Anula el método de autenticación del cliente para el punto final PAR. Por defecto es token_endpoint_auth_method.
             --pushed_authorization_request_endpoint_auth_method = "client_secret_basic",
             -- Cuando use_dpop está configurado como verdadero, lua-resty-openidc enviará JWTs de prueba DPoP (RFC 9449)
             -- al punto final de token y utilizará tokens de acceso vinculados a DPoP para userinfo.
             -- La implementación inicial de DPoP admite pruebas ES256, RS256 y PS256 y vuelve a intentar llamadas de token/userinfo
             -- una vez cuando el OP devuelve un desafío DPoP-Nonce.
             -- Las solicitudes de autorización incluyen dpop_jkt para vincular el código de autorización a la clave pública DPoP configurada.
             -- Se espera que las respuestas de token devuelvan token_type=DPoP.
             -- Si el OP proporciona DPoP-Nonce en una respuesta de token exitosa, se reutiliza para solicitudes de token subsiguientes.
             --use_dpop = false,
             --dpop_signing_alg = "ES256",
             --dpop_private_key = "clave privada codificada en PEM que coincide con dpop_signing_alg",
             --dpop_public_jwk = {
             --  kty = "EC",
             --  crv = "P-256",
             --  x = "...",
             --  y = "..."
             --},
             --scope = "openid email profile",
             -- Refresca el id_token de los usuarios después de 900 segundos sin requerir re-autenticación
             --refresh_session_interval = 900,
             --iat_slack = 600,
             --redirect_uri_scheme = "https",
             --logout_path = "/logout",
             --redirect_after_logout_uri = "/",
             -- Dónde debe ser redirigido el usuario después de cerrar sesión del RP. Esta opción anula cualquier end_session_endpoint que el OP pueda haber proporcionado en la respuesta de discovery.
             --redirect_after_logout_with_id_token_hint = true,
             -- Si la redirección después del cierre de sesión debe incluir el id token como un indicio (si está disponible). Esta opción se utiliza solo si redirect_after_logout_uri está configurado.
             --redirect_after_logout_with_client_id = true,
             -- Si la redirección después del cierre de sesión debe incluir el client id cuando no se envía id_token_hint (si está disponible).
             --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
             -- Dónde solicita el RP que el OP redirija al usuario después del cierre de sesión. Si esta opción se establece en una URI relativa, será relativa al punto final de cierre de sesión del OP, no al del RP.

             --accept_none_alg = false
             -- si tu Proveedor de OpenID Connect no firma sus id tokens
             -- (utiliza el algoritmo de firma "none") entonces establece esto en verdadero.

             --accept_unsupported_alg = true
             -- si deseas rechazar tokens firmados utilizando un algoritmo
             -- no soportado por lua-resty-jwt establece esto en falso. Si
             -- lo dejas sin configurar o lo estableces en verdadero, la firma del token no será
             -- verificada cuando se utilice un algoritmo no soportado.

             --renew_access_token_on_expiry = true
             -- si este plugin intentará renovar silenciosamente el token de acceso una vez que haya expirado si hay un token de refresco disponible.
             -- si no logra renovar el token, el usuario será redirigido al punto final de autorización.
             --access_token_expires_in = 3600
             -- Tiempo de vida predeterminado en segundos del access_token si no hay atributo expires_in presente en la respuesta del punto final de token.

             --access_token_expires_leeway = 0
             --  Margen de expiración para la renovación del access_token. Si esto se establece, la renovación ocurrirá access_token_expires_leeway segundos antes de la expiración del token. Esto evita errores en caso de que el access_token expire justo al llegar al Servidor de Recursos OAuth.

             --force_reauthorize = false
             -- Cuando force_reauthorize está configurado como verdadero, el flujo de autorización se ejecutará incluso si ya se ha almacenado en caché un token
             --session_contents = {id_token=true}
             -- Lista blanca del contenido de la sesión para habilitar. Esto se puede usar para reducir el tamaño de la sesión.
             -- Cuando no se establece, todo se incluirá en la sesión.
             -- Disponibles son:
             -- id_token, enc_id_token, user, access_token (incluye el token de refresco)

             -- Puedes especificar tiempos de espera para connect/send/read como un solo número (configurando todos los tiempos de espera) o como una tabla. Los valores están en milisegundos
             -- timeout = 1000
             -- timeout = { connect = 500, send = 1000, read = 1000 }

             --use_nonce = false
             -- Por defecto, la solicitud de autorización incluye el
             -- parámetro nonce. Puedes usar esta opción para deshabilitarlo
             -- lo cual puede ser necesario al hablar con un proveedor de OpenID
             -- Connect defectuoso que ignora el parámetro, ya que
             -- el id_token será rechazado de lo contrario.

             --authorization_state_expires_in = 300
             -- Edad máxima en segundos para el estado de autorización pendiente almacenado
             -- en la sesión. Los estados de autorización expirados se eliminan cuando
             -- se crea una nueva solicitud de autorización.

             --authorization_state_max_number = 10
             -- Número máximo de estados de autorización pendientes almacenados en la
             -- sesión. Cuando se excede, los estados de autorización más antiguos se
             -- eliminan cuando se crea una nueva solicitud de autorización.

             --revoke_tokens_on_logout = false
             -- Cuando revoke_tokens_on_logout está configurado como verdadero, un cierre de sesión notifica al servidor de autorización que los tokens de refresco y acceso obtenidos anteriormente ya no son necesarios. Esto requiere que revocation_endpoint sea descubrible.
             -- Si no se proporciona un punto final de revocación o si hay errores en la revocación, el usuario no será notificado y el proceso de cierre de sesión continuará normalmente.

             -- Opcional: usar un proxy saliente para los puntos finales del proveedor de OpenID Connect con la tabla proxy_opts:
             -- esto requiere lua-resty-http >= 0.12
             -- proxy_opts = {
             --    http_proxy  = "http://<proxy_host>:<proxy_port>/",
             --    https_proxy = "http://<proxy_host>:<proxy_port>/"
             -- }

             -- Hooks de Ciclo de Vida
             --
             -- lifecycle = {
             --    on_created = handle_created,
             --    on_authenticated = handle_authenticated,
             --    on_regenerated = handle_regenerated
             --    on_logout = handle_logout
             -- }
             --
             -- donde `handle_created`, `handle_authenticated`, `handle_regenerated` y `handle_logout` son funciones
             -- que aceptan el argumento `session`. `handle_created` acepta también un segundo argumento `params` que es una tabla
             -- que contiene los parámetros de consulta de la solicitud de autorización utilizada para redirigir al usuario al punto final del Proveedor de OpenID
             -- Connect.
             --
             --  -- El hook `on_created` se invoca *después* de que se ha creado una sesión en
             --     `openidc_authorize` inmediatamente antes de guardar la sesión
             --  -- El hook `on_authenticated` se invoca *después* de recibir la respuesta de autorización en
             --     `openidc_authorization_response` inmediatamente antes de guardar la sesión
             --     A partir de lua-resty-openidc 1.7.5, esto recibe el id_token decodificado como segundo y la respuesta del punto final de token como tercer argumento      
             --  -- `on_regenerated` se invoca inmediatamente después de que
             --     se ha obtenido un nuevo token de acceso a través de la
             --     renovación del token y se llama con la tabla de sesión regenerada
             --  -- El hook `on_logout` se invoca *antes* de que se destruya una sesión en
             --     `openidc_logout`
             --
             --  Cualquiera, todos o ninguno de los hooks pueden ser utilizados. Un `lifecycle` vacío no hace nada.
             --  Un hook que devuelve un valor verdadero causa que la acción del ciclo de vida de la que forman parte falle.

             -- Opcional: agregar un decorador para la solicitud HTTP que se
             -- aplica cuando lua-resty-openidc se comunica directamente con el Proveedor de OpenID Connect.
             -- Puede usarse para proporcionar encabezados HTTP adicionales
             -- o agregar otro comportamiento similar.
             -- http_request_decorator = function(req)
             --   local h = req.headers or {}
             --   h[EXTRA_HEADER] = 'mi encabezado adicional'
             --   req.headers = h
             --   return req
             -- end,

             -- use_pkce = false,
             -- cuando se establece en verdadero, se utilizará la "Prueba de Clave para el Intercambio de Código" como
             -- se define en RFC 7636. El método de desafío de código
             -- siempre será S256

          }

          -- Configurar lua-resty-session
          -- La lista completa de opciones de configuración está documentada en el repositorio de GitHub de lua-resty-session: https://github.com/bungle/lua-resty-session?tab=readme-ov-file#session-configuration
          local session_opts = {
               -- Al usar cookies para almacenar sesiones, establece un secreto compartido para la encriptación de cookies de sesión. Esto permite que las sesiones permanezcan válidas después de un reinicio de nginx.
               -- También habilita la gestión de sesiones "sin estado", por lo que múltiples instancias de nginx pueden manejar solicitudes sin necesidad de técnicas de balanceo de carga "pegajosas".
               -- secret = "xxxxxxxxxxxxxxxxxxx",
               -- Opcionalmente, establece el prefijo de la cookie para evitar sobrescribir accidentalmente la cookie de sesión
               -- cookie_prefix = "__Host-",
               -- Las cookies de sesión de inicio de sesión deben ser solo HTTP
               cookie_http_only = true,
               -- Las cookies de sesión de inicio de sesión deben estar marcadas como "Seguras"
               cookie_secure = true,
               -- Establece la política de cookies del mismo sitio
               cookie_same_site = "Lax",
               -- Establece esto en verdadero si deseas que las cookies de sesión de inicio de sesión persistan después de un reinicio del navegador
               remember = true
          }

          -- llama a authenticate para la autenticación de usuario de OpenID Connect
          local res, err = require("resty.openidc").authenticate(opts, nil, nil, session_opts)

          if err then
            ngx.status = 500
            ngx.say(err)
            ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
          end

          -- en este punto, res es una tabla Lua con 3 claves:
          --   id_token    : una tabla Lua con las afirmaciones del id_token (requerido)
          --   access_token: el token de acceso (opcional)
          --   user        : una tabla Lua con las afirmaciones devueltas desde el punto final de información del usuario (opcional)

          --if res.id_token.hd ~= "zmartzone.eu" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.user.email ~= "[email protected]" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          -- establece encabezados con información del usuario: esto sobrescribirá cualquier encabezado existente
          -- pero también los limpiará (!) en caso de que no se proporcione ningún valor en el token
          ngx.req.set_header("X-USER", res.id_token.sub)
      }

      proxy_pass http://localhost:80;
    }
  }
}

Acerca de redirect_uri

El llamado redirect_uri es una URI que forma parte del protocolo OpenID Connect. La URI de redirección está registrada con tu proveedor de OpenID Connect y es la URI a la que tu proveedor redirigirá a los usuarios después de un inicio de sesión exitoso. Esta URI es manejada por lua-resty-openidc donde obtiene tokens y realiza algunas verificaciones y solo después de eso el navegador es redirigido a donde tu usuario quería ir inicialmente.

No se espera que la redirect_uri sea manejada por el código de tu aplicación en absoluto. Debe ser una URI de la que lua-resty-openidc es responsable, por lo que debe estar en una location protegida por lua-resty-openidc.

Configuras la redirect_uri del lado de lua-resty-openidc a través del parámetro opts.redirect_uri (que por defecto es /redirect_uri). Si comienza con un /, entonces lua-resty-openidc le antepondrá el protocolo y el nombre de host actual al enviar la URI al proveedor de OpenID Connect (teniendo en cuenta los encabezados HTTP Forwarded y X-Forwarded-*). Pero también puedes especificar una URI absoluta que contenga el host y el protocolo tú mismo.

Antes de la versión 1.6.1, opts.redirect_uri_path era la forma de configurar la redirect_uri sin ninguna opción para tomar el control sobre las partes del protocolo y el host.

Siempre que lua-resty-openidc "ve" una ruta local navegada que coincide con la ruta de opts.redirect_uri (o opts.redirect_uri_path), interceptará la solicitud y la manejará por sí misma.

Esto funciona para la mayoría de los casos, pero a veces la redirect_uri visible externamente tiene una ruta diferente a la que es localmente visible para el servidor. Esto puede suceder si un proxy inverso frente a tu servidor reescribe URIs antes de reenviar las solicitudes. Por lo tanto, la versión 1.7.6 introdujo una nueva opción opts.local_redirect_uri_path. Si se establece, lua-resty-openidc interceptará las solicitudes a esta ruta en lugar de la ruta de opts.redirect_uri.

Verificar autenticación solamente

-- verificar sesión, pero no redirigir a autenticación si no ha iniciado sesión
local res, err = require("resty.openidc").authenticate(opts, nil, "pass")

Verificar autenticación solamente y denegar acceso no autenticado

-- verificar sesión, no redirigir a autenticación si no ha iniciado sesión pero devolver un error en su lugar
local res, err = require("resty.openidc").authenticate(opts, nil, "deny")

Sesiones y Bloqueo

La función authenticate devuelve el objeto de sesión actual como su cuarto argumento de retorno. Si has configurado lua-resty-session para usar un backend de almacenamiento del lado del servidor que utiliza bloqueo, la sesión puede estar bloqueada cuando se devuelve. En este caso, es posible que desees cerrarla explícitamente.

local res, err, target, session = require("resty.openidc").authenticate(opts)
session:close()

Caché

lua-resty-openidc puede utilizar cachés de memoria compartida para varias cosas. Si deseas que utilice las cachés, debes usar lua_shared_dict en tu archivo nginx.conf.

Actualmente se utilizan hasta cuatro cachés

  • la caché llamada discovery almacena los metadatos de Discovery de OpenID Connect de tu Proveedor de OpenID Connect. Los elementos de la caché expiran después de 24 horas a menos que sean anulados por opts.discovery_expires_in (un valor dado en segundos). Esta caché almacenará un elemento por URI de emisor y puedes buscar el documento de discovery tú mismo para obtener una estimación del tamaño requerido: generalmente unos pocos kB por Proveedor de OpenID Connect.
  • la caché llamada jwks almacena el material de clave de tu Proveedor de OpenID Connect si se proporciona a través del punto final JWKS. Los elementos de la caché expiran después de 24 horas a menos que sean anulados por opts.jwks_expires_in. Esta caché almacenará un elemento por URI de JWKS y puedes buscar los jwks tú mismo para obtener una estimación del tamaño requerido: generalmente unos pocos kB por Proveedor de OpenID Connect.
  • la caché llamada introspection almacena el resultado de la introspección del token de OAuth2. Los elementos de la caché expiran cuando el token correspondiente expira. Los tokens con expiración desconocida no se almacenan en caché en absoluto. Esta caché contendrá una entrada por token de acceso introspeccionado: generalmente esto será unos pocos kB por token.
  • la caché llamada jwt_verification almacena el resultado de la verificación de JWT. Los elementos de la caché expiran cuando el token correspondiente expira. Los tokens con expiración desconocida no se almacenan en caché durante dos minutos. Esta caché contendrá una entrada por JWT verificado: generalmente esto será unos pocos kB por token.

Caché de Resultados de Introspección y Verificación de JWT

Ten en cuenta que las cachés jwt_verification e introspection son compartidas entre todas las ubicaciones configuradas. Si estás utilizando ubicaciones con diferentes configuraciones de opts, la caché compartida puede permitir que un token que es válido solo para una ubicación sea aceptado por otra si se lee desde la caché. Para evitar confusiones en la caché, se recomienda establecer opts.cache_segment en cadenas únicas para cada conjunto de ubicaciones relacionadas.

Caché de respuestas de Introspección negativas

Por defecto, la caché introspection no almacenará respuestas negativas. Esto significa que un actor malicioso puede intentar agotar el punto final de introspección inundando el servicio con muchas llamadas con un token inapropiado. Para prevenir esta situación, opts.introspection_enable_negative_cache puede establecerse en true. Esto habilitará a la caché introspection para almacenar respuestas negativas durante el tiempo definido en el campo exp. Almacenar respuestas negativas de introspección descargará tráfico del punto final de introspección, pero también expondrá a NGINX a ataques de agotamiento de recursos, ya que almacenar respuestas negativas de introspección utilizará almacenamiento adicional en caché.

Revocar tokens

La función revoke_tokens(opts, session) revoca el token de refresco y el token de acceso actuales. A diferencia de un cierre de sesión completo, la cookie de sesión no se destruirá y no se llamará al punto final de cierre de sesión. La función devuelve true si ambos tokens fueron revocados con éxito. Esta función puede ser útil en escenarios donde deseas destruir/eliminar una sesión del lado del servidor.

Con revoke_token(opts, token_type_hint, token) también es posible revocar un token específico. token_type_hint puede ser generalmente refresh_token o access_token.

Configuración de Ejemplo para Validación de Tokens JWT de OAuth 2.0

Configuración de ejemplo nginx.conf para verificar Tokens de Acceso Bearer JWT contra un secreto/claves preconfigurado. Una vez verificado con éxito, el servidor NGINX puede funcionar como un proxy inverso hacia un servidor de origen interno.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  # caché para resultados de verificación de JWT
  lua_shared_dict jwt_verification 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {

            -- 1. ejemplo de un secreto compartido para la verificación de firma HS???
            --symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            -- en versiones hasta 1.6.1 la clave de esta opción habría sido secret
            -- en lugar de symmetric_key

            -- 2. otro ejemplo de un certificado público para la verificación de firma RS???
            public_key = [[-----BEGIN CERTIFICATE-----
MIIC0DCCAbigAwIBAgIGAVSbMZs1MA0GCSqGSIb3DQEBCwUAMCkxCzAJBgNVBAYTAlVTMQwwCgYD
VQQKEwNibGExDDAKBgNVBAMTA2JsYTAeFw0xNjA1MTAxNTAzMjBaFw0yNjA1MDgxNTAzMjBaMCkx
CzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNibGExDDAKBgNVBAMTA2JsYTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAIcLtHjX2GFxYv1033dvfohyCU6nsuR1qoDXfHTG3Mf/Yj4BfLHtMjJr
nR3sgHItH3B6qZPnfErfsN0LP4uZ10/74CrWVqT5dy6ecXMqYtz/KNJ8rG0vY8vltc417AU4fie8
gyeWv/Z6wHWUCf3NHRV8GfFgfuvywgUpHo8ujpUPFr+zrPr8butrzJPq1h3+r0f5P45tfWOdpjCT
gsTzK6urUG0k3WkwdDYapL3wRCAw597nYfgKzzXuh9N0ZL3Uj+eJ6BgCzUZDLXABpMBZfk6hmmzp
cAFV4nTf1AaAs/EOwVE0YgZBJiBrueMcteAIxKrKjEHgThU2Zs9gN9cSFicCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEAQLU1A58TrSwrEccCIy0wxiGdCwQbaNMohzirc41zRMCXleJXbtsn1vv85J6A
RmejeH5f/JbDqRRRArGMdLooGbqjWG/lwZT456Q6DXqF2plkBvh37kp/GjthGyR8ODJn5ekZwxuB
OcTuruRhqYOIJjiYZSgK/P0zUw1cjLwUJ9ig/O6ozYmof83974fygA/wK3SgFNEoFlTkTpOvZhVW
9kLfCVA/CRBfJNKnz5PWBBxd/3XSEuP/fcWqKGTy7zZso4MTB0NKgWO4duGTgMyZbM4onJPyA0CY
lAc5Csj0o5Q+oEhPUAVBIF07m4rd0OvAVPOCQ2NJhQSL1oWASbf+fg==
-----END CERTIFICATE-----]],
            -- en versiones hasta 1.6.1 la clave de esta opción habría sido secret
            -- en lugar de public_key

            -- 3. alternativamente se puede apuntar a un documento de Discovery que
            -- contenga la entrada "jwks_uri"; el punto final de jwks debe proporcionar ya sea una entrada "x5c"
            -- o ambas entradas "n" módulo y "e" exponente para la verificación de firma RSA
            -- discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- el algoritmo de firma que esperas que se haya utilizado;
             -- puede ser una sola cadena o una tabla.
             -- Debes establecer esto por razones de seguridad para
             -- evitar aceptar un token que afirme estar firmado por HMAC
             -- utilizando una clave pública RSA.
             --token_signing_alg_values_expected = { "RS256" }

             -- si deseas aceptar tokens no firmados (utilizando el
             -- algoritmo de firma "none") entonces establece esto en verdadero.
             --accept_none_alg = false

             -- si deseas rechazar tokens firmados utilizando un algoritmo
             -- no soportado por lua-resty-jwt establece esto en falso. Si
             -- lo dejas sin configurar, la firma del token no será
             -- verificada en absoluto.
             --accept_unsupported_alg = true

             -- el tiempo de expiración en segundos para la caché jwk, el valor predeterminado es 1 día.
             --jwk_expires_in = 24 * 60 * 60

             -- Puede ser necesario forzar la verificación para un token bearer e ignorar los resultados de
             -- verificación en caché existentes. Si es así, debes establecer la opción jwt_verification_cache_ignore en verdadero.
             -- jwt_verification_cache_ignore = true

             -- nombre opcional de un segmento de caché si necesitas cachés separadas
             -- para ubicaciones configuradas de manera diferente
             -- cache_segment = 'api'
          }

          -- llama a bearer_jwt_verify para la validación de JWT de OAuth 2.0
          local res, err = require("resty.openidc").bearer_jwt_verify(opts)

           if err or not res then
            ngx.status = 403
            ngx.say(err and err or "no se proporcionó access_token")
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- en este punto, res es una tabla Lua que representa el (validado) JSON
          -- carga útil en el token JWT; ahora típicamente no queremos permitir cualquier
          -- token que fue emitido por el Servidor de Autorización, sino que queremos aplicar
          -- algunas restricciones de acceso a través de client IDs o scopes

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';

       proxy_pass http://localhost:80;
    }
  }
}

Configuración de Ejemplo para PingFederate OAuth 2.0

Configuración de ejemplo nginx.conf para validar Tokens de Acceso Bearer contra un Servidor de Autorización OAuth 2.0 de PingFederate.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
  lua_ssl_verify_depth 5;

  # caché para resultados de validación
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             introspection_endpoint="https://localhost:9031/as/introspect.oauth2",
             client_id="rs_client",
             client_secret="2Federate",
             ssl_verify = "no",

             -- Por defecto es "exp" - Controla el TTL de la caché de introspección
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- nombre opcional de un segmento de caché si necesitas cachés separadas
             -- para ubicaciones configuradas de manera diferente
             -- cache_segment = 'api'
          }

          -- llama a introspect para la validación del Token de Acceso Bearer de OAuth 2.0
          local res, err = require("resty.openidc").introspect(opts)

          if err then
            ngx.status = 403
            ngx.say(err)
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- en este punto, res es una tabla Lua que representa el objeto JSON
          -- devuelto desde el punto final de introspección/validación

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';
    }
  }
}

Configuración de ejemplo nginx.conf para validar Tokens de Acceso Bearer pasados como cookie contra un Servidor de Autorización ORY/Hydra.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  lua_ssl_trusted_certificate /opt/local/etc/openssl/cert.pem;
  lua_ssl_verify_depth 5;

  # caché para resultados de validación
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             -- establece la URI del punto final de introspección
             introspection_endpoint="https://localhost:9031/oauth2/introspect",

             -- alternativamente, si tu Proveedor OAuth2 proporciona un documento de discovery que contiene la
             -- afirmación introspection_endpoint, puedes dejar la opción introspection_endpoint
             -- sin establecer y en su lugar usar
             -- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",

             client_id="admin",
             client_secret="demo-password",
             ssl_verify = "no",

             -- Define el intervalo en segundos después del cual un token de acceso en caché e introspeccionado necesita
             -- ser refrescado al introspeccionarlo (y validarlo) nuevamente contra el Servidor de Autorización.
             -- Cuando no se define, el valor es 0, lo que significa que solo expira después de la `exp` (o alternativa,
             -- ver introspection_expiry_claim) sugerida por el Servidor de Autorización
             -- introspection_interval = 60,

             -- Define la forma en que los tokens de acceso OAuth 2.0 Bearer pueden ser pasados a este Servidor de Recursos.
             -- "cookie" como un encabezado de cookie llamado "PA.global" o utilizando el nombre especificado después de ":"
             -- "header" "Authorization: bearer" encabezado
             -- Cuando no se define, se utiliza el encabezado predeterminado "Authorization: bearer"
             -- auth_accept_token_as = "cookie:PA",

             -- Si se usa el encabezado, el campo del encabezado es Authorization
             -- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"

             -- Método de autenticación para el punto final de introspección del Servidor de Autorización OAuth 2.0,
             -- Utilizado para autenticar al cliente en el punto final de introspección con un client_id/client_secret
             -- Por defecto es "client_secret_post"
             -- introspection_endpoint_auth_method = "client_secret_basic",

             -- Especifica los nombres de las cookies separadas por espacios para recoger del navegador y enviar junto en las llamadas de backchannel
             -- al OP y AS puntos finales.
             -- Cuando no se define, no se envían tales cookies.
             -- pass_cookies = "JSESSION"

             -- Por defecto es "exp" - Controla el TTL de la caché de introspección
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- Puede ser necesario forzar una llamada de introspección para un access_token e ignorar los resultados de
             -- introspección en caché existentes. Si es así, debes establecer la opción introspection_cache_ignore en verdadero.
             -- introspection_cache_ignore = true

             -- nombre opcional de un segmento de caché si necesitas cachés separadas
             -- para ubicaciones configuradas de manera diferente
             -- cache_segment = 'api'
          }

          -- llama a introspect para la validación del Token de Acceso Bearer de OAuth 2.0
          local res, err = require("resty.openidc").introspect(opts)

          if err then
            ngx.status = 403
            ngx.say(err)
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- en este punto, res es una tabla Lua que representa el objeto JSON
          -- devuelto desde el punto final de introspección/validación

          --if res.scope ~= "edit" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end

          --if res.client_id ~= "ro_client" then
          --  ngx.exit(ngx.HTTP_FORBIDDEN)
          --end
      ';
    }
  }
}

Registro

El registro puede ser personalizado, incluyendo el uso de un registrador personalizado y el remapeo de los niveles de registro predeterminados de OpenIDC, por ejemplo:

local openidc = require("resty.openidc")
openidc.set_logging(nil, { DEBUG = ngx.INFO })

Ejecución de Pruebas

Hemos creado una configuración dockerizada para la prueba con el fin de simplificar la instalación de dependencias.

Para ejecutar las pruebas, realiza

$ docker build -f tests/Dockerfile . -t lua-resty-openidc/test
$ docker run -it --rm lua-resty-openidc/test:latest

si deseas crear luacov cobertura mientras pruebas usa

$ docker run -it --rm -e coverage=t lua-resty-openidc/test:latest

como el segundo comando.

Soporte

Para preguntas generales, consulta las páginas de Wiki con Preguntas Frecuentes en:
https://github.com/zmartzone/lua-resty-openidc/wiki
Cualquier pregunta/problema debe dirigirse a las discusiones de GitHub o al rastreador de problemas.

Descargo de Responsabilidad

Este software es de código abierto por ZmartZone IAM pero no se soporta comercialmente como tal. Cualquier pregunta/problema debe dirigirse a las discusiones de GitHub o al rastreador de problemas. Consulta también el archivo DISCLAIMER en este directorio.

GitHub

Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-openidc.