web-dev-qa-db-fra.com

Comment vérifier JWT d'AWS Cognito dans le backend de l'API?

Je construis un système composé d'une application simple page Angular2 et d'une API REST s'exécutant sur ECS. L'API fonctionne sur .Net / Nancy , mais cela pourrait bien changer.

J'aimerais essayer Cognito et voici comment j'ai imaginé le processus d'authentification:

  1. SPA se connecte à l'utilisateur et reçoit un JWT
  2. SPA envoie JWT à l'API REST à chaque requête
  3. L'API REST vérifie que le JWT est authentique

Ma question concerne l'étape 3. Comment mon serveur (ou plutôt: mes conteneurs Docker sans charge, à échelle automatique et à charge équilibrée) peut-il vérifier l'authenticité du jeton? Le "serveur" n'ayant pas émis le JWT lui-même, il ne peut pas utiliser son propre secret (comme décrit dans l'exemple de base de JWT ici ).

J'ai parcouru la documentation de Cognito et cherché beaucoup, mais je ne trouve pas de bonne directive sur l'utilisation du JWT côté serveur.

43
EagleBeak

Il s'avère que je n'ai pas bien lu la documentation. C'est expliqué ici (faites défiler jusqu'à "Utilisation de jetons d'identification et de jetons d'accès dans vos API Web").

Le service API peut télécharger les secrets de Cognito et les utiliser pour vérifier les fichiers JWT reçus. Parfait.

Modifier

Le commentaire de @ Groady est pertinent: mais comment validez-vous les jetons? Je dirais que, pour cela, utilisez une bibliothèque testée au combat, telle que jose4j ou nimbus (les deux en Java) et n’implémentez pas vous-même la vérification.

Here Voici un exemple d'implémentation de Spring Boot avec nimbus qui m'a lancé lorsque j'ai récemment eu à l'implémenter dans le service Java/dropwizard.

28
EagleBeak

Voici un moyen de vérifier la signature sur NodeJS:

var jwt = require('jsonwebtoken');
var jwkToPem = require('jwk-to-pem');
var pem = jwkToPem(jwk);
jwt.verify(token, pem, function(err, decoded) {
  console.log(decoded)
});


// Note : You can get jwk from https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json 
18
FacePalm

J'ai eu un problème similaire mais sans utiliser la passerelle API. Dans mon cas, je souhaitais vérifier la signature d'un jeton JWT obtenue via la route d'identité authentifiée du développeur AWS Cognito.

Comme beaucoup d'affiches sur divers sites, j'ai eu du mal à rassembler exactement les éléments dont j'ai besoin pour vérifier la signature d'un jeton AWS JWT en externe, c.-à-d. Côté serveur ou via un script.

Je pense que j'ai compris et mis un Gist pour vérifier une signature de jeton AWS JWT . Il vérifiera un jeton AWS JWT/JWS avec pyjwt ou PKCS1_v1_5c de Crypto.Signature dans PyCrypto.

Donc, oui, il s’agissait de python dans mon cas, mais c’est aussi faisable facilement dans node (demande npm install jsonwebtoken jwk-to-pem). 

J'ai essayé de mettre en évidence quelques pièges dans les commentaires parce que, lorsque j'essayais de comprendre cela, je faisais surtout la bonne chose, mais il y avait certaines nuances telles que l'ordre du dict python, ou l'absence de représentation, et la représentation json.

J'espère que cela pourra aider quelqu'un quelque part. 

8
David Kierans

Réponse courte:
Vous pouvez obtenir la clé publique de votre groupe d'utilisateurs à partir du noeud final suivant:
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
Si vous décodez le jeton avec succès à l'aide de cette clé publique, le jeton est valide, sinon il est falsifié.


Longue réponse:
Après vous être authentifié via cognito, vous obtenez vos jetons d’accès et d’identifiant. Vous voulez maintenant valider si ce jeton a été falsifié ou non. En règle générale, nous renvoyions ces jetons au service d'authentification (qui a émis ce jeton en premier lieu) pour vérifier si le jeton est valide. Ces systèmes utilisent des algorithmes symmetric key encryption tels que HMAC pour chiffrer le contenu à l'aide d'une clé secrète. Seul ce système est donc capable de dire si ce jeton est valide ou non .
En-tête de jeton JWT d'authentification traditionnelle: 

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

Notez ici que l’algorithme de cryptage utilisé ici est symétrique - HMAC + SHA256

Mais les systèmes d’authentification modernes tels que Cognito utilisent des algorithmes asymmetric key encryption tels que RSA pour chiffrer la charge utile à l’aide d’une paire de clés publique et privée. Les données utiles sont cryptées à l'aide d'une clé privée, mais peuvent être décodées via une clé publique. L'avantage majeur de l'utilisation d'un tel algorithme est qu'il n'est pas nécessaire de demander un seul service d'authentification pour savoir si un jeton est valide ou non. Puisque tout le monde a accès à la clé publique, tout le monde peut vérifier la validité du jeton. La charge de validation est correctement répartie et il n’existe pas de point de défaillance unique.
En-tête de jeton Cognito JWT:

{
  "kid": "abcdefghijklmnopqrsexample=",
  "alg": "RS256"
}

Algorithme de chiffrement asymétrique utilisé dans ce cas - RSA + SHA256 

2
Gautam Jain

cela fonctionne pour moi en dot net 4.5 

    public static bool VerifyCognitoJwt(string accessToken)
    {
        string[] parts = accessToken.Split('.');

        string header = parts[0];
        string payload = parts[1];

        string headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
        JObject headerData = JObject.Parse(headerJson);

        string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
        JObject payloadData = JObject.Parse(payloadJson);

        var kid = headerData["kid"];
        var iss = payloadData["iss"];

        var issUrl = iss + "/.well-known/jwks.json";
        var keysJson= string.Empty;

        using (WebClient wc = new WebClient())
        {
            keysJson = wc.DownloadString(issUrl);
        }

        var keyData = GetKeyData(keysJson,kid.ToString());

        if (keyData==null)
            throw new ApplicationException(string.Format("Invalid signature"));

        var modulus = Base64UrlDecode(keyData.Modulus);
        var exponent = Base64UrlDecode(keyData.Exponent);

        RSACryptoServiceProvider provider = new RSACryptoServiceProvider();

        var rsaParameters= new RSAParameters();
        rsaParameters.Modulus = new BigInteger(modulus).ToByteArrayUnsigned();
        rsaParameters.Exponent = new BigInteger(exponent).ToByteArrayUnsigned();

        provider.ImportParameters(rsaParameters);

        SHA256CryptoServiceProvider sha256 = new SHA256CryptoServiceProvider();
        byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(parts[0] + "." + parts[1]));

        RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(provider);
        rsaDeformatter.SetHashAlgorithm(sha256.GetType().FullName);

        if (!rsaDeformatter.VerifySignature(hash, Base64UrlDecode(parts[2])))
            throw new ApplicationException(string.Format("Invalid signature"));

        return true;
    }

 public class KeyData
    {
        public string Modulus { get; set; }
        public string Exponent { get; set; }
    }

    private static KeyData GetKeyData(string keys,string kid)
    {
        var keyData = new KeyData();

        dynamic obj = JObject.Parse(keys);
        var results = obj.keys;
        bool found = false;

        foreach (var key in results)
        {
            if (found)
                break;

            if (key.kid == kid)
            {
                keyData.Modulus = key.n;
                keyData.Exponent = key.e;
                found = true;
            }
        }

        return keyData;
    }
0
Arvind Krmar