Aller au contenu

openidc: Implémentation de OpenID Connect Relying Party et OAuth 2.0 Resource Server en Lua pour NGINX / nginx-module-lua

Installation

Si vous n'avez pas encore configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.

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

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.

Ce document décrit lua-resty-openidc v1.9.0 publié le 01 juillet 2026.


CI Status OpenID Certification

lua-resty-openidc

lua-resty-openidc est une bibliothèque pour NGINX implémentant la fonctionnalité Relying Party (RP) de OpenID Connect et/ou la fonctionnalité Resource Server (RS) de OAuth 2.0.

Lorsqu'il est utilisé comme Relying Party OpenID Connect, il authentifie les utilisateurs contre un fournisseur OpenID Connect en utilisant OpenID Connect Discovery et le profil client de base (c'est-à-dire le flux de code d'autorisation). Lorsqu'il est utilisé comme Resource Server OAuth 2.0, il peut valider les jetons d'accès Bearer OAuth 2.0 contre un serveur d'autorisation ou, dans le cas où un JSON Web Token est utilisé comme jeton d'accès, la vérification peut se faire contre un secret/clé préconfiguré.

Il maintient des sessions pour les utilisateurs authentifiés en s'appuyant sur lua-resty-session, offrant ainsi un choix configurable entre le stockage de l'état de session dans un cookie de navigateur côté client ou l'utilisation des mécanismes de stockage côté serveur shared-memory|memcache|redis.

Il prend en charge la mise en cache à l'échelle du serveur des documents Discovery résolus et des jetons d'accès validés.

Il peut être utilisé comme un proxy inverse terminant OAuth/OpenID Connect devant un serveur d'origine afin que le serveur/origine/services puissent être protégés par les normes pertinentes sans les implémenter sur le serveur lui-même.

Configuration d'exemple pour la connexion Google+

