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.8.0 lanzado el 13 de septiembre de 2024.


CI Status OpenID Certification

lua-resty-openidc

lua-resty-openidc es una biblioteca para NGINX que implementa la funcionalidad de Relying Party (RP) de OpenID Connect y/o el 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 de Cliente Básico (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 use un JSON Web Token como Token de Acceso, la verificación puede realizarse contra un secreto/clave preconfigurado.

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 ser utilizado como un proxy inverso que termina OAuth/OpenID Connect frente a un servidor de origen para que los servidores/servicios de origen puedan estar protegidos con los estándares relevantes sin implementar esos 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;

  # NB: si tienes "lua_code_cache off;", usa:
  # set $session_secret xxxxxxxxxxxxxxxxxxx;
  # ver: https://github.com/bungle/lua-resty-session#notes-about-turning-lua-code-cache-off

  server {
    listen 8080;

    location / {

      access_by_lua_block {

          local opts = {
             -- la URI de redirección completa debe ser 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 sobreescrito usando 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 especificabas
             -- 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 de 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 de 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 de 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 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",
             --   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"). El valor predeterminado es 60 segundos.
             --client_jwt_assertion_expires_in = 60,
             -- Al utilizar 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 se puede usar para hacer que lua-resty-openidc use el [OAuth 2.0 Form Post Response Mode](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 el Relying Party compartan el mismo dominio.
             --authorization_params = { hd="zmartzone.eu" },
             --scope = "openid email profile",
             -- Actualiza el id_token del usuario 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 de cerrar sesión debe incluir el id token como un hint (si está disponible). Esta opción solo se utiliza si redirect_after_logout_uri está configurado.
             --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
             -- Dónde solicita el RP que el OP redirija al usuario después de cerrar 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 true.

             --accept_unsupported_alg = true
             -- si deseas rechazar tokens firmados utilizando un algoritmo
             -- no soportado por lua-resty-jwt establece esto en false. Si
             -- lo dejas sin establecer o lo configuras en true, la firma del token no será
             -- verificada cuando se use 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 actualización disponible.
             -- si falla en renovar el token, el usuario será redirigido al punto final de autorización.
             --access_token_expires_in = 3600
             -- Duración de vida predeterminada 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 está establecido, 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 true, 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 a 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 actualización)

             -- 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.

             --revoke_tokens_on_logout = false
             -- Cuando revoke_tokens_on_logout está configurado como true, un cierre de sesión notifica al servidor de autorización que los tokens de actualización y acceso obtenidos anteriormente ya no son necesarios. Esto requiere que el 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 continúa 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` también acepta 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 habla directamente con el Proveedor de OpenID Connect.
             -- Puede ser utilizado 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 extra'
             --   req.headers = h
             --   return req
             -- end,

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

          }

          -- llama a authenticate para la autenticación de usuario OpenID Connect
          local res, err = require("resty.openidc").authenticate(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 los claims del id_token (requerido)
          --   access_token: el token de acceso (opcional)
          --   user        : una tabla Lua con los claims devueltos 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 es 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 luego 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 añadirá el protocolo y el nombre de host actual cuando envíe 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 controlar 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á solicitudes a esta ruta en lugar de la ruta de opts.redirect_uri.

Comprobar autenticación solamente

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

Comprobar autenticación solamente y denegar acceso no autenticado

-- comprobar 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 usar cachés de memoria compartida para varias cosas. Si deseas que use 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 caché expiran después de 24 horas a menos que sean sobreescritos 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 clave de tu Proveedor de OpenID Connect si se proporciona a través del punto final JWKS. Los elementos de caché expiran después de 24 horas a menos que sean sobreescritos por opts.jwks_expires_in. Esta caché almacenará un elemento por URI de JWKS y puedes buscar el 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 OAuth2. Los elementos de 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 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.

Revocar tokens

La función revoke_tokens(opts, session) revoca el token de actualización y el token de acceso actuales. A diferencia de un cierre de sesión completo, la cookie de sesión no será destruida y el punto final de cierre de sesión no será llamado. 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 Token JWT de OAuth 2.0

Configuración de ejemplo nginx.conf para verificar Tokens de Acceso Bearer JWT contra un secreto/clave 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 llamado documento de Discovery que
            -- contiene la entrada "jwks_uri"; el punto final 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 (usando el
             -- algoritmo de firma "none") entonces establece esto en true.
             --accept_none_alg = false

             -- si deseas rechazar tokens firmados utilizando un algoritmo
             -- no soportado por lua-resty-jwt establece esto en false. Si
             -- lo dejas sin establecer, 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í, necesitas establecer la opción jwt_verification_cache_ignore en true.
             -- 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 access_token provided")
            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 IDs de cliente 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 PingFederate OAuth 2.0.

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",

             -- Predeterminado a "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
             -- entrada 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 introspectarlo (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 del `exp` (o alternativa,
             -- ver introspection_expiry_claim) sugerido 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 utiliza el encabezado, el campo de 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
             -- Predeterminado a "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.
             -- Cuando no se define, no se envían tales cookies.
             -- pass_cookies = "JSESSION"

             -- Predeterminado a "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í, necesitas establecer la opción introspection_cache_ignore en true.
             -- 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 la 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 utiliza

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

como el segundo comando

Soporte

Para preguntas genéricas, 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 o al rastreador de problemas de Github.

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 o al rastreador de problemas de Github. 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.