Pular para conteúdo

openidc: Implementação de OpenID Connect Relying Party e OAuth 2.0 Resource Server em Lua para NGINX / nginx-module-lua

Instalação

Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Depois, você pode prosseguir com os seguintes passos.

CentOS/RHEL 7 ou 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 com o NGINX, certifique-se de que o nginx-module-lua está instalado.

Este documento descreve lua-resty-openidc v1.9.0 lançado em 01 de julho de 2026.


CI Status Certificação OpenID

lua-resty-openidc

lua-resty-openidc é uma biblioteca para NGINX que implementa a funcionalidade de Relying Party (RP) de OpenID Connect e/ou o Resource Server (RS) de OAuth 2.0.

Quando usado como um Relying Party OpenID Connect, ele autentica usuários contra um Provedor OpenID Connect usando OpenID Connect Discovery e o Perfil Básico do Cliente (ou seja, o fluxo de Código de Autorização). Quando usado como um Resource Server OAuth 2.0, ele pode validar Tokens de Acesso Bearer OAuth 2.0 contra um Servidor de Autorização ou, caso um JSON Web Token seja usado como Token de Acesso, a verificação pode ocorrer contra um segredo/chave pré-configurado.

Ele mantém sessões para usuários autenticados aproveitando lua-resty-session, oferecendo assim uma escolha configurável entre armazenar o estado da sessão em um cookie do navegador do lado do cliente ou usar um dos mecanismos de armazenamento do lado do servidor shared-memory|memcache|redis.

Ele suporta cache em todo o servidor de documentos de Discovery resolvidos e Tokens de Acesso validados.

Pode ser usado como um proxy reverso terminando OAuth/OpenID Connect em frente a um servidor de origem, de modo que o servidor/serviços de origem possam ser protegidos com os padrões relevantes sem implementar isso no próprio servidor.

Configuração de Exemplo para Login com Google+

