it-swarm.com.de

Wie kann ich JWT von AWS Cognito im API-Backend überprüfen?

Ich baue ein System, das aus einer Angular2-App für eine einzige Seite und einer REST - API besteht, die auf ECS ausgeführt wird. Die API läuft unter .Net / Nancy , aber das könnte sich ändern.

Ich möchte Cognito ausprobieren, und so stellte ich mir den Authentifizierungsworkflow vor:

  1. SPA meldet sich beim Benutzer an und erhält eine JWT
  2. SPA sendet bei jeder Anforderung JWT an die API REST
  3. Die REST-API bestätigt, dass die JWT authentisch ist

Meine Frage bezieht sich auf Schritt 3. Wie kann mein Server (oder besser: mein statusloser, automatisch skalierter Docker-Container mit Lastausgleich) überprüfen, ob das Token authentisch ist? Da der "Server" die JWT selbst nicht ausgegeben hat, kann er kein eigenes Geheimnis verwenden (wie im grundlegenden JWT-Beispiel hier beschrieben).

Ich habe die Cognito-Dokumente gelesen und viel gegoogelt, aber ich kann keine gute Richtlinie finden, was auf der Serverseite mit dem JWT zu tun ist.

43
EagleBeak

Es stellte sich heraus, dass ich die Dokumente nicht richtig gelesen habe. Es wird hier erklärt (scrollen Sie nach unten zu "ID-Token und Access-Token in Ihren Web-APIs verwenden").

Der API-Dienst kann die Geheimnisse von Cognito herunterladen und zur Bestätigung der erhaltenen JWTs verwenden. Perfekt.

Bearbeiten

@ Groadys Kommentar steht auf dem Punkt: aber wie validiert ihr die Token? Ich würde sagen, verwenden Sie eine kampferprobte Bibliothek wie jose4j oder Nimbus (beide Java) und implementieren Sie die Verifizierung nicht von Grund auf selbst.

Here ist eine Beispielimplementierung für Spring Boot unter Verwendung von Nimbus, mit der ich angefangen habe, als ich dies kürzlich in Java/dropwizard implementieren musste.

28
EagleBeak

So überprüfen Sie die Signatur auf 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

Ich hatte ein ähnliches Problem, aber ohne das API-Gateway zu verwenden. In meinem Fall wollte ich die Signatur eines JWT-Tokens überprüfen, das über die Identitätsroute von AWS Cognito Developer Authenticated Identity abgerufen wurde.

Wie bei vielen Postern auf verschiedenen Websites hatte ich Probleme, genau die Bits zusammenzustellen, die ich benötige, um die Signatur eines AWS-JWT-Tokens extern zu überprüfen, d

Ich glaube, ich habe es herausgefunden und eine Gist auf AWS JWT-Token-Signatur überprüfen gesetzt. Ein AWS-JWT/JWS-Token wird entweder mit pyjwt oder PKCS1_v1_5c von Crypto.Signature in PyCrypto überprüft

Ja, das war in meinem Fall Python, aber es ist auch problemlos im Knoten möglich (npm install jsonwebtoken jwk-to-pem-Anfrage). 

Ich habe versucht, einige Gotchas in den Kommentaren hervorzuheben, denn als ich versuchte, dies herauszufinden, tat ich meistens das Richtige, aber es gab einige Nuancen wie Python-Diktierreihenfolge oder Mangel an Json-Darstellung.

Hoffentlich kann es jemandem irgendwo helfen. 

8
David Kierans

Ausführen eines Berechtigungscode-Erteilungsflusses

Vorausgesetzt, Sie:

  • einen Benutzerpool in AWS Cognito korrekt konfiguriert haben und
  • können sich anmelden/einloggen und erhalten einen Zugangscode über:

    https://<your-domain>.auth.us-west-2.amazoncognito.com/login?response_type=code&client_id=<your-client-id>&redirect_uri=<your-redirect-uri>
    

Ihr Browser sollte auf <your-redirect-uri>?code=4dd94e4f-3323-471e-af0f-dc52a8fe98a0 Umleiten


Jetzt müssen Sie diesen Code an Ihr Back-End übergeben und von diesem einen Token anfordern lassen.

POST https://<your-domain>.auth.us-west-2.amazoncognito.com/oauth2/token

  • setzen Sie Ihren Authorization -Header auf Basic und verwenden Sie username=<app client id> und password=<app client secret> für Ihren in AWS Cognito konfigurierten App-Client
  • geben Sie Folgendes in Ihren Anfragetext ein:
    • grant_type=authorization_code
    • code=<your-code>
    • client_id=<your-client-id>
    • redirect_uri=<your-redirect-uri>

Bei Erfolg sollte Ihr Back-End einen Satz Base64-codierter Token erhalten.

{
    id_token: '...',
    access_token: '...',
    refresh_token: '...',
    expires_in: 3600,
    token_type: 'Bearer'
}