Configuration d'exemple nginx.conf pour authentifier les utilisateurs contre la connexion Google+, protégeant un chemin proxy inverse.

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 pour les documents de métadonnées de découverte
  lua_shared_dict discovery 1m;
  # cache pour les JWKs
  lua_shared_dict jwks 1m;

  server {
    listen 8080;

    location / {

      access_by_lua_block {

          local opts = {
             -- l'URI de redirection complète doit être protégée par ce script
             -- si l'URI commence par un / l'URI de redirection complète devient
             -- ngx.var.scheme.."://"..ngx.var.http_host..opts.redirect_uri
             -- sauf si le schéma a été remplacé en utilisant opts.redirect_uri_scheme ou un en-tête X-Forwarded-Proto dans la requête entrante
             redirect_uri = "https://MY_HOST_NAME/redirect_uri",
             -- jusqu'à la version 1.6.1, vous deviez spécifier
             -- redirect_uri_path = "/redirect_uri",
             -- et ne pouviez pas définir le nom d'hôte

             -- Le point de terminaison de découverte de l'OP. Activez pour obtenir l'URI de tous les points de terminaison (Jeton, introspection, déconnexion...)
             discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- L'accès au point de terminaison de jeton de l'OP nécessite une authentification. Plusieurs modes d'authentification sont pris en charge :
             --token_endpoint_auth_method = ["client_secret_basic"|"client_secret_post"|"private_key_jwt"|"client_secret_jwt"],
             -- o Si token_endpoint_auth_method est défini sur "client_secret_basic", "client_secret_post" ou "client_secret_jwt", l'authentification au point de terminaison de jeton utilise client_id et client_secret
             --   Pour les OP non conformes à OAuth 2.0 RFC 6749 pour l'authentification des clients (cf. https://tools.ietf.org/html/rfc6749#section-2.3.1)
             --   client_id et client_secret DOIVENT être invariants lors de l'encodage d'URL
             client_id = "<client_id>",
             client_secret = "<client_secret>",
             -- o Si token_endpoint_auth_method est défini sur "private_key_jwt", l'authentification au point de terminaison de jeton utilise client_id, client_rsa_private_key et client_rsa_private_key_id pour calculer un JWT signé
             --   client_rsa_private_key est la clé privée RSA à utiliser pour signer le JWT généré par lua-resty-openidc pour l'authentification à l'OP
             --   client_rsa_private_key_id (optionnel) est l'identifiant de clé à définir dans l'en-tête JWT pour identifier quelle clé publique l'OP doit utiliser pour vérifier la signature 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",
             --   Algorithme de signature pour le JWT d'assertion du client. Par défaut, RS256 pour private_key_jwt et HS256 pour client_secret_jwt.
             --   Lorsque token_endpoint_auth_signing_alg_values_supported est annoncé dans la découverte, la valeur configurée doit être prise en charge par l'OP.
             --client_jwt_assertion_alg = "PS256",
             --   Audience pour le JWT d'assertion du client. Par défaut, l'URL du point de terminaison appelé.
             --   Définissez ceci lorsque le point de terminaison est atteint via une URL interne mais que l'OP attend son URL de point de terminaison de jeton externe comme audience.
             --client_jwt_assertion_audience = "https://op.example.com/token",
             --   Durée de vie exprimée en secondes du JWT signé généré par lua-resty-openidc pour l'authentification à l'OP.
             --   (utilisé lorsque token_endpoint_auth_method est défini sur "private_key_jwt" ou "client_secret_jwt" authentification). Par défaut, c'est 60 secondes.
             --client_jwt_assertion_expires_in = 60,
             -- Lorsque vous utilisez https pour n'importe quel point de terminaison OP, l'application de la vérification du certificat SSL peut être exigée ("oui") ou non ("non").
             --ssl_verify = "no",
             -- La connexion keepalive avec l'OP peut être activée ("oui") ou désactivée ("non").
             --keepalive = "no",

             --response_mode=form_post peut être utilisé pour faire en sorte que lua-resty-openidc utilise le [OAuth 2.0 Form Post Response Mode](https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html). *Remarque* pour les navigateurs modernes, vous devrez définir [`$session_cookie_samesite`](https://github.com/bungle/lua-resty-session#string-sessioncookiesamesite) sur `None` avec form_post à moins que votre fournisseur OpenID Connect et Relying Party ne partagent le même domaine.
             --authorization_params = { hd="zmartzone.eu" },
             -- Lorsque use_par est défini sur true, lua-resty-openidc utilisera les demandes d'autorisation poussées OAuth 2.0 (PAR, RFC 9126).
             -- Les paramètres de demande d'autorisation sont postés au pushed_authorization_request_endpoint de la découverte,
             -- et le navigateur est redirigé vers le point de terminaison d'autorisation avec le request_uri retourné.
             --use_par = false,
             -- Remplacez le point de terminaison PAR de la découverte si nécessaire.
             --pushed_authorization_request_endpoint = "https://MY_OP/pushed_authorization_request",
             -- Remplacez la méthode d'authentification du client pour le point de terminaison PAR. Par défaut, token_endpoint_auth_method.
             --pushed_authorization_request_endpoint_auth_method = "client_secret_basic",
             -- Lorsque use_dpop est défini sur true, lua-resty-openidc enverra des JWT de preuve DPoP (RFC 9449)
             -- au point de terminaison de jeton et utilisera des jetons d'accès liés à DPoP pour les informations utilisateur.
             -- La mise en œuvre initiale de DPoP prend en charge les preuves ES256, RS256 et PS256 et réessaie les appels de jeton/informations utilisateur
             -- une fois lorsque l'OP renvoie un défi DPoP-Nonce.
             -- Les demandes d'autorisation incluent dpop_jkt pour lier le code d'autorisation à la clé publique DPoP configurée.
             -- Les réponses de jeton doivent renvoyer token_type=DPoP.
             -- Si l'OP fournit DPoP-Nonce lors d'une réponse de jeton réussie, il est réutilisé pour les demandes de jeton suivantes.
             --use_dpop = false,
             --dpop_signing_alg = "ES256",
             --dpop_private_key = "clé privée encodée en PEM correspondant à dpop_signing_alg",
             --dpop_public_jwk = {
             --  kty = "EC",
             --  crv = "P-256",
             --  x = "...",
             --  y = "..."
             --},
             --scope = "openid email profile",
             -- Rafraîchir l'id_token de l'utilisateur après 900 secondes sans nécessiter de ré-authentification
             --refresh_session_interval = 900,
             --iat_slack = 600,
             --redirect_uri_scheme = "https",
             --logout_path = "/logout",
             --redirect_after_logout_uri = "/",
             --  l'utilisateur doit-il être redirigé après la déconnexion du RP. Cette option remplace tout end_session_endpoint que l'OP pourrait avoir fourni dans la réponse de découverte.
             --redirect_after_logout_with_id_token_hint = true,
             -- Si la redirection après la déconnexion doit inclure le jeton id comme indice (si disponible). Cette option n'est utilisée que si redirect_after_logout_uri est défini.
             --redirect_after_logout_with_client_id = true,
             -- Si la redirection après la déconnexion doit inclure l'identifiant du client lorsque aucun id_token_hint n'est envoyé (si disponible).
             --post_logout_redirect_uri = "https://www.zmartzone.eu/logoutSuccessful",
             --  le RP demande que l'OP redirige l'utilisateur après la déconnexion. Si cette option est définie sur une URI relative, elle sera relative au point de terminaison de déconnexion de l'OP, pas à celui du RP.

             --accept_none_alg = false
             -- si votre fournisseur OpenID Connect ne signe pas ses jetons id
             -- (utilise l'algorithme de signature "none"), alors définissez ceci sur true.

             --accept_unsupported_alg = true
             -- si vous souhaitez rejeter les jetons signés à l'aide d'un algorithme
             -- non pris en charge par lua-resty-jwt, définissez ceci sur false. Si
             -- vous le laissez non défini ou le définissez sur true, la signature du jeton ne sera pas
             -- vérifiée lorsqu'un algorithme non pris en charge est utilisé.

             --renew_access_token_on_expiry = true
             -- si ce plugin doit essayer de renouveler silencieusement le jeton d'accès une fois qu'il est expiré si un jeton de rafraîchissement est disponible.
             -- s'il échoue à renouveler le jeton, l'utilisateur sera redirigé vers le point de terminaison d'autorisation.
             --access_token_expires_in = 3600
             -- Durée de vie par défaut en secondes du access_token si aucun attribut expires_in n'est présent dans la réponse du point de terminaison du jeton.

             --access_token_expires_leeway = 0
             --  Marge d'expiration pour le renouvellement du access_token. Si cela est défini, le renouvellement se produira access_token_expires_leeway secondes avant l'expiration du jeton. Cela évite les erreurs au cas  le access_token expire juste à l'arrivée au serveur de ressources OAuth.

             --force_reauthorize = false
             -- Lorsque force_reauthorize est défini sur true, le flux d'autorisation sera exécuté même si un jeton a déjà été mis en cache
             --session_contents = {id_token=true}
             -- Liste blanche du contenu de session à activer. Cela peut être utilisé pour réduire la taille de la session.
             -- Lorsque non défini, tout sera inclus dans la session.
             -- Disponibles :
             -- id_token, enc_id_token, user, access_token (inclut le jeton de rafraîchissement)

             -- Vous pouvez spécifier des délais d'attente pour connect/send/read comme un seul nombre (définissant tous les délais d'attente) ou comme une table. Les valeurs sont en millisecondes
             -- timeout = 1000
             -- timeout = { connect = 500, send = 1000, read = 1000 }

             --use_nonce = false
             -- Par défaut, la demande d'autorisation inclut le
             -- paramètre nonce. Vous pouvez utiliser cette option pour le désactiver
             -- ce qui peut être nécessaire lorsque vous parlez à un fournisseur OpenID
             -- Connect défectueux qui ignore le paramètre car le
             -- id_token sera rejeté sinon.

             --authorization_state_expires_in = 300
             -- Âge maximum en secondes pour l'état d'autorisation en attente stocké
             -- dans la session. Les états d'autorisation expirés sont supprimés lorsque
             -- une nouvelle demande d'autorisation est créée.

             --authorization_state_max_number = 10
             -- Nombre maximum d'états d'autorisation en attente stockés dans la
             -- session. Lorsqu'il est dépassé, les états d'autorisation les plus anciens sont
             -- supprimés lorsque une nouvelle demande d'autorisation est créée.

             --revoke_tokens_on_logout = false
             -- Lorsque revoke_tokens_on_logout est défini sur true, une déconnexion notifie le serveur d'autorisation que les jetons de rafraîchissement et d'accès précédemment obtenus ne sont plus nécessaires. Cela nécessite que revocation_endpoint soit découvrable.
             -- S'il n'y a pas de point de terminaison de révocation fourni ou s'il y a des erreurs lors de la révocation, l'utilisateur ne sera pas informé et le processus de déconnexion se poursuivra normalement.

             -- Optionnel : utiliser un proxy sortant vers les points de terminaison du fournisseur OpenID Connect avec la table proxy_opts :
             -- cela nécessite lua-resty-http >= 0.12
             -- proxy_opts = {
             --    http_proxy  = "http://<proxy_host>:<proxy_port>/",
             --    https_proxy = "http://<proxy_host>:<proxy_port>/"
             -- }

             -- Hooks de cycle de vie
             --
             -- lifecycle = {
             --    on_created = handle_created,
             --    on_authenticated = handle_authenticated,
             --    on_regenerated = handle_regenerated
             --    on_logout = handle_logout
             -- }
             --
             --  `handle_created`, `handle_authenticated`, `handle_regenerated` et `handle_logout` sont des appelables
             -- acceptant l'argument `session`. `handle_created` accepte également un deuxième argument `params` qui est une table
             -- contenant les paramètres de requête de la demande d'autorisation utilisée pour rediriger l'utilisateur vers le fournisseur OpenID
             -- Connect.
             --
             --  -- Le hook `on_created` est invoqué *après* qu'une session a été créée dans
             --     `openidc_authorize` immédiatement avant de sauvegarder la session
             --  -- Le hook `on_authenticated` est invoqué *après* avoir reçu la réponse d'autorisation dans
             --     `openidc_authorization_response` immédiatement avant de sauvegarder la session
             --     À partir de lua-resty-openidc 1.7.5, cela reçoit le id_token décodé comme deuxième et la réponse du point de terminaison du jeton comme troisième argument      
             --  -- `on_regenerated` est invoqué immédiatement après que
             --     un nouveau jeton d'accès a été obtenu via le rafraîchissement du jeton
             --  -- Le hook `on_logout` est invoqué *avant* qu'une session ne soit détruite dans
             --     `openidc_logout`
             --
             --  Tous, certains ou aucun des hooks peuvent être utilisés. Un `lifecycle` vide ne fait rien.
             --  Un hook qui renvoie une valeur véridique entraîne l'échec de l'action de cycle de vie dont il fait partie.

             -- Optionnel : ajouter un décorateur pour la requête HTTP qui est
             -- appliqué lorsque lua-resty-openidc communique directement avec le fournisseur OpenID Connect. Peut être utilisé pour fournir des en-têtes HTTP supplémentaires
             -- ou ajouter un comportement similaire.
             -- http_request_decorator = function(req)
             --   local h = req.headers or {}
             --   h[EXTRA_HEADER] = 'mon en-tête supplémentaire'
             --   req.headers = h
             --   return req
             -- end,

             -- use_pkce = false,
             -- lorsqu'il est défini sur true, le "Proof Key for Code Exchange" tel que
             -- défini dans RFC 7636 sera utilisé. La méthode de défi de code
             -- sera toujours S256

          }

          -- Configurer lua-resty-session
          -- La liste complète des options de configuration est documentée dans le dépôt GitHub de lua-resty-session : https://github.com/bungle/lua-resty-session?tab=readme-ov-file#session-configuration
          local session_opts = {
               -- Lors de l'utilisation de cookies pour stocker des sessions, définissez un secret partagé pour le chiffrement des cookies de session. Cela permet aux sessions de rester valides après un redémarrage de nginx.
               -- Cela permet également une gestion de session "sans état", de sorte que plusieurs instances de nginx peuvent gérer des requêtes sans avoir besoin de techniques de répartition de charge "sticky".
               -- secret = "xxxxxxxxxxxxxxxxxxx",
               -- Optionnellement, définissez le préfixe de cookie pour éviter l'écrasement accidentel du cookie de session
               -- cookie_prefix = "__Host-",
               -- Les cookies de session de connexion doivent être HTTP Only
               cookie_http_only = true,
               -- Les cookies de session de connexion doivent être marqués comme "Secure"
               cookie_secure = true,
               -- Définir la même politique de cookie
               cookie_same_site = "Lax",
               -- Définissez ceci sur true si vous souhaitez que les cookies de session de connexion persistent après un redémarrage du navigateur
               remember = true
          }

          -- appeler authenticate pour l'authentification des utilisateurs 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

          -- à ce stade, res est une table Lua avec 3 clés :
          --   id_token    : une table Lua avec les revendications du id_token (requis)
          --   access_token: le jeton d'accès (optionnel)
          --   user        : une table Lua avec les revendications retournées par le point de terminaison des informations utilisateur (optionnel)

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

          -- définir les en-têtes avec les informations utilisateur : cela écrasera tous les en-têtes existants
          -- mais les nettoiera également (!) au cas  aucune valeur ne serait fournie dans le jeton
          ngx.req.set_header("X-USER", res.id_token.sub)
      }

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

À propos de redirect_uri

Le soi-disant redirect_uri est une URI qui fait partie du protocole OpenID Connect. L'URI de redirection est enregistré auprès de votre fournisseur OpenID Connect et est l'URI vers laquelle votre fournisseur redirigera les utilisateurs après une connexion réussie. Cette URI est ensuite gérée par lua-resty-openidc où elle obtient des jetons et effectue certaines vérifications, et ce n'est qu'après cela que le navigateur est redirigé vers l'endroit où votre utilisateur voulait aller initialement.

Le redirect_uri n'est pas censé être géré par votre code d'application du tout. Il doit être une URI dont lua-resty-openidc est responsable, donc il doit être dans un location protégé par lua-resty-openidc.

Vous configurez le redirect_uri du côté de lua-resty-openidc via le paramètre opts.redirect_uri (qui par défaut est /redirect_uri). S'il commence par un /, alors lua-resty-openidc préfixera le protocole et le nom d'hôte actuel lors de l'envoi de l'URI au fournisseur OpenID Connect (en tenant compte des en-têtes HTTP Forwarded et X-Forwarded-*). Mais vous pouvez également spécifier une URI absolue contenant l'hôte et le protocole vous-même.

Avant la version 1.6.1, opts.redirect_uri_path était la façon de configurer le redirect_uri sans aucune option pour contrôler les parties protocole et hôte.

Chaque fois que lua-resty-openidc "voit" un chemin local navigué qui correspond au chemin de opts.redirect_uri (ou opts.redirect_uri_path), il interceptera la requête et la gérera lui-même.

Cela fonctionne dans la plupart des cas, mais parfois l'URI de redirection visible de l'extérieur a un chemin différent de celui qui est localement visible par le serveur. Cela peut se produire si un proxy inverse devant votre serveur réécrit les URIs avant de transférer les requêtes. Par conséquent, la version 1.7.6 a introduit une nouvelle option opts.local_redirect_uri_path. Si elle est définie, lua-resty-openidc interceptera les requêtes vers ce chemin plutôt que le chemin de opts.redirect_uri.

Vérifier l'authentification uniquement

-- vérifier la session, mais ne pas rediriger vers l'auth si pas déjà connecté
local res, err = require("resty.openidc").authenticate(opts, nil, "pass")

Vérifier l'authentification uniquement et refuser l'accès non authentifié

-- vérifier la session, ne pas rediriger vers l'auth si pas déjà connecté mais retourner une erreur à la place
local res, err = require("resty.openidc").authenticate(opts, nil, "deny")

Sessions et verrouillage

La fonction authenticate retourne l'objet de session actuel comme son quatrième argument de retour. Si vous avez configuré lua-resty-session pour utiliser un backend de stockage côté serveur qui utilise le verrouillage, la session peut encore être verrouillée lorsqu'elle est retournée. Dans ce cas, vous voudrez peut-être la fermer explicitement.

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

Mise en cache

lua-resty-openidc peut utiliser des caches de mémoire partagée pour plusieurs choses. Si vous souhaitez qu'il utilise les caches, vous devez utiliser lua_shared_dict dans votre fichier nginx.conf.

Actuellement, jusqu'à quatre caches sont utilisés :

  • le cache nommé discovery stocke les métadonnées de découverte OpenID Connect de votre fournisseur OpenID Connect. Les éléments du cache expirent après 24 heures, sauf si remplacés par opts.discovery_expires_in (une valeur donnée en secondes). Ce cache stockera un élément par URI d'émetteur et vous pouvez consulter le document de découverte vous-même pour obtenir une estimation de la taille requise - généralement quelques kB par fournisseur OpenID Connect.
  • le cache nommé jwks stocke le matériel de clé de votre fournisseur OpenID Connect s'il est fourni via le point de terminaison JWKS. Les éléments du cache expirent après 24 heures, sauf si remplacés par opts.jwks_expires_in. Ce cache stockera un élément par URI JWKS et vous pouvez consulter le jwks vous-même pour obtenir une estimation de la taille requise - généralement quelques kB par fournisseur OpenID Connect.
  • le cache nommé introspection stocke le résultat de l'introspection du jeton OAuth2. Les éléments du cache expirent lorsque le jeton correspondant expire. Les jetons avec une expiration inconnue ne sont pas mis en cache du tout. Ce cache contiendra une entrée par jeton d'accès introspecté - généralement, cela sera quelques kB par jeton.
  • le cache nommé jwt_verification stocke le résultat de la vérification JWT. Les éléments du cache expirent lorsque le jeton correspondant expire. Les jetons avec une expiration inconnue ne sont pas mis en cache pendant deux minutes. Ce cache contiendra une entrée par JWT vérifié - généralement, cela sera quelques kB par jeton.

Mise en cache des résultats d'introspection et de vérification JWT

Notez que les caches jwt_verification et introspection sont partagés entre tous les emplacements configurés. Si vous utilisez des emplacements avec une configuration opts différente, le cache partagé peut permettre à un jeton qui n'est valide que pour un seul emplacement d'être accepté par un autre s'il est lu à partir du cache. Afin d'éviter la confusion du cache, il est recommandé de définir opts.cache_segment sur des chaînes uniques pour chaque ensemble d'emplacements liés.

Mise en cache des réponses d'introspection négatives

Par défaut, le cache introspection ne stockera pas les réponses négatives. Cela signifie qu'un acteur malveillant peut potentiellement essayer d'épuiser le point de terminaison d'introspection en inondant le service avec de nombreux appels avec un jeton inapproprié. Pour prévenir cette situation, opts.introspection_enable_negative_cache peut être défini sur true. Cela permettra au cache introspection de stocker des réponses négatives pendant le temps défini dans le champ exp. La mise en cache des réponses d'introspection négatives déchargera le trafic du point de terminaison d'introspection, mais exposera également NGINX à des attaques d'épuisement des ressources, car le stockage des réponses d'introspection négatives utilisera un stockage de cache supplémentaire.

Révoquer des jetons

La fonction revoke_tokens(opts, session) révoque le jeton de rafraîchissement et le jeton d'accès actuels. Contrairement à une déconnexion complète, le cookie de session ne sera pas détruit et le point de terminaison de fin de session ne sera pas appelé. La fonction retourne true si les deux jetons ont été révoqués avec succès. Cette fonction peut être utile dans des scénarios où vous souhaitez détruire/retirer une session du côté serveur.

Avec revoke_token(opts, token_type_hint, token), il est également possible de révoquer un jeton spécifique. token_type_hint peut généralement être refresh_token ou access_token.

Configuration d'exemple pour la validation des jetons JWT OAuth 2.0

Configuration d'exemple nginx.conf pour vérifier les jetons d'accès Bearer JWT contre un secret/clé préconfiguré. Une fois vérifié avec succès, le serveur NGINX peut fonctionner comme un proxy inverse vers un serveur d'origine interne.

events {
  worker_connections 128;
}

http {

  resolver 8.8.8.8;

  # cache pour les résultats de vérification JWT
  lua_shared_dict jwt_verification 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {

            -- 1. exemple d'un secret partagé pour la vérification de signature HS???
            --symmetric_key = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            -- dans les versions jusqu'à 1.6.1, la clé de cette option aurait été secret
            -- plutôt que symmetric_key

            -- 2. un autre exemple d'un certificat public pour la vérification de signature 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-----]],
            -- dans les versions jusqu'à 1.6.1, la clé de cette option aurait été secret
            -- plutôt que public_key

            -- 3. alternativement, on peut pointer vers un document de découverte qui
            -- contient l'entrée "jwks_uri" ; le point de terminaison jwks doit fournir soit une entrée "x5c"
            -- soit les entrées "n" modulus et "e" exponent pour la vérification de signature RSA
            -- discovery = "https://accounts.google.com/.well-known/openid-configuration",

             -- l'algorithme de signature que vous attendez d'avoir été utilisé ;
             -- peut être une seule chaîne ou une table.
             -- Vous devriez définir cela pour des raisons de sécurité afin d'éviter d'accepter un jeton prétendant être signé par HMAC
             -- en utilisant une clé publique RSA.
             --token_signing_alg_values_expected = { "RS256" }

             -- si vous souhaitez accepter des jetons non signés (utilisant l'algorithme
             -- de signature "none"), alors définissez ceci sur true.
             --accept_none_alg = false

             -- si vous souhaitez rejeter les jetons signés à l'aide d'un algorithme
             -- non pris en charge par lua-resty-jwt, définissez ceci sur false. Si
             -- vous le laissez non défini, la signature du jeton ne sera pas
             -- vérifiée du tout.
             --accept_unsupported_alg = true

             -- le temps d'expiration en secondes pour le cache jwk, par défaut c'est 1 jour.
             --jwk_expires_in = 24 * 60 * 60

             -- Il peut être nécessaire de forcer la vérification pour un jeton bearer et d'ignorer les résultats de vérification mis en cache existants.
             -- Si c'est le cas, vous devez définir l'option jwt_verification_cache_ignore sur true.
             -- jwt_verification_cache_ignore = true

             -- nom optionnel d'un segment de cache si vous avez besoin de caches séparés
             -- pour des emplacements configurés différemment
             -- cache_segment = 'api'
          }

          -- appeler bearer_jwt_verify pour la validation 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 "aucun access_token fourni")
            ngx.exit(ngx.HTTP_FORBIDDEN)
          end

          -- à ce stade, res est une table Lua qui représente le JSON
          -- payload dans le jeton JWT ; maintenant nous ne voulons généralement pas permettre juste n'importe quel
          -- jeton qui a été émis par le serveur d'autorisation, mais nous voulons appliquer
          -- certaines restrictions d'accès via des identifiants de client ou des 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;
    }
  }
}