Configuração de exemplo nginx.conf para autenticar usuários contra o Login do Google+, protegendo um caminho de proxy reverso.

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;

  # cache para documentos de metadados de discovery
  lua_shared_dict discovery 1m;
  # cache para JWKs
  lua_shared_dict jwks 1m;

  server {
    listen 8080;

    location / {

      access_by_lua_block {

          local opts = {
             -- o URI de redirecionamento completo deve ser protegido por este script
             -- se o URI começar com uma / o URI de redirecionamento completo se torna
             -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
             -- a menos que o esquema tenha sido substituído usando opts.redirect_uri_scheme ou um cabeçalho X-Forwarded-Proto na solicitação de entrada
             redirect_uri = "https://MY_HOST_NAME/redirect_uri",
             -- até a versão 1.6.1 você especificaria
             -- redirect_uri_path = "/redirect_uri",
             -- e não poderia definir o nome do host

             -- O endpoint de discovery do OP. Habilite para obter o URI de todos os endpoints (Token, introspecção, logout...)
             discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- O acesso ao endpoint Token do OP requer uma autenticação. Vários modos de autenticação são suportados:
             --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
             -- o Se token_endpoint_auth_method estiver definido como "client_secret_basic", "client_secret_post" ou "client_secret_jwt", a autenticação no endpoint Token é feita usando client_id e client_secret
             --   Para OPs não compatíveis com OAuth 2.0 RFC 6749 para Autenticação de Cliente (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1)
             --   client_id e client_secret DEVEM ser invariantes quando codificados em URL
             client_id = "<client_id>",
             client_secret = "<client_secret>",
             -- o Se token_endpoint_auth_method estiver definido como "private_key_jwt", a autenticação no endpoint Token é feita usando client_id, client_rsa_private_key e client_rsa_private_key_id para calcular um JWT assinado
             --   client_rsa_private_key é a chave privada RSA a ser usada para assinar o JWT gerado pelo lua-resty-openidc para autenticação ao OP
             --   client_rsa_private_key_id (opcional) é o id da chave a ser definido no cabeçalho do JWT para identificar qual chave pública o OP deve usar para verificar a assinatura do 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 assinatura para o JWT de afirmação do cliente. O padrão é RS256 para private_key_jwt e HS256 para client_secret_jwt.
             --   Quando token_endpoint_auth_signing_alg_values_supported é anunciado no discovery, o valor configurado deve ser suportado pelo OP.
             --client_jwt_assertion_alg = "PS256",
             --   Público para o JWT de afirmação do cliente. O padrão é a URL do endpoint sendo chamado.
             --   Defina isso quando o endpoint for alcançado através de uma URL interna, mas o OP espera sua URL externa do endpoint de token como público.
             --client_jwt_assertion_audience = "https://op.example.com/token",
             --   Duração de vida expressa em segundos do JWT assinado gerado pelo lua-resty-openidc para autenticação ao OP.
             --   (usado quando token_endpoint_auth_method está definido como "private_key_jwt" ou "client_secret_jwt"). O padrão é 60 segundos.
             --client_jwt_assertion_expires_in = 60,
             -- Ao usar https para qualquer endpoint OP, a imposição da verificação do certificado SSL pode ser exigida ("sim") ou não ("não").
             --ssl_verify = "não",
             -- A conexão keepalive com o OP pode ser habilitada ("sim") ou desabilitada ("não").
             --keepalive = "não",

             --response_mode=form_post pode ser usado para fazer lua-resty-openidc usar o [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). *Nota* para navegadores modernos, você precisará definir [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) como `None` com form_post, a menos que seu Provedor OpenID Connect e Relying Party compartilhem o mesmo domínio.
             --authorization_params = { hd="zmartzone.eu" },
             -- Quando use_par estiver definido como verdadeiro, lua-resty-openidc usará Solicitações de Autorização Pushed OAuth 2.0 (PAR, RFC 9126).
             -- Os parâmetros da solicitação de autorização são postados para o pushed_authorization_request_endpoint do discovery,
             -- e o navegador é redirecionado para o endpoint de autorização com o request_uri retornado.
             --use_par = false,
             -- Substitua o endpoint PAR do discovery quando necessário.
             --pushed_authorization_request_endpoint = "https://MY_OP/pushed_authorization_request",
             -- Substitua o método de autenticação do cliente para o endpoint PAR. O padrão é token_endpoint_auth_method.
             --pushed_authorization_request_endpoint_auth_method = "client_secret_basic",
             -- Quando use_dpop estiver definido como verdadeiro, lua-resty-openidc enviará OAuth 2.0 DPoP (RFC 9449)
             -- JWTs de prova para o endpoint token e usará tokens de acesso vinculados a DPoP para userinfo.
             -- A implementação inicial do DPoP suporta provas ES256, RS256 e PS256 e tenta chamadas de token/userinfo
             -- uma vez quando o OP retorna um desafio DPoP-Nonce.
             -- As solicitações de autorização incluem dpop_jkt para vincular o código de autorização à chave pública DPoP configurada.
             -- As respostas do token devem retornar token_type=DPoP.
             -- Se o OP fornecer DPoP-Nonce em uma resposta de token bem-sucedida, ele será reutilizado para solicitações de token subsequentes.
             --use_dpop = false,
             --dpop_signing_alg = "ES256",
             --dpop_private_key = "chave privada codificada em PEM correspondente ao dpop_signing_alg",
             --dpop_public_jwk = {
             --  kty = "EC",
             --  crv = "P-256",
             --  x = "...",
             --  y = "..."
             --},
             --scope = "openid email profile",
             -- Atualiza o id_token do usuário após 900 segundos sem exigir reautenticação
             --refresh_session_interval = 900,
             --iat_slack = 600,
             --redirect_uri_scheme = "https",
             --logout_path = "/logout",
             --redirect_after_logout_uri = "/",
             -- Para onde o usuário deve ser redirecionado após o logout do RP. Esta opção substitui qualquer end_session_endpoint que o OP possa ter fornecido na resposta de discovery.
             --redirect_after_logout_with_id_token_hint = true,
             -- Se o redirecionamento após logout deve incluir o id token como uma dica (se disponível). Esta opção é usada apenas se redirect_after_logout_uri estiver definido.
             --redirect_after_logout_with_client_id = true,
             -- Se o redirecionamento após logout deve incluir o client id quando nenhum id_token_hint é enviado (se disponível).
             --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
             -- Para onde o RP solicita que o OP redirecione o usuário após o logout. Se esta opção estiver definida como um URI relativo, será relativo ao endpoint de logout do OP, não ao do RP.

             --accept_none_alg = false
             -- se seu Provedor OpenID Connect não assina seus id tokens
             -- (usa o algoritmo de assinatura "none"), então defina isso como verdadeiro.

             --accept_unsupported_alg = true
             -- se você quiser rejeitar tokens assinados usando um algoritmo
             -- não suportado pelo lua-resty-jwt, defina isso como falso. Se
             -- você deixar sem definir ou definir como verdadeiro, a assinatura do token não será
             -- verificada quando um algoritmo não suportado for usado.

             --renew_access_token_on_expiry = true
             -- se este plugin deve tentar renovar silenciosamente o token de acesso uma vez que ele expire se um token de atualização estiver disponível.
             -- se falhar ao renovar o token, o usuário será redirecionado para o endpoint de autorização.
             --access_token_expires_in = 3600
             -- Tempo de vida padrão em segundos do access_token se nenhum atributo expires_in estiver presente na resposta do endpoint do token.

             --access_token_expires_leeway = 0
             --  Margem de expiração para renovação do access_token. Se isso estiver definido, a renovação ocorrerá access_token_expires_leeway segundos antes da expiração do token. Isso evita erros caso o access_token expire ao chegar ao OAuth Resource Server.

             --force_reauthorize = false
             -- Quando force_reauthorize estiver definido como verdadeiro, o fluxo de autorização será executado mesmo que um token  tenha sido armazenado em cache
             --session_contents = {id_token=true}
             -- Lista de permissões do conteúdo da sessão a ser habilitada. Isso pode ser usado para reduzir o tamanho da sessão.
             -- Quando não definido, tudo será incluído na sessão.
             -- Disponíveis são:
             -- id_token, enc_id_token, user, access_token (inclui token de atualização)

             -- Você pode especificar timeouts para connect/send/read como um único número (definindo todos os timeouts) ou como uma tabela. Os valores estão em milissegundos
             -- timeout = 1000
             -- timeout = { connect = 500, send = 1000, read = 1000 }

             --use_nonce = false
             -- Por padrão, a solicitação de autorização inclui o
             -- parâmetro nonce. Você pode usar esta opção para desativá-lo
             -- o que pode ser necessário ao falar com um provedor OpenID
             -- Connect quebrado que ignora o parâmetro, pois o
             -- id_token será rejeitado caso contrário.

             --authorization_state_expires_in = 300
             -- Idade máxima em segundos para o estado de autorização pendente armazenado
             -- na sessão. Estados de autorização expirados são removidos quando
             -- uma nova solicitação de autorização é criada.

             --authorization_state_max_number = 10
             -- Número máximo de estados de autorização pendentes armazenados na
             -- sessão. Quando excedido, os estados de autorização mais antigos são
             -- removidos quando uma nova solicitação de autorização é criada.

             --revoke_tokens_on_logout = false
             -- Quando revoke_tokens_on_logout estiver definido como verdadeiro, um logout notifica o servidor de autorização que os tokens de atualização e acesso obtidos anteriormente não são mais necessários. Isso requer que o revocation_endpoint seja descobrível.
             -- Se não houver um endpoint de revogação fornecido ou se houver erros na revogação, o usuário não será notificado e o processo de logout continuará normalmente.

             -- Opcional: use um proxy de saída para os endpoints do Provedor OpenID Connect com a tabela proxy_opts:
             -- isso requer 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
             -- }
             --
             -- onde `handle_created`, `handle_authenticated`, `handle_regenerated` e `handle_logout` são chamáveis
             -- aceitando o argumento `session`. `handle_created` aceita também o segundo argumento `params` que é uma tabela
             -- contendo os parâmetros de consulta da solicitação de autorização usada para redirecionar o usuário para o endpoint do Provedor OpenID
             -- Connect.
             --
             --  -- O hook `on_created` é invocado *após* uma sessão ter sido criada em
             --     `openidc_authorize` imediatamente antes de salvar a sessão
             --  -- O hook `on_authenticated` é invocado *após* receber a resposta de autorização em
             --     `openidc_authorization_response` imediatamente antes de salvar a sessão
             --     A partir do lua-resty-openidc 1.7.5, isso recebe o id_token decodificado como segundo e a resposta do endpoint do token como terceiro argumento      
             --  -- `on_regenerated` é invocado imediatamente após o
                     um novo token de acesso ter sido obtido via refresh de token
                     e é chamado com a tabela de sessão regenerada
             --  -- O hook `on_logout` é invocado *antes* que uma sessão seja destruída em
             --     `openidc_logout`
             --
             --  Qualquer, todos ou nenhum dos hooks pode ser usado. Um `lifecycle` vazio não faz nada.
             --  Um hook que retorna um valor verdadeiro faz com que a ação do ciclo de vida da qual fazem parte falhe.

             -- Opcional: adicione um decorador para a solicitação HTTP que é
             -- aplicado quando lua-resty-openidc fala diretamente com o Provedor OpenID Connect.
             -- Pode ser usado para fornecer cabeçalhos HTTP extras
             -- ou adicionar outro comportamento semelhante.
             -- http_request_decorator = function(req)
             --   local h = req.headers or {}
             --   h[EXTRA_HEADER] = 'meu cabeçalho extra'
             --   req.headers = h
             --   return req
             -- end,

             -- use_pkce = false,
             -- quando definido como verdadeiro, a "Proof Key for Code Exchange" conforme
             -- definido na RFC 7636 será usada. O método de desafio de código
             -- será sempre S256

          }

          -- Configure lua-resty-session
          -- A lista completa de opções de configuração está documentada no repositório do lua-resty-session: https://github.com/bungle/lua-resty-session?tab=readme-ov-file#session-configuration
          local session_opts = {
               -- Ao usar cookies para armazenar sessões, defina um segredo compartilhado para a criptografia do cookie da sessão. Isso permite que as sessões permaneçam válidas após uma reinicialização do nginx.
               -- Também habilita o gerenciamento de sessão "sem estado", para que várias instâncias do nginx possam lidar com solicitações sem a necessidade de técnicas de balanceamento de carga "sticky".
               -- secret = "xxxxxxxxxxxxxxxxxxx",
               -- Opcionalmente, defina o prefixo do cookie para evitar a sobrescrição acidental do cookie da sessão
               -- cookie_prefix = "__Host-",
               -- Cookies de sessão de login devem ser HTTP Only
               cookie_http_only = true,
               -- Cookies de sessão de login devem ser marcados como "Secure"
               cookie_secure = true,
               -- Defina a política de cookie same site
               cookie_same_site = "Lax",
               -- Defina isso como verdadeiro se você quiser que os cookies de sessão de login persistam após uma reinicialização do navegador
               remember = true
          }

          -- chame authenticate para autenticação de usuário 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

          -- neste ponto, res é uma tabela Lua com 3 chaves:
          --   id_token    : uma tabela Lua com as declarações do id_token (obrigatório)
          --   access_token: o token de acesso (opcional)
          --   user        : uma tabela Lua com as declarações retornadas do endpoint de informações do usuário (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

          -- defina cabeçalhos com informações do usuário: isso irá sobrescrever quaisquer cabeçalhos existentes
          -- mas também limpar(!) eles caso nenhum valor seja fornecido no token
          ngx.req.set_header("X-USER", res.id_token.sub)
      }

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

Sobre redirect_uri

O chamado redirect_uri é um URI que faz parte do protocolo OpenID Connect. O URI de redirecionamento é registrado com seu provedor OpenID Connect e é o URI para o qual seu provedor redirecionará os usuários após um login bem-sucedido. Este URI é então tratado pelo lua-resty-openidc, onde obtém tokens e realiza algumas verificações e somente após isso o navegador é redirecionado para onde seu usuário queria ir inicialmente.

O redirect_uri não deve ser tratado pelo seu código de aplicação. Deve ser um URI do qual o lua-resty-openidc é responsável, portanto, deve estar em uma location protegida pelo lua-resty-openidc.

Você configura o redirect_uri do lado do lua-resty-openidc através do parâmetro opts.redirect_uri (que por padrão é /redirect_uri). Se começar com uma /, então o lua-resty-openidc irá adicionar o protocolo e o nome do host atual quando enviar o URI para o provedor OpenID Connect (levando em conta os cabeçalhos HTTP Forwarded e X-Forwarded-*). Mas você também pode especificar um URI absoluto contendo host e protocolo por conta própria.

Antes da versão 1.6.1, opts.redirect_uri_path era a maneira de configurar o redirect_uri sem qualquer opção para controlar as partes do protocolo e do host.

Sempre que o lua-resty-openidc "ver" um caminho local navegado que corresponda ao caminho de opts.redirect_uri (ou opts.redirect_uri_path), ele interceptará a solicitação e a tratará.

Isso funciona para a maioria dos casos, mas às vezes o redirect_uri visível externamente tem um caminho diferente do que o visível localmente para o servidor. Isso pode acontecer se um proxy reverso na frente do seu servidor reescrever URIs antes de encaminhar as solicitações. Portanto, a versão 1.7.6 introduziu uma nova opção opts.local_redirect_uri_path. Se estiver definida, o lua-resty-openidc interceptará solicitações para esse caminho em vez do caminho de opts.redirect_uri.

Verificação de autenticação apenas

-- verifica a sessão, mas não redireciona para autenticação se não estiver logado
local res, err = require("resty.openidc").authenticate(opts, nil, "pass")

Verificação de autenticação apenas e negar acesso não autenticado

-- verifica a sessão, não redireciona para autenticação se não estiver logado, mas retorna um erro em vez disso
local res, err = require("resty.openidc").authenticate(opts, nil, "deny")

Sessões e Bloqueio

A função authenticate retorna o objeto de sessão atual como seu quarto argumento de retorno. Se você configurou o lua-resty-session para usar um backend de armazenamento do lado do servidor que usa bloqueio, a sessão pode ainda estar bloqueada quando for retornada. Nesse caso, você pode querer fechá-la explicitamente.

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

Cache

lua-resty-openidc pode usar caches de memória compartilhada para várias coisas. Se você quiser que ele use os caches, deve usar lua_shared_dict em seu arquivo nginx.conf.

Atualmente, até quatro caches são usados:

  • o cache nomeado discovery armazena os metadados de Discovery do OpenID Connect do seu Provedor OpenID Connect. Os itens do cache expiram após 24 horas, a menos que sejam substituídos por opts.discovery_expires_in (um valor dado em segundos). Este cache armazenará um item por URI do emissor e você pode consultar o documento de discovery você mesmo para obter uma estimativa do tamanho necessário - geralmente alguns kB por Provedor OpenID Connect.
  • o cache nomeado jwks armazena o material da chave do seu Provedor OpenID Connect, se fornecido via o endpoint JWKS. Os itens do cache expiram após 24 horas, a menos que sejam substituídos por opts.jwks_expires_in. Este cache armazenará um item por URI JWKS e você pode consultar o jwks você mesmo para obter uma estimativa do tamanho necessário - geralmente alguns kB por Provedor OpenID Connect.
  • o cache nomeado introspection armazena o resultado da introspecção do token OAuth2. Os itens do cache expiram quando o token correspondente expira. Tokens com expiração desconhecida não são armazenados em cache. Este cache conterá uma entrada por token de acesso introspectado - geralmente isso será alguns kB por token.
  • o cache nomeado jwt_verification armazena o resultado da verificação do JWT. Os itens do cache expiram quando o token correspondente expira. Tokens com expiração desconhecida não são armazenados em cache por dois minutos. Este cache conterá uma entrada por JWT verificado - geralmente isso será alguns kB por token.

Cache de Resultados de Introspecção e Verificação de JWT

Observe que os caches jwt_verification e introspection são compartilhados entre todas as localizações configuradas. Se você estiver usando localizações com configurações opts diferentes, o cache compartilhado pode permitir que um token que é válido apenas para uma localização seja aceito por outra se for lido do cache. Para evitar confusão de cache, é recomendável definir opts.cache_segment como strings exclusivas para cada conjunto de localizações relacionadas.

Cache de respostas negativas de Introspecção

Por padrão, o cache introspection não armazenará respostas negativas. Isso significa que um ator mal-intencionado pode potencialmente tentar esgotar o endpoint de introspecção inundando o serviço com muitas chamadas com um token inadequado. Para prevenir essa situação, opts.introspection_enable_negative_cache pode ser definido como true. Isso permitirá que o cache introspection armazene respostas negativas por um tempo definido no campo exp. O cache de respostas negativas de introspecção aliviará o tráfego do endpoint de introspecção, mas também exporá o NGINX a ataques de exaustão de recursos, pois armazenar respostas negativas de introspecção usará armazenamento de cache extra.

Revogar tokens

A função revoke_tokens(opts, session) revoga o token de atualização e o token de acesso atuais. Em contraste com um logout completo, o cookie da sessão não será destruído e o endpoint de finalização da sessão não será chamado. A função retorna true se ambos os tokens foram revogados com sucesso. Esta função pode ser útil em cenários onde você deseja destruir/remover uma sessão do lado do servidor.

Com revoke_token(opts, token_type_hint, token) também é possível revogar um token específico. token_type_hint pode geralmente ser refresh_token ou access_token.

Configuração de Exemplo para Validação de Token JWT OAuth 2.0

Configuração de exemplo nginx.conf para verificar Tokens de Acesso JWT Bearer contra um segredo/chave pré-configurado. Uma vez verificado com sucesso, o servidor NGINX pode funcionar como um proxy reverso para um servidor de origem interno.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  # cache para resultados de verificação de JWT
  lua_shared_dict jwt_verification 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {

            -- 1. exemplo de um segredo compartilhado para verificação de assinatura HS???
            --symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            -- em versões até 1.6.1, a chave desta opção seria secret
            -- em vez de symmetric_key

            -- 2. outro exemplo de um certificado público para verificação de assinatura 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-----]],
            -- em versões até 1.6.1, a chave desta opção seria secret
            -- em vez de public_key

            -- 3. alternativamente, pode-se apontar para um chamado documento de Discovery que
            -- contém a entrada "jwks_uri"; o endpoint jwks deve fornecer ou uma entrada "x5c"
            -- ou tanto a entrada "n" do módulo quanto a entrada "e" do expoente para verificação de assinatura RSA
            -- discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- o algoritmo de assinatura que você espera que tenha sido usado;
             -- pode ser uma única string ou uma tabela.
             -- Você deve definir isso por razões de segurança para
             -- evitar aceitar um token que afirma ser assinado por HMAC
             -- usando uma chave pública RSA.
             --token_signing_alg_values_expected = { "RS256" }

             -- se você quiser aceitar tokens não assinados (usando o
             -- algoritmo de assinatura "none"), então defina isso como verdadeiro.
             --accept_none_alg = false

             -- se você quiser rejeitar tokens assinados usando um algoritmo
             -- não suportado pelo lua-resty-jwt, defina isso como falso. Se
             -- você deixar sem definir, a assinatura do token não será
             -- verificada de forma alguma.
             --accept_unsupported_alg = true

             -- o tempo de expiração em segundos para o cache jwk, o padrão é 1 dia.
             --jwk_expires_in = 24 * 60 * 60

             -- Pode ser necessário forçar a verificação de um token bearer e ignorar os resultados de verificação em cache existentes.
             -- Se sim, você precisa definir a opção jwt_verification_cache_ignore como verdadeira.
             -- jwt_verification_cache_ignore = true

             -- nome opcional de um segmento de cache se você precisar de caches separados
             -- para localizações configuradas de forma diferente
             -- cache_segment = 'api'
          }

          -- chame bearer_jwt_verify para validação de JWT 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 "nenhum access_token fornecido")
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- neste ponto, res é uma tabela Lua que representa o JSON
          -- payload no token JWT; agora normalmente não queremos permitir apenas qualquer
          -- token que foi emitido pelo Servidor de Autorização, mas queremos aplicar
          -- algumas restrições de acesso via client IDs ou 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;
    }
  }
}

