Estoy atascado en el proceso "Grupos de usuarios de Amazon Cognito Identity".
Probé todos los códigos posibles para autenticar al usuario en los grupos de usuarios de cognito. Pero siempre recibo un error que dice "Error: no se puede verificar el hash secreto para el cliente 4b ******* fd".
Aquí está el código:
AWS.config.region = 'us-east-1'; // Region AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: 'us-east-1:b64bb629-ec73-4569-91eb-0d950f854f4f' }); AWSCognito.config.region = 'us-east-1'; AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: 'us-east-1:b6b629-er73-9969-91eb-0dfffff445d' }); AWSCognito.config.update({accessKeyId: 'AKIAJNYLRONAKTKBXGMWA', secretAccessKey: 'PITHVAS5/UBADLU/dHITesd7ilsBCm'}) var poolData = { UserPoolId : 'us-east-1_l2arPB10', ClientId : '4bmsrr65ah3oas5d4sd54st11k' }; var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData); var userData = { Username : 'ronakpatel@gmail.com', Pool : userPool }; var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData); cognitoUser.confirmRegistration('123456', true,function(err, result) { if (err) { alert(err); return; } console.log('call result: ' + result); });C++ con el Marco Qt
QByteArray MyObject::secretHash( const QByteArray& email, const QByteArray& appClientId, const QByteArray& appSecretKey) { QMessageAuthenticationCode code(QCryptographicHash::Sha256); code.setKey(appSecretKey); code.addData(email); code.addData(appClientId); return code.result().toBase64(); };Dado que todos los demás han publicado su idioma, aquí está el nodo (y funciona en el navegador con browserify-crypto , se usa automáticamente si usa webpack o browserify):
const crypto = require('crypto'); ... crypto.createHmac('SHA256', clientSecret) .update(username + clientId) .digest('base64')Solución para golang . Parece que esto debería agregarse al SDK.
import ( "crypto/hmac" "crypto/sha256" "encoding/base64" ) func SecretHash(username, clientID, clientSecret string) string { mac := hmac.New(sha256.New, []byte(clientSecret)) mac.Write([]byte(username + ClientID)) return base64.StdEncoding.EncodeToString(mac.Sum(nil)) }Esto puede tardar algunos años, pero simplemente desmarque la opción "Generar secreto de cliente" y funcionará para sus clientes web.
para JAVA y .NET, debe pasar el secreto que tiene en los parámetros de autenticación con el nombre SECRET_HASH .
AdminInitiateAuthRequest request = new AdminInitiateAuthRequest { ClientId = this.authorizationSettings.AppClientId, AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH, AuthParameters = new Dictionary<string, string> { {"USERNAME", username}, {"PASSWORD", password}, { "SECRET_HASH", EncryptionHelper.GetSecretHash(username, AppClientId, AppClientSecret) } }, UserPoolId = this.authorizationSettings.UserPoolId };Y debería funcionar.
Solución para NodeJS con SecretHash
Parece una tontería que AWS eliminó la clave secreta del SDK, ya que no estará expuesta en NodeJS.
Lo hice funcionar en NodeJS interceptando fetch y agregando la clave hash usando la respuesta de @Simon Buchan .
cognito.js
import { CognitoUserPool, CognitoUserAttribute, CognitoUser } from 'amazon-cognito-identity-js' import crypto from 'crypto' import * as fetchIntercept from './fetch-intercept' const COGNITO_SECRET_HASH_API = [ 'AWSCognitoIdentityProviderService.ConfirmForgotPassword', 'AWSCognitoIdentityProviderService.ConfirmSignUp', 'AWSCognitoIdentityProviderService.ForgotPassword', 'AWSCognitoIdentityProviderService.ResendConfirmationCode', 'AWSCognitoIdentityProviderService.SignUp', ] const CLIENT_ID = 'xxx' const CLIENT_SECRET = 'xxx' const USER_POOL_ID = 'xxx' const hashSecret = (clientSecret, username, clientId) => crypto.createHmac('SHA256', clientSecret) .update(username + clientId) .digest('base64') fetchIntercept.register({ request(url, config) { const { headers } = config if (headers && COGNITO_SECRET_HASH_API.includes(headers['X-Amz-Target'])) { const body = JSON.parse(config.body) const { ClientId: clientId, Username: username } = body // eslint-disable-next-line no-param-reassign config.body = JSON.stringify({ ...body, SecretHash: hashSecret(CLIENT_SECRET, username, clientId), }) } return [url, config] }, }) const userPool = new CognitoUserPool({ UserPoolId: USER_POOL_ID, ClientId: CLIENT_ID, }) const register = ({ email, password, mobileNumber }) => { const dataEmail = { Name: 'email', Value: email } const dataPhoneNumber = { Name: 'phone_number', Value: mobileNumber } const attributeList = [ new CognitoUserAttribute(dataEmail), new CognitoUserAttribute(dataPhoneNumber), ] return userPool.signUp(email, password, attributeList, null, (err, result) => { if (err) { console.log((err.message || JSON.stringify(err))) return } const cognitoUser = result.user console.log(`user name is ${cognitoUser.getUsername()}`) }) } export { register, }fetch-inceptor.js (bifurcado y editado para NodeJS de Fork de https://github.com/werk85/fetch-intercept/blob/develop/src/index.js )
let interceptors = [] if (!global.fetch) { try { // eslint-disable-next-line global-require global.fetch = require('node-fetch') } catch (err) { throw Error('No fetch available. Unable to register fetch-intercept') } } global.fetch = (function (fetch) { return (...args) => interceptor(fetch, ...args) }(global.fetch)) const interceptor = (fetch, ...args) => { const reversedInterceptors = interceptors.reduce((array, _interceptor) => [_interceptor].concat(array), []) let promise = Promise.resolve(args) // Register request interceptors reversedInterceptors.forEach(({ request, requestError }) => { if (request || requestError) { promise = promise.then(_args => request(..._args), requestError) } }) // Register fetch call promise = promise.then(_args => fetch(..._args)) // Register response interceptors reversedInterceptors.forEach(({ response, responseError }) => { if (response || responseError) { promise = promise.then(response, responseError) } }) return promise } const register = (_interceptor) => { interceptors.push(_interceptor) return () => { const index = interceptors.indexOf(_interceptor) if (index >= 0) { interceptors.splice(index, 1) } } } const clear = () => { interceptors = [] } export { register, clear, }Puede haber una versión más compacta, pero esta funciona para Ruby, específicamente en Ruby on Rails sin tener que requerir nada:
key = ENV['COGNITO_SECRET_HASH'] data = username + ENV['COGNITO_CLIENT_ID'] digest = OpenSSL::Digest.new('sha256') hmac = Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))Amazon menciona cómo Computing SecretHash Values for Amazon Cognito en su documentación con el código de la aplicación Java. Aquí este código funciona con boto 3 Python SDK .
Puede encontrar sus App clients en el menú del lado izquierdo en General settings . Obtenga App client id de App client secret de la aplicación para crear SECRET_HASH . Para su mejor comprensión, comenté todos los resultados de todas y cada una de las líneas.
import hashlib import hmac import base64 app_client_secret = 'u8f323eb3itbr3731014d25spqtv5r6pu01olpp5tm8ebicb8qa' app_client_id = '396u9ekukfo77nhcfbmqnrec8p' username = 'wasdkiller' # convert str to bytes key = bytes(app_client_secret, 'latin-1') # b'u8f323eb3itbr3731014d25spqtv5r6pu01olpp5tm8ebicb8qa' msg = bytes(username + app_client_id, 'latin-1') # b'wasdkiller396u9ekukfo77nhcfbmqnrec8p' new_digest = hmac.new(key, msg, hashlib.sha256).digest() # b'P$#\xd6\xc1\xc0U\xce\xc1$\x17\xa1=\x18L\xc5\x1b\xa4\xc8\xea,\x92\xf5\xb9\xcdM\xe4\x084\xf5\x03~' SECRET_HASH = base64.b64encode(new_digest).decode() # UCQj1sHAVc7BJBehPRhMxRukyOoskvW5zU3kCDT1A34= En la documentación del boto 3 , podemos ver mucho tiempo preguntando sobre SECRET_HASH . Entonces, las líneas de código anteriores lo ayudan a crear este SECRET_HASH .
Si no desea utilizar SECRET_HASH , simplemente desmarque Generate client secret al crear una aplicación.
Tuve el mismo problema en el SDK de .net.
Así es como lo resolví, en caso de que alguien más lo necesite:
public static class CognitoHashCalculator { public static string GetSecretHash(string username, string appClientId, string appSecretKey) { var dataString = username + appClientId; var data = Encoding.UTF8.GetBytes(dataString); var key = Encoding.UTF8.GetBytes(appSecretKey); return Convert.ToBase64String(HmacSHA256(data, key)); } public static byte[] HmacSHA256(byte[] data, byte[] key) { using (var shaAlgorithm = new System.Security.Cryptography.HMACSHA256(key)) { var result = shaAlgorithm.ComputeHash(data); return result; } } }Registrarse luego se ve así:
public class CognitoSignUpController { private readonly IAmazonCognitoIdentityProvider _amazonCognitoIdentityProvider; public CognitoSignUpController(IAmazonCognitoIdentityProvider amazonCognitoIdentityProvider) { _amazonCognitoIdentityProvider = amazonCognitoIdentityProvider; } public async Task<bool> SignUpAsync(string userName, string password, string email) { try { var request = CreateSignUpRequest(userName, password, email); var authResp = await _amazonCognitoIdentityProvider.SignUpAsync(request); return true; } catch { return false; } } private static SignUpRequest CreateSignUpRequest(string userName, string password, string email) { var clientId = ConfigurationManager.AppSettings["ClientId"]; var clientSecretId = ConfigurationManager.AppSettings["ClientSecretId"]; var request = new SignUpRequest { ClientId = clientId, SecretHash = CognitoHashCalculator.GetSecretHash(userName, clientId, clientSecretId), Username = userName, Password = password, }; request.UserAttributes.Add("email", email); return request; } }Según los documentos: http://docs.aws.amazon.com/cognito/latest/developerguide/setting-up-the-javascript-sdk.html
El SDK de Javascript no admite aplicaciones con un secreto de cliente.
Las instrucciones ahora indican que debe desmarcar "Generar secreto de cliente" al crear la aplicación para el grupo de usuarios.
En Java podrías usar este código:
private String getSecretHash(String email, String appClientId, String appSecretKey) throws Exception { byte[] data = (email + appClientId).getBytes("UTF-8"); byte[] key = appSecretKey.getBytes("UTF-8"); return Base64.encodeAsString(HmacSHA256(data, key)); } static byte[] HmacSHA256(byte[] data, byte[] key) throws Exception { String algorithm = "HmacSHA256"; Mac mac = Mac.getInstance(algorithm); mac.init(new SecretKeySpec(key, algorithm)); return mac.doFinal(data); }Para cualquier persona interesada en usar AWS Lambda para registrar a un usuario usando el SDK de AWS JS, estos son los pasos que hice:
Cree otra función lambda en python para generar la clave:
import hashlib import hmac import base64 secretKey = "key" clientId = "clientid" digest = hmac.new(secretKey, msg=username + clientId, digestmod=hashlib.sha256 ).digest() signature = base64.b64encode(digest).decode()Llame a la función a través de la función nodeJS en AWS. La firma actuó como el hash secreto para Cognito.
Nota: La respuesta se basa en gran medida en la respuesta de George Campbell en el siguiente enlace: Cálculo de un hash SHA con una cadena + clave secreta en python
este es un código php de muestra que uso para generar el hash secreto
<?php $userId = "aaa"; $clientId = "bbb"; $clientSecret = "ccc"; $s = hash_hmac('sha256', $userId.$clientId, $clientSecret, true); echo base64_encode($s); ?>en este caso el resultado es:
DdSuILDJ2V84zfOChcn6TfgmlfnHsUYq0J6c01QV43I=Parece que actualmente AWS Cognito no maneja perfectamente el secreto del cliente. Funcionará en un futuro próximo, pero por ahora todavía es una versión beta.
Para mí, funciona bien para una aplicación sin un secreto de cliente, pero falla para una aplicación con un secreto de cliente.
Entonces, en su grupo de usuarios, intente crear una nueva aplicación sin generar un secreto de cliente. Luego use esa aplicación para registrar un nuevo usuario o para confirmar el registro.
Autenticación de Cognito
Error: el cliente de la aplicación no está configurado para el secreto, pero se recibió un hash secreto
Proporcionar secretKey como cero funcionó para mí. Las credenciales proporcionadas incluyen: -
AWSCognitoUserPoolsSignInProviderKey (Id. de clave de acceso)
// setup service configuration let serviceConfiguration = AWSServiceConfiguration(region: CognitoIdentityUserPoolRegion, credentialsProvider: nil) // create pool configuration let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoIdentityUserPoolAppClientId, clientSecret: nil, poolId: CognitoIdentityUserPoolId) // initialize user pool client AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)Todo lo anterior funciona con el ejemplo de código vinculado a continuación.
Código de muestra de AWS: https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift
Avísame si eso no funciona para ti.
Aquí está mi comando 1, y funciona (Confirmado :))
EMAIL="EMAIL@HERE.com" \ CLIENT_ID="[CLIENT_ID]" \ CLIENT_SECRET="[CLIENT_ID]" \ && SECRET_HASH=$(echo -n "${EMAIL}${CLIENT_ID}" | openssl dgst -sha256 -hmac "${CLIENT_SECRET}" | xxd -r -p | openssl base64) \ && aws cognito-idp ... --secret-hash "${SECRET_HASH}"Lo siguiente parece funcionar con .NET ahora, para páginas asp.net que usan Alexa Skills SDK para .NET de Time Heur
Inyectar dependencia
private readonly CognitoUserManager<CognitoUser> _userManager; public RegisterModel( UserManager<CognitoUser> userManager, ) _userManager = userManager as CognitoUserManager<CognitoUser> as CognitoUserManager<CognitoUser>;Luego asigna un hash
var user = _pool.GetUser(Input.UserName); _userManager.PasswordHasher.HashPassword(user,Input.Password); var result = await _userManager.CreateAsync(user, Input.Password);Una solución rápida para la declaración del problema mencionado anteriormente sería eliminar el "Cliente de aplicación" existente y crear uno nuevo con Generar secreto de cliente sin marcar
Nota: no olvide cambiar la cadena del cliente de la aplicación en el código.
Esta solución funciona en marzo de 2021:
En caso de que esté trabajando con un cliente que tiene generado tanto "client_secret" como "client_id", en lugar de calcular SECRET_HASH y proporcionarlo a la función como se especifica en los documentos de AWS , pase "client_secret" .
Nota: estaba tratando de generar nuevos tokens a partir del token de actualización.
let result = await cognitoIdentityServiceProvidor .initiateAuth({ AuthFlow: "REFRESH_TOKEN", ClientId: clientId, AuthParameters: { REFRESH_TOKEN: refresh_token, SECRET_HASH: clientSecret, }, }) .promise();¡Es absurdo, pero funciona!
Solución NodeJS:
Calcule el hash secreto para autenticar la acción:
import * as crypto from 'crypto'; const secretHash = crypto .createHmac('SHA256', clientSecret) .update(email + clientId) .digest('base64');Calcule el hash secreto para la acción del token de actualización:
import * as crypto from 'crypto'; const secretHash = crypto .createHmac('SHA256', clientSecret) .update(sub + clientId) .digest('base64');El objeto de parámetro se ve así:
const authenticateParams = { ClientId: clientId, UserPoolId: poolId, AuthFlow: CognitoAuthFlow.ADMIN_NO_SRP_AUTH, AuthParameters: { PASSWORD: password, USERNAME: email, SECRET_HASH: secretHash, }, }; const refreshTokenParams = { ClientId: clientId, UserPoolId: poolId, AuthFlow: CognitoAuthFlow.REFRESH_TOKEN_AUTH, AuthParameters: { REFRESH_TOKEN: refreshToken, SECRET_HASH: secretHash, }, };Uso:
import * as CognitoIdentityProvider from 'aws-sdk/clients/cognitoidentityserviceprovider'; const provider = new CognitoIdentityProvider({ region }); provider.adminInitiateAuth(params).promise(); // authenticateParams or refreshTokenParams, return a promise object.