Nun sollte Ihr Back-End laut Dokumentation die JWT-Signatur validieren durch:

  1. ID-Token entschlüsseln
  2. Vergleichen der lokalen Schlüssel-ID (Kind) mit dem öffentlichen Kind
  3. Verwenden des öffentlichen Schlüssels zum Überprüfen der Signatur mithilfe Ihrer JWT-Bibliothek.

Da AWS Cognito für jeden Benutzerpool zwei Paare von RSA-Verschlüsselungsschlüsseln generiert, müssen Sie herausfinden, mit welchem ​​Schlüssel das Token verschlüsselt wurde.

Hier ist ein NodeJS Snippet, das die Überprüfung einer JWT demonstriert.

import jsonwebtoken from 'jsonwebtoken'
import jwkToPem from 'jwk-to-pem'

const jsonWebKeys = [  // from https://cognito-idp.us-west-2.amazonaws.com/<UserPoolId>/.well-known/jwks.json
    {
        "alg": "RS256",
        "e": "AQAB",
        "kid": "ABCDEFGHIJKLMNOPabc/1A2B3CZ5x6y7MA56Cy+6ubf=",
        "kty": "RSA",
        "n": "...",
        "use": "sig"
    },
    {
        "alg": "RS256",
        "e": "AQAB",
        "kid": "XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=",
        "kty": "RSA",
        "n": "...",
        "use": "sig"
    }
]

function validateToken(token) {
    const header = decodeTokenHeader(token)  // {"kid":"XYZAAAAAAAAAAAAAAA/1A2B3CZ5x6y7MA56Cy+6abc=", "alg": "RS256"}
    const jsonWebKey = getJsonWebKeyWithKID(header.kid)
    verifyJsonWebTokenSignature(token, jsonWebKey, function(err, decodedToken) {
        if (err) {
            console.error(err)
        } else {
            console.log(decodedToken)
        }
    })
}

function decodeTokenHeader(token) {
    const [headerEncoded] = token.split('.')[0]
    const buff = new Buffer(headerEncoded, 'base64')
    const text = buff.toString('ascii')
    return JSON.parse(text)
}

func getJsonWebKeyWithKID(kid) {
    for (let jwk of jsonWebKeys) {
        if (jwk.kid == kid) {
            return jwk
        }
    }
    return null
}

function verifyJsonWebTokenSignature(token, jsonWebKey, clbk) {
    const pem = jwkToPem(jsonWebKey)
    jsonwebtoken.verify(token, pem, { algorithms: ['RS256'] }, function(err, decodedToken) {
        return clbk(err, decodedToken)
    })
}

validateToken('xxxxxxxxx.XXXXXXXX.xxxxxxxx')
3
Derek Soike

Kurze Antwort:
Den öffentlichen Schlüssel für Ihren Benutzerpool erhalten Sie von folgendem Endpunkt:
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
Wenn Sie das Token erfolgreich mit diesem öffentlichen Schlüssel decodieren, ist das Token gültig, andernfalls wird es gefälscht.


Lange Antwort:
Nachdem Sie sich erfolgreich über cognito authentifiziert haben, erhalten Sie Ihre Zugangs- und ID-Token. Jetzt möchten Sie überprüfen, ob dieses Token manipuliert wurde oder nicht. Normalerweise senden wir diese Token zurück an den Authentifizierungsdienst (der dieses Token an erster Stelle ausgegeben hat), um zu prüfen, ob das Token gültig ist. Diese Systeme verwenden symmetric key encryption-Algorithmen wie HMAC, um die Nutzdaten mit einem geheimen Schlüssel zu verschlüsseln. Daher kann nur dieses System erkennen, ob dieses Token gültig ist oder nicht .
Traditioneller auth-JWT-Token-Header: 

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

Beachten Sie hier, dass der hier verwendete Verschlüsselungsalgorithmus symmetrisch ist - HMAC + SHA256

Moderne Authentifizierungssysteme wie Cognito verwenden jedoch asymmetric key encryption-Algorithmen wie RSA, um die Nutzdaten mit einem Paar öffentlicher und privater Schlüssel zu verschlüsseln. Die Payload wird mit einem privaten Schlüssel verschlüsselt, kann aber mit einem öffentlichen Schlüssel entschlüsselt werden. Ein großer Vorteil der Verwendung eines solchen Algorithmus besteht darin, dass wir keinen einzigen Authentifizierungsdienst anfordern müssen, um festzustellen, ob ein Token gültig ist oder nicht. Da jeder Zugriff auf den öffentlichen Schlüssel hat, kann jeder die Gültigkeit des Tokens überprüfen. Die Last für die Validierung ist gerecht verteilt und es gibt keinen einzigen Ausfallpunkt.
Cognito JWT-Token-Header:

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

In diesem Fall verwendeter asymmetrischer Verschlüsselungsalgorithmus - RSA + SHA256 

2
Gautam Jain

das funktioniert für mich in 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