Configuração de Exemplo para PingFederate OAuth 2.0

Configuração de exemplo nginx.conf para validar Tokens de Acesso Bearer contra um Servidor de Autorização 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;

  # cache para resultados de validação
  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",

             -- O padrão é "exp" - Controla o TTL do cache de introspecção
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- nome opcional de um segmento de cache se você precisar de caches separados
             -- para localizações configuradas de forma diferente
             -- cache_segment = 'api'
          }

          -- chame introspect para validação do Token de Acesso Bearer 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

          -- neste ponto, res é uma tabela Lua que representa o JSON
          -- objeto retornado do endpoint de introspecção/validação

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

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

Configuração de exemplo nginx.conf para validar Tokens de Acesso Bearer passados como cookie contra um Servidor de Autorização 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;

  # cache para resultados de validação
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             -- define o URI do endpoint de introspecção
             introspection_endpoint="https://localhost:9031/oauth2/introspect",

             -- alternativamente, se seu Provedor OAuth2 fornecer um documento de discovery que contém a
             -- reivindicação introspection_endpoint, você pode deixar a opção introspection_endpoint
             -- não definida e em vez disso usar
             -- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",

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

             -- Define o intervalo em segundos após o qual um token de acesso introspectado e em cache precisa
             -- ser atualizado ao introspectá-lo (e validá-lo) novamente contra o Servidor de Autorização.
             -- Quando não definido, o valor é 0, o que significa que ele  expira após o `exp` (ou alternativa,
             -- veja introspection_expiry_claim) dica conforme retornado pelo Servidor de Autorização
             -- introspection_interval = 60,

             -- Define a forma como os tokens de acesso bearer OAuth 2.0 podem ser passados para este Resource Server.
             -- "cookie" como um cabeçalho de cookie chamado "PA.global" ou usando o nome especificado após ":"
             -- "header" cabeçalho "Authorization: bearer"
             -- Quando não definido, o cabeçalho padrão "Authorization: bearer" é usado
             -- auth_accept_token_as = "cookie:PA",

             -- Se o cabeçalho for usado, o campo do cabeçalho é Authorization
             -- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"

             -- Método de autenticação para o endpoint de introspecção do Servidor de Autorização OAuth 2.0,
             -- Usado para autenticar o cliente no endpoint de introspecção com um client_id/client_secret
             -- O padrão é "client_secret_post"
             -- introspection_endpoint_auth_method = "client_secret_basic",

             -- Especifique os nomes dos cookies separados por espaço para pegar do navegador e enviar junto em chamadas de backchannel
             -- para os endpoints OP e AS.
             -- Quando não definido, nenhum desses cookies é enviado.
             -- pass_cookies = "JSESSION"

             -- O padrão é "exp" - Controla o TTL do cache de introspecção
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- Pode ser necessário forçar uma chamada de introspecção para um access_token e ignorar os resultados de introspecção em cache existentes. Se sim, você precisa definir a opção introspection_cache_ignore como verdadeira.
             -- introspection_cache_ignore = true

             -- nome opcional de um segmento de cache se você precisar de caches separados
             -- para localizações configuradas de forma diferente
             -- cache_segment = 'api'
          }

          -- chame introspect para validação do Token de Acesso Bearer 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

          -- neste ponto, res é uma tabela Lua que representa o JSON
          -- objeto retornado do endpoint de introspecção/validação

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

O registro pode ser personalizado, incluindo o uso de um logger personalizado e remapeamento dos níveis de log padrão do OpenIDC, por exemplo:

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

Executando Testes

Criamos uma configuração dockerizada para o teste a fim de simplificar a instalação de dependências.

Para executar os testes, execute

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

se você quiser criar luacov cobertura enquanto testa, use

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

como o segundo comando

Suporte

Para perguntas genéricas, consulte as páginas do Wiki com Perguntas Frequentes em:
https://github.com/zmartzone/lua-resty-openidc/wiki
Qualquer pergunta/problema deve ser direcionado para as Discussões ou rastreador de Problemas do Github.

Isenção de Responsabilidade

Este software é de código aberto pela ZmartZone IAM, mas não é suportado comercialmente como tal. Qualquer pergunta/problema deve ser direcionado para as Discussões ou rastreador de Problemas do Github. Veja também o arquivo DISCLAIMER neste diretório.

GitHub

Você pode encontrar dicas de configuração adicionais e documentação para este módulo no repositório GitHub para nginx-module-openidc.