Как проверить JWT от AWS Cognito в API-интерфейсе API?
Я создаю систему, состоящую из приложения с одной страницей Angular2 и REST API, работающего на ECS. API работает на .Net/Nancy, но это может измениться.
Я хотел бы дать Cognito попробовать, и вот как я представил процесс проверки подлинности:
- Знаки SPA в пользователе и получают JWT
- SPA отправляет JWT API REST с каждым запросом
- API REST подтверждает, что JWT является подлинным
Мой вопрос о шаге 3. Как мой сервер (а точнее: мои бездокументарные, автомасштабированные, балансированные по нагрузке контейнеры Docker) подтверждают, что токен аутентичен? Поскольку "сервер" не выдал сам JWT, он не может использовать свой собственный секрет (как описано в основном примере JWT здесь).
Я прочитал документы Cognito и много сделал для googled, но я не могу найти никаких хороших рекомендаций относительно того, что делать с JWT на стороне сервера.
Ответы
Ответ 1
Оказывается, я не правильно прочитал документы. Это объясняется здесь (прокрутите вниз до "Использование идентификаторов токенов и токенов доступа в ваших веб-API").
Служба API может загружать секреты Cognito и использовать их для проверки полученных JWT. Отлично.
редактировать
@Хороший комментарий на точку: но как вы проверяете токены? Я бы сказал, что Используйте боевые проверенные библиотеки как jose4j или венчик (как Java) для этого и не осуществлять проверку с нуля самостоятельно.
Вот пример реализации Spring Boot с использованием nimbus, с которого я начал, когда мне недавно пришлось реализовать это в сервисе java/dropwizard.
Ответ 2
Вот способ проверить подпись на 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
Ответ 3
У меня была похожая проблема, но без использования шлюза API. В моем случае я хотел проверить подпись токена JWT, полученного через идентификационный маршрут AWS Cognito Developer Authenticated.
Как и многие постеры на разных сайтах, у меня возникли проблемы с соединением именно тех битов, которые мне нужны для внешней проверки подписи токена AWS JWT, т.е. На стороне сервера или с помощью сценария.
Я думаю, что выяснил и поставил суть, чтобы проверить подпись токена AWS JWT. Он будет проверять токен AWS JWT/JWS с помощью pyjwt или PKCS1_v1_5c из Crypto.Signature в PyCrypto.
Так что, да, в моем случае это был python, но он также легко выполним в узле (запрос nw install jsonwebtoken jwk-to-pem).
Я попытался выделить несколько замечаний в комментариях, потому что, когда я пытался понять это, я в основном делал правильные вещи, но были некоторые нюансы, такие как упорядочение в python dict или его отсутствие, и представление json.
Надеюсь, это может кому-нибудь помочь.
Ответ 4
Выполнить поток предоставления кода авторизации
Предполагая, что вы:
Ваш браузер должен перенаправить на <your-redirect-uri>?code=4dd94e4f-3323-471e-af0f-dc52a8fe98a0
Теперь вам нужно передать этот код своему бэкэнду и попросить у него токен.
POST https://<your-domain>.auth.us-west-2.amazoncognito.com/oauth2/token
- установите заголовок
Authorization
на Basic
и используйте username=<app client id>
и password=<app client secret>
для клиента вашего приложения, настроенного в AWS Cognito
- установите следующее в теле запроса:
grant_type=authorization_code
code=<your-code>
client_id=<your-client-id>
redirect_uri=<your-redirect-uri>
В случае успеха ваш сервер должен получить набор токенов в кодировке base64.
{
id_token: '...',
access_token: '...',
refresh_token: '...',
expires_in: 3600,
token_type: 'Bearer'
}
Теперь, согласно документации, ваш бэкэнд должен проверить подпись JWT:
- Расшифровка идентификатора токена
- Сравнение идентификатора локального ключа (ребенка) с открытым ребенком
- Использование открытого ключа для проверки подписи с использованием вашей библиотеки JWT.
Поскольку AWS Cognito генерирует две пары криптографических ключей RSA для каждого пула пользователей, вам необходимо выяснить, какой ключ использовался для шифрования токена.
Вот фрагмент NodeJS, демонстрирующий проверку JWT.
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')
Ответ 5
Короткий ответ:
Вы можете получить открытый ключ для своего пула пользователей из следующей конечной точки:
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json
Если вы успешно расшифровали токен с помощью этого открытого ключа, токен действителен, иначе он подделан.
Длинный ответ:
После успешной аутентификации с помощью cognito вы получаете токены доступа и id. Теперь вы хотите проверить, был ли этот токен подделан или нет. Традиционно мы отправляли эти токены обратно в службу аутентификации (которая выдавала этот токен в первую очередь), чтобы проверить, действителен ли токен. Эти системы используют алгоритмы symmetric key encryption
, такие как HMAC
для шифрования полезной нагрузки с использованием секретного ключа, и поэтому только эта система способна определить, действителен ли этот токен или нет.
Заголовок токена JWT:
{
"alg": "HS256",
"typ": "JWT"
}
Обратите внимание, что алгоритм шифрования, используемый здесь, является симметричным - HMAC + SHA256
Но современные системы аутентификации, такие как Cognito, используют алгоритмы asymmetric key encryption
, такие как RSA
для шифрования полезной нагрузки с использованием пары открытого и закрытого ключей. Полезная нагрузка шифруется с использованием закрытого ключа, но может быть расшифрована с помощью открытого ключа. Основным преимуществом использования такого алгоритма является то, что нам не нужно запрашивать единый сервис аутентификации, чтобы узнать, является ли токен действительным или нет. Поскольку каждый имеет доступ к открытому ключу, любой может проверить действительность токена. Нагрузка для проверки достоверно распределена, и нет единой точки отказа.
Заголовок токена Cognito JWT:
{
"kid": "abcdefghijklmnopqrsexample=",
"alg": "RS256"
}
В этом случае используется алгоритм асимметричного шифрования - RSA + SHA256
Ответ 6
это работает для меня в точечной сети 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;
}