BLOG | NGINX

Authentification des clients API avec JWT et NGINX Plus

NGINX-Partie-de-F5-horiz-black-type-RGB
Miniature de Liam Crilly
Liam Crilly
Publié le 2 décembre 2021

Les jetons Web JSON (JWT, prononcés « jots ») sont un moyen compact et hautement portable d'échanger des informations d'identité. La spécification JWT a été un élément important d’ OpenID Connect , fournissant un jeton d’authentification unique pour l’écosystème OAuth 2.0. Les JWT peuvent également être utilisés comme informations d’identification d’authentification à part entière et constituent un meilleur moyen de contrôler l’accès aux API Web que les clés API traditionnelles.

NGINX Plus R10 et versions ultérieures peuvent valider directement les JWT. Dans cet article de blog, nous décrivons comment utiliser NGINX Plus comme passerelle API, en fournissant une interface à un point de terminaison API et en utilisant des JWT pour authentifier les applications clientes.

La prise en charge native de JWT est disponible uniquement dans NGINX Plus, pas dans NGINX Open Source.

Éditeur – Cet article de blog a été mis à jour en décembre 2021 pour utiliser la directive auth_jwt_require introduite dans NGINX Plus R25 . Pour une discussion détaillée de la directive, voir Règles de validation JWT personnalisées dans le blog annonçant NGINX Plus R25 .

NGINX Plus R15 et versions ultérieures peuvent également contrôler le « flux de code d'autorisation » dans OpenID Connect 1.0, ce qui permet l'intégration avec la plupart des principaux fournisseurs d'identité. Pour plus de détails, voir Annonce de NGINX Plus R15<.htmlspan> .

Anatomie d'un JWT

Les JWT comportent trois parties : un en-tête, une charge utile et une signature. En transmission, ils ressemblent à ce qui suit. Nous avons ajouté des sauts de ligne pour plus de lisibilité (le JWT réel est une chaîne unique) et un code couleur pour distinguer les trois parties :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . ewogICAgInN1YiI6ICJsYzEiLAogICAgImVtYWlsIjogImxpYW0uY3JpbGx5QG5naW54LmNvbSIsCn0= . VGYHWPterIaLjRi0LywgN3jnDUQbSsFptUw99g2slfc

Comme indiqué, un point ( . ) sépare l’en-tête, la charge utile et la signature. L'en-tête et la charge utile sont des objets JSON codés en Base64 . La signature est chiffrée à l'aide de l'algorithme spécifié par l'en-tête alg , que nous pouvons voir lorsque nous décodons notre exemple JWT :

  Encodé Décodé
En-tête eyJhbGciOiJIUzI1NiIsInR5cCI6Ik
pXVCJ9
{
"alg": "HS256",
"type": "JWT"
}
Charge utile ewogICAgInN1YiI6ICJsYzEiLAogICAgImVtYWlsIjogImxpYW0uY3JpbGx5QG5naW54LmNvbSIsCn0= {
"sous-marin": "lc1",
"courriel": "liam.crilly@nginx.com",
}

La norme JWT définit plusieurs algorithmes de signature. La valeur HS256 dans notre exemple fait référence à HMAC SHA‑256, que nous utilisons pour tous les exemples de JWT dans cet article de blog. NGINX Plus prend en charge les algorithmes de signature HS xxx , RS xxx et ES xxx définis dans la norme . La possibilité de signer de manière cryptographique les JWT les rend idéaux pour être utilisés comme informations d'identification d'authentification.

JWT comme clé API

Un moyen courant d’authentifier un client API (le client logiciel distant demandant des ressources API) consiste à utiliser un secret partagé, généralement appelé clé API . Une clé API traditionnelle est essentiellement un mot de passe long et complexe que le client envoie comme en-tête HTTP supplémentaire à chaque demande. Le point de terminaison de l'API accorde l'accès à la ressource demandée si la clé API fournie figure dans la liste des clés valides. En règle générale, le point de terminaison de l'API ne valide pas lui-même les clés API ; à la place, une passerelle API gère le processus d'authentification et achemine chaque demande vers le point de terminaison approprié. Outre le déchargement informatique, cela offre les avantages d'un proxy inverse, tels qu'une haute disponibilité et un équilibrage de charge sur un certain nombre de points de terminaison d'API.