Configuration d'exemple pour PingFederate OAuth 2.0

Configuration d'exemple nginx.conf pour valider les jetons d'accès Bearer contre un serveur d'autorisation 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 pour les résultats de validation
  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",

             -- Par défaut, c'est "exp" - Contrôle la durée de vie du cache d'introspection
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- nom optionnel d'un segment de cache si vous avez besoin de caches séparés
             -- pour des emplacements configurés différemment
             -- cache_segment = 'api'
          }

          -- appeler introspect pour la validation du jeton d'accès 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

          -- à ce stade, res est une table Lua qui représente l'objet JSON
          -- retourné par le point de terminaison d'introspection/validation

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

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

Configuration d'exemple nginx.conf pour valider les jetons d'accès Bearer passés en tant que cookie contre un serveur d'autorisation 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 pour les résultats de validation
  lua_shared_dict introspection 10m;

  server {
    listen 8080;

    location /api {

      access_by_lua '

          local opts = {
             -- définit l'URI du point de terminaison d'introspection
             introspection_endpoint="https://localhost:9031/oauth2/introspect",

             -- alternativement, si votre fournisseur OAuth2 fournit un document de découverte qui contient le
             -- champ introspection_endpoint, vous pouvez laisser l'option introspection_endpoint
             -- non définie et utiliser à la place
             -- discovery = "https://my-oauth2-provider/.well-known/oauth-authorization-server",

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

             -- Définit l'intervalle en secondes après lequel un jeton d'accès mis en cache et introspecté doit
             -- être rafraîchi en l'introspectant (et en le validant) à nouveau contre le serveur d'autorisation.
             -- Lorsqu'il n'est pas défini, la valeur est 0, ce qui signifie qu'il expire uniquement après le `exp` (ou alternatif,
             -- voir introspection_expiry_claim) indice tel que retourné par le serveur d'autorisation
             -- introspection_interval = 60,

             -- Définit la manière dont les jetons d'accès OAuth 2.0 Bearer peuvent être passés à ce serveur de ressources.
             -- "cookie" en tant qu'en-tête de cookie appelé "PA.global" ou en utilisant le nom spécifié après ":"
             -- "header" "Authorization: bearer" en-tête
             -- Lorsqu'il n'est pas défini, l'en-tête par défaut "Authorization: bearer" est utilisé
             -- auth_accept_token_as = "cookie:PA",

             -- Si l'en-tête est utilisée, le champ d'en-tête est Authorization
             -- auth_accept_token_as_header_name = "cf-Access-Jwt-Assertion"

             -- Méthode d'authentification pour le point de terminaison d'introspection du serveur d'autorisation OAuth 2.0,
             -- Utilisée pour authentifier le client auprès du point de terminaison d'introspection avec un client_id/client_secret
             -- Par défaut, c'est "client_secret_post"
             -- introspection_endpoint_auth_method = "client_secret_basic",

             -- Spécifiez les noms des cookies séparés par des espaces à récupérer depuis le navigateur et à envoyer lors des appels en backchannel
             -- au OP et AS points de terminaison.
             -- Lorsqu'il n'est pas défini, aucun cookie de ce type n'est envoyé.
             -- pass_cookies = "JSESSION"

             -- Par défaut, c'est "exp" - Contrôle la durée de vie du cache d'introspection
             -- https://tools.ietf.org/html/rfc7662#section-2.2
             -- introspection_expiry_claim = "exp"

             -- Il peut être nécessaire de forcer un appel d'introspection pour un access_token et d'ignorer les résultats d'introspection mis en cache existants. Si c'est le cas, vous devez définir l'option introspection_cache_ignore sur true.
             -- introspection_cache_ignore = true

             -- nom optionnel d'un segment de cache si vous avez besoin de caches séparés
             -- pour des emplacements configurés différemment
             -- cache_segment = 'api'
          }

          -- appeler introspect pour la validation du jeton d'accès 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

          -- à ce stade, res est une table Lua qui représente l'objet JSON
          -- retourné par le point de terminaison d'introspection/validation

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

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

Journalisation

La journalisation peut être personnalisée, y compris l'utilisation d'un journaliseur personnalisé et le remappage des niveaux de journalisation par défaut d'OpenIDC, par exemple :

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

Exécution des tests

Nous avons créé une configuration dockerisée pour le test afin de simplifier l'installation des dépendances.

Pour exécuter les tests, effectuez

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

si vous souhaitez créer luacov couverture pendant les tests, utilisez

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

comme deuxième commande

Support

Pour des questions générales, consultez les pages Wiki avec les questions fréquentes à :
https://github.com/zmartzone/lua-resty-openidc/wiki
Toute question/problème doit être adressée au suivi des discussions ou des problèmes de Github.

Avertissement

Ce logiciel est open source par ZmartZone IAM mais n'est pas soutenu commercialement en tant que tel. Toute question/problème doit être adressée au suivi des discussions ou des problèmes de Github. Voir également le fichier DISCLAIMER dans ce répertoire.

GitHub

Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-openidc.