I am using AWS cognito pool migration using Lambda function with cognito execution role
Following is my new pool app client setting
or
AWS doc says
User migration authentication flow A user migration Lambda trigger
allows easy migration of users from a legacy user management system
into your user pool. To avoid making your users reset their passwords
during user migration, choose the USER_PASSWORD_AUTH authentication
flow. This flow sends your users' passwords to the service over an
encrypted SSL connection during authentication.
When you have completed migrating all your users, we recommend
switching flows to the more secure SRP flow. The SRP flow does not
send any passwords over the network.
I have created lambda function with role "AmazonCognitoPowerUser"
async function authenticateUser(cognitoISP: CognitoIdentityServiceProvider, username: string, password: string): Promise<User | undefined> {
console.log(`authenticateUser: user='${username}'`);
const params: AdminInitiateAuthRequest = {
AuthFlow: 'ADMIN_USER_PASSWORD_AUTH',
AuthParameters: {
PASSWORD: password,
USERNAME: username,
},
ClientId: OLD_CLIENT_ID,
UserPoolId: OLD_USER_POOL_ID,
};
const cognitoResponse = await cognitoISP.adminInitiateAuth(params).promise();
const awsError: AWSError = cognitoResponse as any as AWSError;
if (awsError.code && awsError.message) {
console.log(`authenticateUser: error ${JSON.stringify(awsError)}`);
return undefined;
}
console.log(`authenticateUser: found ${JSON.stringify(cognitoResponse)}`);
return lookupUser(cognitoISP, username);
}
async function lookupUser(cognitoISP: CognitoIdentityServiceProvider, username: string): Promise<User | undefined> {
console.log(`lookupUser: user='${username}'`);
const params = {
UserPoolId: OLD_USER_POOL_ID,
Username: username,
};
const cognitoResponse = await cognitoISP.adminGetUser(params).promise();
const awsError: AWSError = cognitoResponse as any as AWSError;
if (awsError.code && awsError.message) {
console.log(`lookupUser: error ${JSON.stringify(awsError)}`);
return undefined;
}
console.log(`lookupUser: found ${JSON.stringify(cognitoResponse)}`);
const userAttributes = cognitoResponse.UserAttributes ? cognitoResponse.UserAttributes.reduce((acc, entry) => ({
...acc,
[entry.Name]: entry.Value,
}), {} as {[key: string]: string | undefined}) : {};
const user: User = {
userAttributes,
userName: cognitoResponse.Username,
};
console.log(`lookupUser: response ${JSON.stringify(user)}`);
return user;
}
async function onUserMigrationAuthentication(cognitoISP: CognitoIdentityServiceProvider, event: CognitoUserPoolTriggerEvent) {
// authenticate the user with your existing user directory service
const user = await authenticateUser(cognitoISP, event.userName!, event.request.password!);
if (!user) {
throw new Error('Bad credentials');
}
event.response.userAttributes = {
// old_username: user.userName,
// 'custom:tenant': user.userAttributes['custom:tenant'],
email: user.userAttributes.email!,
email_verified: 'true',
preferred_username: user.userAttributes.preferred_username!,
};
event.response.finalUserStatus = 'CONFIRMED';
event.response.messageAction = 'SUPPRESS';
console.log(`Authentication - response: ${JSON.stringify(event.response)}`);
return event;
}
async function onUserMigrationForgotPassword(cognitoISP: CognitoIdentityServiceProvider, event: CognitoUserPoolTriggerEvent) {
// Lookup the user in your existing user directory service
const user = await lookupUser(cognitoISP, event.userName!);
if (!user) {
throw new Error('Bad credentials');
}
event.response.userAttributes = {
// old_username: user.userName,
// 'custom:tenant': user.userAttributes['custom:tenant'],
email: user.userAttributes.email!,
email_verified: 'true',
preferred_username: user.userAttributes.preferred_username!,
};
event.response.messageAction = 'SUPPRESS';
console.log(`Forgot password - response: ${JSON.stringify(event.response)}`);
return event;
}
export const handler = async (event: CognitoUserPoolTriggerEvent, context: Context): Promise<CognitoUserPoolTriggerEvent> => {
const options: CognitoIdentityServiceProvider.Types.ClientConfiguration = {
region: OLD_USER_POOL_REGION,
};
if (OLD_ROLE_ARN) {
options.credentials = new ChainableTemporaryCredentials({
params: {
ExternalId: OLD_EXTERNAL_ID,
RoleArn: OLD_ROLE_ARN,
RoleSessionName: context.awsRequestId,
},
});
}
const cognitoIdentityServiceProvider = new CognitoIdentityServiceProvider(options);
switch (event.triggerSource) {
case 'UserMigration_Authentication':
return onUserMigrationAuthentication(cognitoIdentityServiceProvider, event);
case 'UserMigration_ForgotPassword':
return onUserMigrationForgotPassword(cognitoIdentityServiceProvider, event);
default:
throw new Error(`Bad triggerSource ${event.triggerSource}`);
}
}
and added trigger into new pool,
After many attempt Lambda trigger is not working on login always getting error .
{__type: "NotAuthorizedException", message: "Incorrect username or password."}
message: "Incorrect username or password."
__type: "NotAuthorizedException"
Though its working fine if we use forget passwords flow after reset password user migrated to new pool
https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html
UPDATED :
When directly run test on lambda using following json
{
"version": "1",
"triggerSource": "UserMigration_Authentication",
"region": "ap-south-1",
"userPoolId": "ap-XXXXXXXXX2",
"userName": "vaquar.test#gmail.com",
"callerContext": {
"awsSdkVersion": "aws-sdk-unknown-unknown",
"clientId": "1XXXXXXXXXXXXXXXXXXfgk"
},
"request": {
"password": "vkhan",
"validationData": null,
"userAttributes": null
},
"response": {
"userAttributes": null,
"forceAliasCreation": null,
"finalUserStatus": null,
"messageAction": null,
"desiredDeliveryMediums": null
}
}
Then getting following response and user migrated into new pool means we have issue in trigger during login.
INFO Authentication - response: {"userAttributes":{"email":"vaquar.test#gmail.com","email_verified":"true"},"forceAliasCreation":null,"finalUserStatus":"CONFIRMED","messageAction":"SUPPRESS","desiredDeliveryMediums":null}
With Lambda trigger, Cognito service invokes Lambda function. So Cognito will require permission to invoke Lambda function. How are you configuring the Lambda trigger on your userpool? If you are using AWS Cognito console, the permission should be set automatically.
You may also verify if the user already exists in the new userpool that could cause such behavior.
Please verify that your application is using OAUTH flow USER_PASSWORD instead of the default USER_SRP_AUTH.
For reference: link
I'm trying to use AWS world wide messaging service using C#/.Net Core.
However I do not receive the message in my phone number. Below is the code:
public static async Task<PublishResponse> sendSMS()
{
string accessKey = "my Key";
string secretAccessKey = "my secret key";
var client = new AmazonSimpleNotificationServiceClient(accessKey,
secretAccessKey, RegionEndpoint.USEast1);
string phoneNumber = "my number";
PublishRequest req = new PublishRequest();
req.Message = "Hellloooo from core";
req.PhoneNumber = "+2" + phoneNumber;
PublishResponse res = await client.PublishAsync(req);
return res;
}
And I invoke this method in the main function:
public static void Main(string[] args)
{
var respond = sendSMS();
}
I appreciate if anyone could help me with this. thanks in advance
public static async Task<PublishResponse> SendMessageToMobileAsync(string countryCode, string mobileNumber, string message)
{
var accessKey = "xxx";
var secretKey = "xxx";
var client = new AmazonSimpleNotificationServiceClient(accessKey, secretKey, RegionEndpoint.USEast1);
var messageAttributes = new Dictionary<string, MessageAttributeValue>();
var smsType = new MessageAttributeValue
{
DataType = "String",
StringValue = "Transactional"
};
messageAttributes.Add("AWS.SNS.SMS.SMSType", smsType);
PublishRequest request = new PublishRequest
{
Message = message,
PhoneNumber = countryCode + mobileNumber,
MessageAttributes = messageAttributes
};
return await client.PublishAsync(request);
}
the following function worked for me.
make sure that the account liked to credential you provided has SNS full access in AWS IAM Console
public static async Task SendSMS(AWSCredentials basicCred, string phoneNum, string message, string smsType= "Promotional")
{
AmazonSimpleNotificationServiceClient snsClient = new AmazonSimpleNotificationServiceClient(basicCred, Amazon.RegionEndpoint.APSoutheast2);
PublishRequest pubRequest = new PublishRequest();
pubRequest.Message = message;
// add optional MessageAttributes, for example:
pubRequest.MessageAttributes.Add("AWS.SNS.SMS.SenderID", new MessageAttributeValue
{ StringValue = "SSystems", DataType = "String" });
pubRequest.MessageAttributes.Add("AWS.SNS.SMS.MaxPrice", new MessageAttributeValue
{ StringValue = "0.50", DataType = "Number" });
pubRequest.PhoneNumber = phoneNum;
pubRequest.MessageAttributes.Add("AWS.SNS.SMS.SMSType", new MessageAttributeValue
{ StringValue = smsType, DataType = "String" });
PublishResponse pubResponse = await snsClient.PublishAsync(pubRequest);
Console.WriteLine(pubResponse.MessageId);
Console.ReadLine();
}
For new Comers
If you are using Aws toolkit for visual studio as I suppose
Nuget Packages:
AWSSDK.CORE
AWSSDK.Extensions.NETCore.Setup
AWSSDK.SimpleNotificationService
In your program.cs (.net core 6)
using Amazon.SimpleNotificationService;
and add your extensions
builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions());
builder.Services.AddAWSService<IAmazonSimpleNotificationService>(builder.Configuration.GetAWSOptions());
builder.Services.AddSingleton<ISMSSender, SMSSender>();
In appsettings
"AWS": {
"Profile": "aws toolkit profile name",
"Region": "aws region"
}
an ISMSSender interface for dependency injection:
public interface ISMSSender
{
Task<PublishResponse> SendSMSAsync(string phone, string subject, string Message);
}
Lastly your implementation:
public class SMSSender : ISMSSender
{
IAmazonSimpleNotificationService _SES;
public SMSSender(IAmazonSimpleNotificationService SES)
{
_SES = SES;
}
public Task<PublishResponse> SendSMSAsync(string phone, string subject, string Message)
{
var messageAttributes = new Dictionary<string, MessageAttributeValue>();
MessageAttributeValue senderID = new MessageAttributeValue();
senderID.DataType = "String";
senderID.StringValue = "sendername";
MessageAttributeValue sMSType = new MessageAttributeValue();
sMSType.DataType = "String";
sMSType.StringValue = "Transactional";//or Promotional according to AWS setup you choosed
MessageAttributeValue maxPrice = new MessageAttributeValue();
maxPrice.DataType = "Number";
maxPrice.StringValue = "0.5";
messageAttributes.Add("AWS.SNS.SMS.SenderID", senderID);
messageAttributes.Add("AWS.SNS.SMS.SMSType", sMSType);
messageAttributes.Add("AWS.SNS.SMS.MaxPrice", maxPrice);
var sendRequest = new PublishRequest()
{
Subject = subject,
Message = Message,
PhoneNumber =phone,
MessageAttributes = messageAttributes
};
try
{
Console.WriteLine("Sending SMS using AWS SES...");
var response = _SES.PublishAsync(sendRequest);
Console.WriteLine("The SMS was sent successfully.");
return response;
}
catch (Exception ex)
{
Console.WriteLine("The SMS was not sent.");
Console.WriteLine("Error message: " + ex.Message);
}
throw new NotImplementedException();
}
}
For usage use dependency injection into your Controller or class
private readonly ISMSSender _smsSender;
public MyController(ISMSSender smsSender)
{
_smsSender = smsSender;
}
and then Send Sms:
try
{
var result = await _smsSender.SendSMSAsync(
phoneNumber,
"Title",
"Message");
}
catch (Exception)
{
//handle failure
}
In order to send sms from your Ec2 or elastic Beanstalk app go to IAM roles and for role aws-elasticbeanstalk-ec2-role add permission AWSElasticBeanstalkRoleSNS and for role aws-elasticbeanstalk-service-role add permission AmazonSNSFullAccess
I am stuck at "Amazon Cognito Identity user pools" process.
I tried all possible codes for authenticating user in cognito userpools. But I always get error saying "Error: Unable to verify secret hash for client 4b*******fd".
Here is code:
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);
});
It seems that currently AWS Cognito doesn't handle client secret perfectly. It will work in the near future but as for now it is still a beta version.
For me it is working fine for an app without a client secret but fails for an app with a client secret.
So in your user pool try to create a new app without generating a client secret. Then use that app to signup a new user or to confirm registration.
According to the Docs: http://docs.aws.amazon.com/cognito/latest/developerguide/setting-up-the-javascript-sdk.html
The Javascript SDK doesn't support Apps with a Client Secret.
The instructions now state that you need to uncheck the "Generate Client Secret" when creating the app for the User Pool.
This might be a fews years late but just uncheck the "Generate client secret" option" and it will work for your web clients.
Since everyone else has posted their language, here's node (and it works in the browser with browserify-crypto, automatically used if you use webpack or browserify):
const crypto = require('crypto');
...
crypto.createHmac('SHA256', clientSecret)
.update(username + clientId)
.digest('base64')
I had the same problem in the .net SDK.
Here's how I solved in, in case anyone else needs it:
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;
}
}
}
Signing up then looks like this:
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;
}
}
Amazon mention how Computing SecretHash Values for Amazon Cognito in their documentation with Java application code. Here this code works with boto 3 Python SDK.
You can find your App clients in left side menu under General settings. Get those App client id and App client secret to create SECRET_HASH. For your better understand I commented out all the outputs of each and every line.
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=
In the boto 3 documentation, we can see lot of time ask about SECRET_HASH. So above code lines help you to create this SECRET_HASH.
If you don't want to use SECRET_HASH just uncheck Generate client secret when creating an app.
For anybody interested in using AWS Lambda to sign up a user using the AWS JS SDK, these are the steps I did:
Create another lambda function in python to generate the key:
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()
Call the function through the nodeJS function in AWS. The signature acted as the secret hash for Cognito
Note: The answer is based heavily off George Campbell's answer in the following link: Calculating a SHA hash with a string + secret key in python
Solution for golang. Seems like this should be added to the 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))
}
Solution for NodeJS with SecretHash
It seems silly that AWS removed the secret key from the SDK as it will not be exposed in NodeJS.
I got it working in NodeJS by intercepting fetch and adding in the hashed key using #Simon Buchan's answer.
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 (Forked and edited for NodeJS from Fork of 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,
}
A quick fix for the above mentioned problem statement would be to delete the existing "App Client" and crate a new one with unchecked Generate client secret
Note : Don't forget to change the app client string in the code.
In Java you could use this code:
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);
}
this is a sample php code that I use to generate the secret hash
<?php
$userId = "aaa";
$clientId = "bbb";
$clientSecret = "ccc";
$s = hash_hmac('sha256', $userId.$clientId, $clientSecret, true);
echo base64_encode($s);
?>
in this case the result is:
DdSuILDJ2V84zfOChcn6TfgmlfnHsUYq0J6c01QV43I=
for JAVA and .NET you need to pass the secret has in the auth parameters with the name 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
};
And it should work.
The crypto package for javascript is deprecated so using crypto-js:
import CryptoJS from 'crypto-js';
import Base64 from 'crypto-js/enc-base64';
const secretHash = Base64.stringify(CryptoJS.HmacSHA256(username + clientId, clientSecret));
Remeber to run npm install #types/crypto-js crypto-js before
C++ with the Qt Framework
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();
};
Here is my 1 command, and it works (Confirmed :))
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}"
This solution works in March 2021:
In case you're working with a client which has both "client_secret" and "client_id" generated, instead of calculating the SECRET_HASH and providing it to the function as specified in AWS docs, pass the "client_secret".
Note: I was trying to generate new tokens from the refresh token.
let result = await cognitoIdentityServiceProvidor
.initiateAuth({
AuthFlow: "REFRESH_TOKEN",
ClientId: clientId,
AuthParameters: {
REFRESH_TOKEN: refresh_token,
SECRET_HASH: clientSecret,
},
})
.promise();
It's absurd, but it works!
There might be a more compact version, but this works for Ruby, specifically in Ruby on Rails without having to require anything:
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))
NodeJS solution:
Compute secret hash for authenticating action:
import * as crypto from 'crypto';
const secretHash = crypto
.createHmac('SHA256', clientSecret)
.update(email + clientId)
.digest('base64');
Compute secret hash for refresh token action:
import * as crypto from 'crypto';
const secretHash = crypto
.createHmac('SHA256', clientSecret)
.update(sub + clientId)
.digest('base64');
The parameter object looks like this:
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,
},
};
Usage:
import * as CognitoIdentityProvider from 'aws-sdk/clients/cognitoidentityserviceprovider';
const provider = new CognitoIdentityProvider({ region });
provider.adminInitiateAuth(params).promise(); // authenticateParams or refreshTokenParams, return a promise object.
Cognito Authentication
Error: App client is not configured for secret but secret hash was received
Providing secretKey as nil worked for me. Credentials provided include :-
CognitoIdentityUserPoolRegion (region)
CognitoIdentityUserPoolId
(userPoolId)
CognitoIdentityUserPoolAppClientId (ClientId)
AWSCognitoUserPoolsSignInProviderKey (AccessKeyId)
// 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)
All above things work with below linked code sample.
AWS Sample code : https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift
Let me know if that doesn't work for you.
The below seems to work with .NET now, for asp.net pages using the Alexa Skills SDK for .NET by Time Heur
Inject dependency
private readonly CognitoUserManager<CognitoUser> _userManager;
public RegisterModel(
UserManager<CognitoUser> userManager,
)
_userManager = userManager as CognitoUserManager<CognitoUser> as CognitoUserManager<CognitoUser>;
Then assign a hash
var user = _pool.GetUser(Input.UserName);
_userManager.PasswordHasher.HashPassword(user,Input.Password);
var result = await _userManager.CreateAsync(user, Input.Password);
I saw a .NET one suggested here, but here is the variation that worked for me since I couldn't find access to "EncryptionHelper.GetSecretHash":
private string GetHMAC(string text, string key)
{
// TODO: null checks or whatever you want on your inputs...
using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
var hash = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(text));
return Convert.ToBase64String(hash);
}
}
And you call this for something like a sign up request as follows:
SignUpRequest signUpRequest = new SignUpRequest
{
ClientId = "<your_client_app_id>",
Password = "<the-password-your-user-wanted>",
Username = "<the-username-your-user-wanted",
};
// TODO: add whatever else you need to on your sign up request (like email, phone number etc...)
// and the magic line right here:
signUpRequest.SecretHash = GetHMAC(
signUpRequest.Username + "<your_client_app_id>",
"<your_client_app_secret>");
SignUpResponse response = await _provider.SignUpAsync(signUpRequest);
For me this worked like a charm. I originally was putting the client app secret directly assigned to this "SecretHash" property, but from scanning the rest of the answers here, I realized I truly needed to hash some data using that key as an input to the hash.