Client API et authentification JWT avec une clé API traditionnelle
La passerelle API valide la clé API en consultant un registre de clés avant de transmettre la demande
au point de terminaison de l'API

Il est courant d’appliquer différents contrôles d’accès et politiques à différents clients API. Avec les clés API traditionnelles, cela nécessite une recherche pour faire correspondre la clé API avec un ensemble d'attributs. Effectuer cette recherche sur chaque requête a un impact compréhensible sur la latence globale du système. Avec JWT, ces attributs sont intégrés, éliminant ainsi le besoin d'une recherche séparée.

L’utilisation de JWT comme clé API offre une alternative hautes performances aux clés API traditionnelles, combinant une technologie d’authentification des meilleures pratiques avec un schéma basé sur des normes pour l’échange d’attributs d’identité.

Client API et authentification JWT avec JWT et NGINX Plus
NGINX Plus valide le JWT avant de transmettre la requête aux points de terminaison de l'API

Configuration de NGINX Plus comme passerelle API d'authentification

La configuration NGINX Plus pour la validation des JWT est très simple.

api_server en amont {
serveur 10.0.0.1 ;
serveur 10.0.0.2 ;
}

serveur {
écoute 80 ;

emplacement /produits/ {
auth_jwt "API des produits" ;
fichier_clé_auth_jwt conf/api_secret.jwk ;
proxy_pass http://api_server ;
}
}

La première chose que nous faisons est de spécifier les adresses des serveurs qui hébergent le point de terminaison de l’API, dans le bloc en amont . Le bloc d'emplacement spécifie que toutes les demandes adressées aux URL commençant par /products/ doivent être authentifiées. La directive auth_jwt définit le domaine d'authentification qui sera renvoyé (avec un401 (code d'état) si l'authentification échoue.

La directive auth_jwt_key_file indique à NGINX Plus comment valider l'élément de signature du JWT. Dans cet exemple, nous utilisons l'algorithme HMAC SHA-256 pour signer les JWT et nous devons donc créer une clé Web JSON dans conf/api_secret.jwk pour contenir la clé symétrique utilisée pour la signature. Le fichier doit suivre le format décrit par la spécification JSON Web Key ; notre exemple ressemble à ceci :

{"clés":
[{
"k": "ZmFudGFzdGljand0",
"kty": "oct",
"kid": "0001"
}]
}

La clé symétrique est définie dans le champ k et voici la valeur codée en Base64URL de la chaîne de caractères en texte clair fantasticjwt . Nous avons obtenu la valeur codée en exécutant cette commande :

$ echo -n fantastiquejwt | base64 | tr '+/' '-_' | tr -d '='

Le champ kty définit le type de clé comme une clé symétrique (séquence d'octets). Enfin, le champ kid (Key ID) définit un numéro de série pour cette clé Web JSON, ici0001 , ce qui nous permet de prendre en charge plusieurs clés dans le même fichier (nommé par la directive auth_jwt_key_file ) et de gérer le cycle de vie de ces clés et des JWT signés avec elles.

Nous sommes maintenant prêts à émettre des JWT à nos clients API.

Envoi d'un JWT aux clients API

En tant qu’exemple de client API, nous utiliserons une application de « système de devis » et créerons un JWT pour le client API. Nous définissons d’abord l’en-tête JWT :

{
"typ": "JWT",
"alg": "HS256",
"kid": "0001"
}

Le champ typ définit le type comme un jeton Web JSON, le champ alg spécifie que le JWT est signé avec l'algorithme HMAC SHA256 et le champ kid spécifie que le JWT est signé avec la clé Web JSON avec ce numéro de série.

Ensuite, nous définissons la charge utile JWT :

{
"name": "Système de devis",
"sub": "citations",
"iss": "Ma passerelle API"
}

Le champ sub (sujet) est notre identifiant unique pour la valeur complète dans le champ nom . Le champ iss décrit l’émetteur du JWT, ce qui est utile si votre passerelle API accepte également les JWT d’émetteurs tiers ou d’un système de gestion des identités centralisé.

Maintenant que nous avons tout ce dont nous avons besoin pour créer le JWT, nous suivons ces étapes pour l’encoder et le signer correctement. Les commandes et les valeurs codées apparaissent sur plusieurs lignes uniquement pour des raisons de lisibilité ; chacune est en fait tapée comme ou apparaît sur une seule ligne.

  1. Aplatissez et codez séparément l'en-tête et la charge utile en URL Base64.

     

    $ echo -n '{"typ":"JWT","alg":"HS256","kid":"0001"}' | base64 | tr '+/' '-_' | tr -d '=' eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ $ echo -n '{"name":"Système de devis","sub":"quotes","iss":"Ma passerelle API"}' | base64 | tr '+/' '-_' | tr -d '=' eyJuYW1lIjoiUXVvdGF0aW9uIFN5c3RlbSIsInN1YiI6InF1b3RlcyIsImlzcyI6Ik 15IEFQSSBHYXRld2F5In0
    
  2. Concaténez l'en-tête codé et la charge utile avec un point (.) et attribuez le résultat à la variable HEADER_PAYLOAD .

    $ HEADER_PAYLOAD=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjAwMDEifQ.eyJuYW1lIjoiUXVvdGF0aW9uIFN5c3RlbSIsInN1YiI6InF1b3RlcyIsIm lzcyI6Ik15IEFQSSBHYXRld2F5In0
    
  3. Signez l'en-tête et la charge utile avec notre clé symétrique et encodez la signature en Base64URL.

    $ echo -n $HEADER_PAYLOAD | openssl dgst -binary -sha256 -hmac fantastiquejwt | base64 | tr '+/' '-_' | tr -d '=' ggVOHYnVFB8GVPE-VOIo3jD71gTkLffAY0hQOGXPL2I
    
  4. Ajoutez la signature codée à l’en-tête et à la charge utile.

    $ echo $HEADER_PAYLOAD.ggVOHYnVFB8GVPE-VOIo3jD71gTkLffAY0hQOGXPL2I > citations.jwt
    
  5. Testez en effectuant une demande authentifiée à la passerelle API (dans cet exemple, la passerelle s'exécute sur localhost).

    $ curl -H "Autorisation : Porteur `cat quotes.jwt`" http://localhost/products/widget1
    

La commande curl de l’étape 5 envoie le JWT à NGINX Plus sous la forme d’un jeton porteur , ce que NGINX Plus attend par défaut. NGINX Plus peut également obtenir le JWT à partir d'un cookie ou d'un paramètre de chaîne de requête ; pour configurer cela, incluez le paramètre token= dans la directive auth_jwt . Par exemple, avec la configuration suivante, NGINX Plus peut valider le JWT envoyé avec cette commande curl :

$ curl http://localhost/products/widget1?apijwt=`citations de chat.jwt`
serveur { écoute 80 ; emplacement /produits/ { auth_jwt "API Produits" token=$arg_apijwt ; auth_jwt_key_file conf/api_secret.jwk ; proxy_pass http://api_server; } }

Une fois que vous avez configuré NGINX Plus et généré et vérifié un JWT comme indiqué ci-dessus, vous êtes prêt à envoyer le JWT au développeur du client API et à convenir du mécanisme qui sera utilisé pour soumettre le JWT avec chaque demande d'API.

Exploitation des revendications JWT pour la journalisation et la limitation du débit

L’un des principaux avantages des JWT en tant qu’identifiants d’authentification est qu’ils véhiculent des « revendications », qui représentent des entités associées au JWT et à sa charge utile (son émetteur, l’utilisateur à qui il a été émis et le destinataire prévu, par exemple). Après avoir validé le JWT, NGINX Plus a accès à tous les champs présents dans l'en-tête et la charge utile sous forme de variables. On y accède en préfixant $jwt_header_ ou $jwt_claim_ au champ souhaité (par exemple, $jwt_claim_sub pour la sous- réclamation). Cela signifie que nous pouvons très facilement transmettre les informations contenues dans le JWT au point de terminaison de l'API sans avoir besoin d'implémenter le traitement JWT dans l'API elle-même.

Cet exemple de configuration montre certaines des fonctionnalités avancées.

log_format jwt '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent" ' '$jwt_header_alg $jwt_claim_sub' ; limite_zone de demande $jwt_claim_sub zone=10rps_per_client:1m débit=10r/s; serveur { écoute 80; emplacement /produits/ { auth_jwt "API Produits"; fichier_clé_auth_jwt conf/api_secret.jwk; limite_zone de demande=10rps_per_client; proxy_pass http://api_server; proxy_set_header API-Client $jwt_claim_sub; journal_d'accès /var/log/nginx/access_jwt.log jwt; } }

La directive log_format définit un nouveau format appelé jwt qui étend le format de journal commun avec deux champs supplémentaires, $jwt_header_alg et $jwt_claim_sub . Dans le bloc d'emplacement , nous utilisons la directive access_log pour écrire des journaux avec les valeurs obtenues à partir du JWT validé.

Dans cet exemple, nous utilisons également des variables basées sur des revendications pour fournir une limitation du débit d'API par client API, au lieu de par adresse IP. Cela est particulièrement utile lorsque plusieurs clients API sont intégrés dans un seul portail et ne peuvent pas être différenciés par adresse IP. La directive limit_req_zone utilise la sous -réclamation JWT comme clé pour calculer les limites de débit, qui sont ensuite appliquées au bloc d'emplacement en incluant la directive limit_req .

Enfin, nous fournissons le sujet JWT sous forme de nouvel en-tête HTTP lorsque la requête est transmise par proxy au point de terminaison de l'API. La directive proxy_set_header ajoute un en-tête HTTP appelé API-Client que le point de terminaison de l'API peut facilement consommer. Par conséquent, le point de terminaison de l’API n’a pas besoin d’implémenter de logique de traitement JWT. Cela devient de plus en plus précieux à mesure que le nombre de points de terminaison d’API augmente.

Révocation des JWT

Il peut être nécessaire de temps à autre de révoquer ou de réémettre le JWT d'un client API. En combinant un bloc de carte simple avec la directive auth_jwt_require , nous pouvons refuser l'accès à un client API en marquant son JWT comme non valide jusqu'à ce que la date d'expiration du JWT (représentée dans la revendication exp ) soit atteinte, moment auquel l'entrée de carte pour ce JWT peut être supprimée en toute sécurité.

Dans cet exemple, nous définissons la variable $jwt_status sur0 ou1 selon la valeur de la sous -réclamation dans le jeton (comme capturé dans la variable $jwt_claim_sub ). Nous utilisons ensuite la directive auth_jwt_require dans le bloc d'emplacement pour valider (ou rejeter) en plus le jeton. Pour être valide, la variable $jwt_status ne doit pas être vide et ne doit pas être égale à0 (zéro) .

carte $jwt_claim_sub $jwt_status { "quotes" 0; "test" 0; default 1; } serveur { écoute 80; emplacement /produits/ { auth_jwt "API Produits"; auth_jwt_key_file conf/api_secret.jwk; auth_jwt_require $jwt_status; proxy_pass http://api_server; } }

Résumé

Les jetons Web JSON sont bien adaptés pour fournir un accès authentifié aux API. Pour le développeur client API, ils sont tout aussi faciles à gérer que les clés API traditionnelles et ils fournissent à la passerelle API des informations d'identité qui nécessitent autrement une recherche dans la base de données. NGINX Plus fournit un support pour l’authentification JWT et des solutions de configuration sophistiquées basées sur les informations contenues dans le JWT lui-même. Associé à d’autres fonctionnalités de passerelle API , NGINX Plus vous permet de fournir des services basés sur API avec rapidité, fiabilité, évolutivité et sécurité.

Pour essayer JWT avec NGINX Plus par vous-même, démarrez votre essai gratuit de 30 jours dès aujourd'hui ou contactez-nous pour discuter de vos cas d'utilisation .


« Cet article de blog peut faire référence à des produits qui ne sont plus disponibles et/ou qui ne sont plus pris en charge. Pour obtenir les informations les plus récentes sur les produits et solutions F5 NGINX disponibles, explorez notre famille de produits NGINX . NGINX fait désormais partie de F5. Tous les liens NGINX.com précédents redirigeront vers un contenu NGINX similaire sur F5.com."