QuickSightUserNotFoundException When getting a AWS Quicksight embed URL with cognito user - amazon-web-services

I'm trying to get a Quicksight embed URL in a lambda function,
The lambda function receives a jwtToken from the frontend created on a react app using aws amplify, all the cognito setup works well (userpool and identity pool), the user receives the role "arn:aws:iam::xx:role/Cognito_qa1_Admin" when logging in,
The role has permissions to quicksight:registerUser and quicksight:getDashboardEmbedUrl
var cognitoIdentity = new AWS.CognitoIdentity();
var params = {
IdentityPoolId: "eu-west-2:xxx-291d-xx-b9a7-8b27c73c796c", // your identity pool id here
Logins: {
// your logins here
"cognito-idp.eu-west-2.amazonaws.com/eu-west-2_xxx": event.jwtToken,
},
};
// Get cognito identity from jwtToken
cognitoIdentity.getId(params, function (err, data) {
if (err) {
return callback(err);
}
var roleArn = "arn:aws:iam::xx:role/Cognito_qa1_Admin"; // your cognito authenticated role arn here
data.Logins = params.Logins;
// Get credentials for the identity (it also does the AssumeRoleWithWebIdentity)
cognitoIdentity.getCredentialsForIdentity(data, function (err, data) {
console.log(data);
if (err) {
return callback(err);
}
// update credentials with web identity ones
AWS.config.update({
region: "eu-west-2",
accessKeyId: data.Credentials.AccessKeyId,
secretAccessKey: data.Credentials.SecretKey,
sessionToken: data.Credentials.SessionToken,
expiration: data.Credentials.Expiration,
});
const quicksight = new AWS.QuickSight();
var getDashboardParams = {
AwsAccountId: "xx",
DashboardId: "a048efb6-3d3c-xx-8920-xxx",
IdentityType: "IAM",
ResetDisabled: false,
SessionLifetimeInMinutes: 100,
UndoRedoDisabled: false,
};
var registerUserParams = {
AwsAccountId: "xxx",
Email: event.userEmail,
IdentityType: "IAM",
Namespace: "default",
UserRole: "READER",
IamArn: roleArn,
SessionName: event.payloadSub,
};
// register user, this one works well
quicksight.registerUser(registerUserParams, function (err, data) {
if (err) {
if (err.code !== "ResourceExistsException") {
console.log("error registering user");
return callback(err);
}
console.log("user already exists");
}
console.log("User registration data", data);
// Get dashboard url, this is the one failing with QuickSightUserNotFoundException
quicksight.getDashboardEmbedUrl(getDashboardParams, function (
err,
data
) {
if (err) {
console.log("getDashboardEmbedUrl error", err);
return callback(err);
}
callback(null, data);
});
});
});
});
Everything goes smooth, the credentials for the web identity are retrieved and set to the config, the registerUser call registers the user (or returns user already exists error)
But the getDashboardEmbedUrl fails with QuickSightUserNotFoundException: Could not find user information in QuickSight
If I call sts.getCallerIdentity after setting the credentials I get this
{
ResponseMetadata: { RequestId: 'd5cb26f1-f2f5-4148-87e5-74d6c998fb91' },
UserId: 'AROAU63RLM5WIRTFDRETQ:CognitoIdentityCredentials',
Account: 'xxx',
Arn: 'arn:aws:sts::xxx:assumed-role/Cognito_qa1_Admin/CognitoIdentityCredentials'
}
Any idea? Thanks a lot in advance

When registering the user the IdentityType has to be IAM but for getDashboardEmbedUrl it has to be type QUICKSIGHT and you need to pass UserArn which you can find in the response from registerUser

Related

How can I use Postman to work with AWS Cognito?

I'll start by saying that I have everything working, I'm just not sure how to put all the pieces together so that I can use Postman to test/work with my API.
I am using NestJS as my backend, and am using AWS Cognito to provide an authentication mechanism.
I'm using adminCreateUser to register users as I want the new user to be forced to reset their password the first time they attempt to log in. I'm successfully getting an email with an email & temporary password. I know the next step is to incorporate completeNewPasswordChallenge within my authentication method, but that's where I'm struggling.
Using postman, sending a POST request to my /signup route ressolves to this method in my cognitoService
private async adminCreateUser(
createUserDto: CreateUserDto,
): Promise<AdminCreateUserResponse> {
const { givenName, familyName, email, phone, role } = createUserDto;
const poolData = {
UserPoolId: this.userPool.getUserPoolId(),
ClientId: this.userPool.getClientId(),
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
try {
const cognito = new AWS.CognitoIdentityServiceProvider();
return await new Promise(function (resolve, reject) {
cognito.adminCreateUser(
{
UserPoolId: userPool.getUserPoolId(),
Username: email,
UserAttributes: [
{
Name: 'given_name',
Value: foo,
},
...
],
DesiredDeliveryMediums: ['EMAIL'],
},
function (error, adminCreateUserResponse) {
if (error) {
reject(error);
}
resolve(adminCreateUserResponse);
},
);
});
} catch (error) {
throw new BadRequestException(error.message);
}
}
From that I am successfully getting an email:
Your username is email#example.com and temporary password is YWE#8CEu
From there I have another POST route of /signin that resolves to this method:
async authenticateUser(emailPasswordDto: EmailPasswordDto) {
const { email, password } = emailPasswordDto;
const authenticationDetails =
new AmazonCognitoIdentity.AuthenticationDetails({
Username: email,
Password: password,
});
const userData = {
Username: email,
Pool: this.userPool,
};
this.cognitoUser = new CognitoUser(userData);
return new Promise((resolve, reject) => {
this.cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (cognitoUserSession) {
//
},
onFailure: function (err) {
//
},
mfaRequired: function (codeDeliveryDetails) {
//
},
newPasswordRequired: (userAttributes, requiredAttributes) => {
// I'm getting here & don't know how to provide a new password.
},
});
});
}
If I log out userAttributes, this is what I'm seeing which looks good:
{
"email_verified": "false",
"phone_number_verified": "false",
"phone_number": "+12345678900",
"given_name": "First",
"family_name": "Last",
"email": "example#email.com"
}
I have another method in my service, but I'm not sure how to get from A to B.
For example, I don't know how to get to this method from within the previous callback.
async completeNewPasswordChallenge(
completePasswordChallengeDto: CompletePasswordChallengeDto,
) {
const { email, newPassword } = completePasswordChallengeDto;
// get the user
// get the user's attributes
// delete userAttributes.email_verified
// ??
this.cognitoUser.completeNewPasswordChallenge(newPassword, null, null);
}
How can I use Postman to work through the entire auth process? I thought perhaps I could just resolve to forgotPassword, but I think I need to have the email verified before cognito will send that email.

How can I tell a AWS Lambda Function to redirect based off the environment?

So currently I'm using AWS Lambdas as triggers for my Cognito Passwordless authentication. For the create_auth_challenge trigger I have an AWS Lambda function that sends a link to the user to redirect them somewhere based on the environment. The only problem is that I'm not sure how to dynamically tell the function which environment the auth request is coming from.
AWS.config.update({ region: 'us-west-2' });
const SES = new AWS.SES();
exports.handler = async (event,context) => {
console.log("HERE: ", event,context);
let secretLoginCode;
if (!event.request.session || !event.request.session.length) {
// Generate a new secret login code and send it to the user
secretLoginCode = Date.now().toString().slice(-4);
try {
if ('email' in event.request.userAttributes) {
const emailResult = await SES.sendEmail({
Destination: { ToAddresses: [event.request.userAttributes.email] },
Message: {
Body: {
Html: {
Charset: 'UTF-8',
Data: `<html><body><p>This is your secret login code:</p>
<h3>Your magic link: ${INSERT ENVIRONMENT HERE}/api/auth/cognito/verify?email=${event.request.userAttributes.email}&code=${secretLoginCode}</h3></body></html>`
},
Text: {
Charset: 'UTF-8',
Data: `Your magic link: ${INSERT ENVIRONMENT HERE}/api/auth/cognito/verify?email=${event.request.userAttributes.email}&code=${secretLoginCode}`
}
},
Subject: {
Charset: 'UTF-8',
Data: 'Your magic link'
}
},
Source: 'Company <no-reply#company.com>'
}).promise();
}
} catch (error) {
console.log(error)
}
} else {
// re-use code generated in previous challenge
const previousChallenge = event.request.session.slice(-1)[0];
secretLoginCode = previousChallenge.challengeMetadata.match(/CODE-(\d*)/)[1];
}
// Add the secret login code to the private challenge parameters
// so it can be verified by the "Verify Auth Challenge Response" trigger
event.response.privateChallengeParameters = { secretLoginCode };
// Add the secret login code to the session so it is available
// in a next invocation of the "Create Auth Challenge" trigger
event.response.challengeMetadata = `CODE-${secretLoginCode}`;
return event;
};```
This is a magic link authentication by the way.

AWS Cognito: Best practice to handle same user (with same email address) signing in from different identity providers (Google, Facebook)

When signing in a user with the same email address through the Google and Facebook identity providers, AWS Cognito creates multiple entries in the user pool, one entry per identity provider used:
I have used the example code provided in this tutorial to set up AWS Cognito: The Complete Guide to User Authentication with the Amplify Framework
How can I create just one user instead of multiple users?
Is it possible to have AWS Cognito automatically combine (federate) the entries
from multiple providers into one entry or should AWS Lambda functions be used to accomplish this?
Yes. You can do it by using AdminLinkProviderForUser https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminLinkProviderForUser.html
The idea is:
In PreSignUp lambda hook, we Link Provider to User if User already signed up. E.g:
import CognitoIdentityServiceProvider from 'aws-sdk/clients/cognitoidentityserviceprovider'
const cognitoIdp = new CognitoIdentityServiceProvider()
const getUserByEmail = async (userPoolId, email) => {
const params = {
UserPoolId: userPoolId,
Filter: `email = "${email}"`
}
return cognitoIdp.listUsers(params).promise()
}
const linkProviderToUser = async (username, userPoolId, providerName, providerUserId) => {
const params = {
DestinationUser: {
ProviderAttributeValue: username,
ProviderName: 'Cognito'
},
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerUserId,
ProviderName: providerName
},
UserPoolId: userPoolId
}
const result = await (new Promise((resolve, reject) => {
cognitoIdp.adminLinkProviderForUser(params, (err, data) => {
if (err) {
reject(err)
return
}
resolve(data)
})
}))
return result
}
exports.handler = async (event, context, callback) => {
if (event.triggerSource === 'PreSignUp_ExternalProvider') {
const userRs = await getUserByEmail(event.userPoolId, event.request.userAttributes.email)
if (userRs && userRs.Users.length > 0) {
const [ providerName, providerUserId ] = event.userName.split('_') // event userName example: "Facebook_12324325436"
await linkProviderToUser(userRs.Users[0].Username, event.userPoolId, providerName, providerUserId)
} else {
console.log('user not found, skip.')
}
}
return callback(null, event)
}
Then when user use OAuth with Facebook/Google with User Pool, the Pool will return this User linked.
Note: You may see 2 records in User Pool UI, but when access User record detail, They already merged.
I have been fiddling around with the same issue for a bit. Accepted answer sort of works but does not cover all scenarios. The main one is that once the user signs up with the external login, they will never be able to sign up with a username and password. Currently, Cognito does not allow linking Cognito users to external users.
My scenarios are as follows:
Scenarios
When the user signs up with a username password and signs up with an external provider, link them.
When the user signs up with an external provider allow them to signup with a username and password.
Have a common username between all linked users to use it as a unique id in other services.
My proposed solution is to always create the Cognito user first and link all external users to it.
Proposed solution
user signs up with username/password first then with an external user. No dramas, just link the external user with the Cognito user.
user signs up with external user first then wants to sign up with username/password. In this scenario, create a Cognito user first then link the external user to this new Cognito user. If the user tries to signup with a username/password in the future, they will get a user already exists error. In this case, they can use the forgot password flow to recover then log in.
const {
CognitoIdentityServiceProvider
} = require('aws-sdk');
const handler = async event => {
const userPoolId = event.userPoolId;
const trigger = event.triggerSource;
const email = event.request.userAttributes.email;
const givenName = event.request.userAttributes.given_name;
const familyName = event.request.userAttributes.family_name;
const emailVerified = event.request.userAttributes.email_verified;
const identity = event.userName;
const client = new CognitoIdentityServiceProvider();
if (trigger === 'PreSignUp_ExternalProvider') {
await client.listUsers({
UserPoolId: userPoolId,
AttributesToGet: ['email', 'family_name', 'given_name'],
Filter: `email = "${email}"`
})
.promise()
.then(({
Users
}) => Users.sort((a, b) => (a.UserCreateDate > b.UserCreateDate ? 1 : -1)))
.then(users => users.length > 0 ? users[0] : null)
.then(async user => {
// user with username password already exists, do nothing
if (user) {
return user;
}
// user with username password does not exists, create one
const newUser = await client.adminCreateUser({
UserPoolId: userPoolId,
Username: email,
MessageAction: 'SUPPRESS', // dont send email to user
UserAttributes: [{
Name: 'given_name',
Value: givenName
},
{
Name: 'family_name',
Value: familyName
},
{
Name: 'email',
Value: email
},
{
Name: 'email_verified',
Value: emailVerified
}
]
})
.promise();
// gotta set the password, else user wont be able to reset it
await client.adminSetUserPassword({
UserPoolId: userPoolId,
Username: newUser.Username,
Password: '<generate random password>',
Permanent: true
}).promise();
return newUser.Username;
}).then(username => {
// link external user to cognito user
const split = identity.split('_');
const providerValue = split.length > 1 ? split[1] : null;
const provider = ['Google', 'Facebook'].find(
val => split[0].toUpperCase() === val.toUpperCase()
);
if (!provider || !providerValue) {
return Promise.reject(new Error('Invalid external user'));
}
return client.adminLinkProviderForUser({
UserPoolId: userPoolId,
DestinationUser: {
ProviderName: 'Cognito',
ProviderAttributeValue: username
},
SourceUser: {
ProviderName: provider,
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerValue
}
})
.promise()
});
}
return event;
};
module.exports = {
handler
};
The solution I created handles, I think, all cases. It also tackles some common issues with Cognito.
If the user is signing up with an external provider, link them to any existing account, including Cognito (username/password) or external provider account.
When linking to existing accounts, link only to the oldest account. This is important is you have more than 2 login options.
If the user is signing up with Cognito (username/password), if an external provider already exists, reject the signup with a custom error message (because the accounts cannot be linked).
Note that when linking accounts, the Cognito pre-signup trigger returns an "Already found an entry for username" error. Your client should handle this and reattempt authentication, or ask the user to sign in again. More info on this here:
Cognito auth flow fails with "Already found an entry for username Facebook_10155611263153532"
Here is my lambda, executed on the Cognito pre-signup trigger
const AWS = require("aws-sdk");
const cognito = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
function checkForExistingUsers(event, linkToExistingUser) {
console.log("Executing checkForExistingUsers");
var params = {
UserPoolId: event.userPoolId,
AttributesToGet: ['sub', 'email'],
Filter: "email = \"" + event.request.userAttributes.email + "\""
};
return new Promise((resolve, reject) =>
cognito.listUsers(params, (err, result) => {
if (err) {
reject(err);
return;
}
if (result && result.Users && result.Users[0] && result.Users[0].Username && linkToExistingUser) {
console.log("Found existing users: ", result.Users);
if (result.Users.length > 1){
result.Users.sort((a, b) => (a.UserCreateDate > b.UserCreateDate) ? 1 : -1);
console.log("Found more than one existing users. Ordered by createdDate: ", result.Users);
}
linkUser(result.Users[0].Username, event).then(result => {
resolve(result);
})
.catch(error => {
reject(err);
return;
});
} else {
resolve(result);
}
})
);
}
function linkUser(sub, event) {
console.log("Linking user accounts with target sub: " + sub + "and event: ", event);
//By default, assume the existing account is a Cognito username/password
var destinationProvider = "Cognito";
var destinationSub = sub;
//If the existing user is in fact an external user (Xero etc), override the the provider
if (sub.includes("_")) {
destinationProvider = sub.split("_")[0];
destinationSub = sub.split("_")[1];
}
var params = {
DestinationUser: {
ProviderAttributeValue: destinationSub,
ProviderName: destinationProvider
},
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: event.userName.split("_")[1],
ProviderName: event.userName.split("_")[0]
},
UserPoolId: event.userPoolId
};
console.log("Parameters for adminLinkProviderForUser: ", params);
return new Promise((resolve, reject) =>
cognito.adminLinkProviderForUser(params, (err, result) => {
if (err) {
console.log("Error encountered whilst linking users: ", err);
reject(err);
return;
}
console.log("Successfully linked users.");
resolve(result);
})
);
}
console.log(JSON.stringify(event));
if (event.triggerSource == "PreSignUp_SignUp" || event.triggerSource == "PreSignUp_AdminCreateUser") {
checkForExistingUsers(event, false).then(result => {
if (result != null && result.Users != null && result.Users[0] != null) {
console.log("Found at least one existing account with that email address: ", result);
console.log("Rejecting sign-up");
//prevent sign-up
callback("An external provider account alreadys exists for that email address", null);
} else {
//proceed with sign-up
callback(null, event);
}
})
.catch(error => {
console.log("Error checking for existing users: ", error);
//proceed with sign-up
callback(null, event);
});
}
if (event.triggerSource == "PreSignUp_ExternalProvider") {
checkForExistingUsers(event, true).then(result => {
console.log("Completed looking up users and linking them: ", result);
callback(null, event);
})
.catch(error => {
console.log("Error checking for existing users: ", error);
//proceed with sign-up
callback(null, event);
});
}
};
If you want to allow the user to continue login with email & password ("Option 1: User Signs Up with Username and Signs In with Username or Alias)") besides identity provider (google, facebook, etc) then the accepted solution won't be enough as Cognito can only have one email as verified.
I solve this by adding a Post Confirmation trigger which automatically verify user email if needed:
const AWS = require('aws-sdk');
const cognitoIdp = new AWS.CognitoIdentityServiceProvider();
const markUserEmailAsVerified = async (username, userPoolId) => {
console.log('marking email as verified for user with username: ' + username);
const params = {
UserAttributes: [
{
Name: 'email_verified',
Value: 'true'
}
// other user attributes like phone_number or email themselves, etc
],
UserPoolId: userPoolId,
Username: username
};
const result = await new Promise((resolve, reject) => {
cognitoIdp.adminUpdateUserAttributes(params, (err, data) => {
if (err) {
console.log(
'Failed to mark user email as verified with error:\n' +
err +
'\n. Manual action is required to mark user email as verified otherwise he/she cannot login with email & password'
);
reject(err);
return;
}
resolve(data);
});
});
return result;
};
exports.handler = async (event, context, callback) => {
console.log('event data:\n' + JSON.stringify(event));
const isEmailVerified = event.request.userAttributes.email_verified;
if (isEmailVerified === 'false') {
await markUserEmailAsVerified(event.userName, event.userPoolId);
}
return callback(null, event);
};
Note: This doesn't seem standard development or common requirement so take as it.
In aws-sdk-js-v3 I'm using #subash approach. I find that when you make an error callback, no extra user is created. Just the one that you create with your email.
const {
CognitoIdentityProviderClient,
ListUsersCommand,
AdminCreateUserCommand,
AdminLinkProviderForUserCommand,
AdminSetUserPasswordCommand,
} = require('#aws-sdk/client-cognito-identity-provider')
const client = new CognitoIdentityProviderClient({
region: process.env.REGION,
})
const crypto = require("crypto")
exports.handler = async(event, context, callback) => {
try {
const {
triggerSource,
userPoolId,
userName,
request: {
userAttributes: { email, name }
}
} = event
if (triggerSource === 'PreSignUp_ExternalProvider') {
const listParam = {
UserPoolId: userPoolId,
Filter: `email = "${email}"`,
}
const listData = await client.send(new ListUsersCommand(listParam))
let [providerName, providerUserId] = userName.split('_')
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
let linkParam = {
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerUserId,
ProviderName: providerName,
},
UserPoolId: userPoolId,
}
if (listData && listData.Users.length > 0) {
linkParam['DestinationUser'] = {
ProviderAttributeValue: listData.Users[0].Username,
ProviderName: 'Cognito',
}
}
else {
const createParam = {
UserPoolId: userPoolId,
Username: email,
MessageAction: 'SUPPRESS',
UserAttributes: [{
//optional name attribute.
Name: 'name',
Value: name,
}, {
Name: 'email',
Value: email,
}, {
Name: 'email_verified',
Value: 'true',
}],
}
const createData = await client.send(new AdminCreateUserCommand(createParam))
const pwParam = {
UserPoolId: userPoolId,
Username: createData.User.Username,
Password: crypto.randomBytes(40).toString('hex'),
Permanent: true,
}
await client.send(new AdminSetUserPasswordCommand(pwParam))
linkParam['DestinationUser'] = {
ProviderAttributeValue: createData.User.Username,
ProviderName: 'Cognito',
}
}
await client.send(new AdminLinkProviderForUserCommand(linkParam))
//throw error to prevent additional user creation
callback(Error('Social account was set, retry to sign in.'), null)
}
else {
callback(null, event)
}
}
catch (err) {
console.error(err)
}
}
However, it is a bad UX as the first sign in with federated identity will only create the user but not allowing it to authenticate. However, the subsequent sign in with federated identity will show no such issue. Let me know, if you get any other solution for that first sign in.
It's also useful to keep email_verified as true so that user can recover their password. Especially true if you are using aws-amplify authenticator. This should be in your post authentication trigger.
const {
CognitoIdentityProviderClient,
AdminUpdateUserAttributesCommand,
} = require('#aws-sdk/client-cognito-identity-provider')
const client = new CognitoIdentityProviderClient({
region: process.env.REGION,
})
exports.handler = async(event, context, callback) => {
try {
const {
userPoolId,
userName,
request: {
userAttributes: { email_verified }
}
} = event
if (!email_verified) {
const param = {
UserPoolId: userPoolId,
Username: userName,
UserAttributes: [{
Name: 'email_verified',
Value: 'true',
}],
}
await client.send(new AdminUpdateUserAttributesCommand(param))
}
callback(null, event)
}
catch (err) {
console.error(err)
}
}

Token received in lambda function returns Unauthorized in APIG

These are my first steps in AWS in general and Cognito specifically. So please bear with me as I'm a bit confused by all the concepts and documention is not very easy to follow.
So I set up 3 lambda functions, one that creates a user, one that confirms a user and a last one that is supposed to authenticate the user.
The first 2 work fine, my user is created an confirmed. Now I'm stuck with the 3rd one which is supposed to return a token to be used in APIG, where I've set up a simple endpoint with my cognito authorizer.
Every token I get back returns Unauthorized when tested in the APIG/Authorizers/Cognito Pool Authorizers section.
My 'sign in' code is the following:
const AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
AWS.config.apiVersions = {
cognitoidentityserviceprovider: '2016-04-18'
};
AWS.config.region = 'us-east-1'; // Region
/*AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
});*/
var identityId = null;
var params = {
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
IdentityId: identityId,
Logins: {
'login.auth.MYPROJECT': 'MY_USERNAME'
},
TokenDuration: 86400
};
var cognito = new AWS.CognitoIdentity({
region: AWS.config.region
});
cognito.getOpenIdTokenForDeveloperIdentity(params, function(err, data) {
if (err) {
return callback(err);
}
else {
/*AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: params.IdentityPoolId
});*/
AWS.config.credentials.get(function(){
// Credentials will be available when this function is called.
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
callback(null, {
identityId: data.IdentityId,
token: data.Token,
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: sessionToken
});
});
}
});
}
Both token and sessionToken return Unauthorized. Can someone tell me what is missing here?
Much appreciated.
EDIT 2016-11-15
The 'register' lambda code:
const AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
});
var poolData = {
UserPoolId : 'MY_USER_POOL_ID',
ClientId : 'MY_CLIENT_ID'
};
var userPool = new AWS.CognitoIdentityServiceProvider(poolData);
var email = "myemail+" + Math.floor(Math.random() * (100 - 1) + 1) + "#example.com";
var params = {
ClientId: 'MY_CLIENT_ID',
Password: '1234567890',
Username: 'testaccount' + Math.floor(Math.random() * (100 - 1) + 1),
UserAttributes: [
{
Name: 'email',
Value: email
}
]
};
userPool.signUp(params, function(err, result){
if (err) {
console.log(err)
return;
}
callback(null, {
"message": "Hello from Lambda",
"data": result
});
});
};
My 'activate' lambda code is the following:
const AWS = require('aws-sdk');
exports.handler = (event, context, callback) => {
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'MY_IDENTITY_POOL_ID',
});
var poolData = {
UserPoolId : 'MY_USER_POOL_ID',
ClientId : 'MY_CLIENT_ID'
};
var userPool = new AWS.CognitoIdentityServiceProvider(poolData);
var email = "email_address#example.com";
var params = {
ClientId: 'MY_CLIENT_ID',
Username: 'test_username',
ForceAliasCreation: false,
ConfirmationCode: '927000'
};
userPool.confirmSignUp(params, function(err, result){
if (err) {
console.log(err)
return;
}
callback(null, {
"message": "Hello from Lambda",
"data": result
});
});
};
In APIG, I created a Cognito User Pool Authorizer, selected my user pool, gave it a name, and set the identity token source to 'method.request.header.Authorization'.
In my APIG resource under the Method Request, I've set Authorization to my Cognito User Pool Authorizer. Additionally, API Key Required is set to true and I've a couple of keys I was testing with and that caused no issues.
I hope this covers everything.

How to change User Status FORCE_CHANGE_PASSWORD?

Using AWS Cognito, I want to create dummy users for testing purposes.
I then use the AWS Console to create such user, but the user has its status set to FORCE_CHANGE_PASSWORD. With that value, this user cannot be authenticated.
Is there a way to change this status?
UPDATE
Same behavior when creating user from CLI
This has finally been added to AWSCLI: https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-set-user-password.html
You can change a user's password and update status using:
aws cognito-idp admin-set-user-password \
--user-pool-id <your-user-pool-id> \
--username <username> \
--password <password> \
--permanent
Before using this, you may need to update your AWS CLI using:
pip3 install awscli --upgrade
I know it's been a while but thought this might help other people who come across this post.
You can use the AWS CLI to change the users password, however it's a multi step process:
Step 1: Get a session token for the desired user:
aws cognito-idp admin-initiate-auth --user-pool-id %USER POOL ID% --client-id %APP CLIENT ID% --auth-flow ADMIN_NO_SRP_AUTH --auth-parameters USERNAME=%USERS USERNAME%,PASSWORD=%USERS CURRENT PASSWORD%
If this returns an error about Unable to verify secret hash for client, create another app client without a secret and use that client ID.
Step 2: If step 1 is successful, it will respond with the challenge NEW_PASSWORD_REQUIRED, other challenge parameters and the users session key. Then, you can run the second command to issue the challenge response:
aws cognito-idp admin-respond-to-auth-challenge --user-pool-id %USER POOL ID% --client-id %CLIENT ID% --challenge-name NEW_PASSWORD_REQUIRED --challenge-responses NEW_PASSWORD=%DESIRED PASSWORD%,USERNAME=%USERS USERNAME% --session %SESSION KEY FROM PREVIOUS COMMAND with ""%
If you get an error about Invalid attributes given, XXX is missing pass the missing attributes using the format userAttributes.$FIELD_NAME=$VALUE
The above command should return a valid Authentication Result and appropriate Tokens.
Important: For this to work, the Cognito User Pool MUST have an App client configured with ADMIN_NO_SRP_AUTH functionality (Step 5 in this doc).
Just add this code after your onSuccess: function (result) { ... }, within your login function. Your user will then have status CONFIRMED.
newPasswordRequired: function(userAttributes, requiredAttributes) {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// the api doesn't accept this field back
delete userAttributes.email_verified;
// unsure about this field, but I don't send this back
delete userAttributes.phone_number_verified;
// Get these details and call
cognitoUser.completeNewPasswordChallenge(newPassword, userAttributes, this);
}
You can change that user status FORCE_CHANGE_PASSWORD by calling respondToAuthChallenge() on the user like this:
var params = {
ChallengeName: 'NEW_PASSWORD_REQUIRED',
ClientId: 'your_own3j6...0obh',
ChallengeResponses: {
USERNAME: 'user3',
NEW_PASSWORD: 'changed12345'
},
Session: 'xxxxxxxxxxZDMcRu-5u...sCvrmZb6tHY'
};
cognitoidentityserviceprovider.respondToAuthChallenge(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
After this, you'll see in the console that user3 status is CONFIRMED.
Sorry you are having difficulties. We do not have a one step process where you can just create users and authenticate them directly. We might change this in the future such as to allow administrators to set passwords that are directly usable by users. For now, when you create users either using AdminCreateUser or by signing up users with the app, extra steps are required, either forcing users to change the password upon login or having users verify the email or phone number to change the status of the user to CONFIRMED.
not sure if you are still fighting with this but for creating a bunch of test users only, I used the awscli as such:
Use the sign-up subcommand from cognito-idp to create the user
aws cognito-idp sign-up \
--region %aws_project_region% \
--client-id %aws_user_pools_web_client_id% \
--username %email_address% \
--password %password% \
--user-attributes Name=email,Value=%email_address%
Confirm the user using admin-confirm-sign-up
aws cognito-idp admin-confirm-sign-up \
--user-pool-id %aws_user_pools_web_client_id% \
--username %email_address%
UPDATE:
There have been some updates, and Amplify client is no longer needed.
After adminCreateUser(), you can now just use
cisp.adminSetUserPassword({
UserPoolId: pool_id,
Username: login,
Password: password,
Permanent: true
})
[https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminSetUserPassword.html]
this will set the user to "confirmed".
UPDATE:
I am now using this, translated to amplify, inside a NodeJS Lambda:
// enable node-fetch polyfill for Node.js
global.fetch = require("node-fetch").default;
global.navigator = {};
const AWS = require("aws-sdk");
const cisp = new AWS.CognitoIdentityServiceProvider();
const Amplify = require("#aws-amplify/core").default;
const Auth = require("#aws-amplify/auth").default;
...
/*
this_user: {
given_name: string,
password: string,
email: string,
cell: string
}
*/
const create_cognito = (this_user) => {
let this_defaults = {
password_temp: Math.random().toString(36).slice(-8),
password: this_user.password,
region: global._env === "prod" ? production_region : development_region,
UserPoolId:
global._env === "prod"
? production_user_pool
: development_user_pool,
ClientId:
global._env === "prod"
? production_client_id
: development_client_id,
given_name: this_user.given_name,
email: this_user.email,
cell: this_user.cell,
};
// configure Amplify
Amplify.configure({
Auth: {
region: this_defaults.region,
userPoolId: this_defaults.UserPoolId,
userPoolWebClientId: this_defaults.ClientId,
},
});
if (!Auth.configure())
return Promise.reject("could not configure amplify");
return new Promise((resolve, reject) => {
let _result = {};
let this_account = undefined;
let this_account_details = undefined;
// create cognito account
cisp
.adminCreateUser({
UserPoolId: this_defaults.UserPoolId,
Username: this_defaults.given_name,
DesiredDeliveryMediums: ["EMAIL"],
ForceAliasCreation: false,
MessageAction: "SUPPRESS",
TemporaryPassword: this_defaults.password_temp,
UserAttributes: [
{ Name: "given_name", Value: this_defaults.given_name },
{ Name: "email", Value: this_defaults.email },
{ Name: "phone_number", Value: this_defaults.cell },
{ Name: "email_verified", Value: "true" },
],
})
.promise()
.then((user) => {
console.warn(".. create_cognito: create..");
_result.username = user.User.Username;
_result.temporaryPassword = this_defaults.password_temp;
_result.password = this_defaults.password;
// sign into cognito account
return Auth.signIn(_result.username, _result.temporaryPassword);
})
.then((user) => {
console.warn(".. create_cognito: signin..");
// complete challenge
return Auth.completeNewPassword(user, _result.password, {
email: this_defaults.email,
phone_number: this_defaults.cell,
});
})
.then((user) => {
console.warn(".. create_cognito: confirmed..");
this_account = user;
// get details
return Auth.currentAuthenticatedUser();
})
.then((this_details) => {
if (!(this_details && this_details.attributes))
throw "account creation failes";
this_account_details = Object.assign({}, this_details.attributes);
// signout
return this_account.signOut();
})
.then(() => {
console.warn(".. create_cognito: complete");
resolve(this_account_details);
})
.catch((err) => {
console.error(".. create_cognito: error");
console.error(err);
reject(err);
});
});
};
I am setting a temp password and then later resetting it to the user's requested password.
OLD POST:
You can solve this using the amazon-cognito-identity-js SDK by authenticating with the temporary password after the account creation with cognitoidentityserviceprovider.adminCreateUser(), and running cognitoUser.completeNewPasswordChallenge() within cognitoUser.authenticateUser( ,{newPasswordRequired}) - all inside the function that creates your user.
I am using the below code inside AWS lambda to create enabled Cognito user accounts. I am sure it can be optimized, be patient with me. This is my first post, and I am still pretty new to JavaScript.
var AWS = require("aws-sdk");
var AWSCognito = require("amazon-cognito-identity-js");
var params = {
UserPoolId: your_poolId,
Username: your_username,
DesiredDeliveryMediums: ["EMAIL"],
ForceAliasCreation: false,
MessageAction: "SUPPRESS",
TemporaryPassword: your_temporaryPassword,
UserAttributes: [
{ Name: "given_name", Value: your_given_name },
{ Name: "email", Value: your_email },
{ Name: "phone_number", Value: your_phone_number },
{ Name: "email_verified", Value: "true" }
]
};
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
let promise = new Promise((resolve, reject) => {
cognitoidentityserviceprovider.adminCreateUser(params, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
promise
.then(data => {
// login as new user and completeNewPasswordChallenge
var anotherPromise = new Promise((resolve, reject) => {
var authenticationDetails = new AWSCognito.AuthenticationDetails({
Username: your_username,
Password: your_temporaryPassword
});
var poolData = {
UserPoolId: your_poolId,
ClientId: your_clientId
};
var userPool = new AWSCognito.CognitoUserPool(poolData);
var userData = {
Username: your_username,
Pool: userPool
};
var cognitoUser = new AWSCognito.CognitoUser(userData);
let finalPromise = new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function(authResult) {
cognitoUser.getSession(function(err) {
if (err) {
} else {
cognitoUser.getUserAttributes(function(
err,
attResult
) {
if (err) {
} else {
resolve(authResult);
}
});
}
});
},
onFailure: function(err) {
reject(err);
},
newPasswordRequired(userAttributes, []) {
delete userAttributes.email_verified;
cognitoUser.completeNewPasswordChallenge(
your_newPoassword,
userAttributes,
this
);
}
});
});
finalPromise
.then(finalResult => {
// signout
cognitoUser.signOut();
// further action, e.g. email to new user
resolve(finalResult);
})
.catch(err => {
reject(err);
});
});
return anotherPromise;
})
.then(() => {
resolve(finalResult);
})
.catch(err => {
reject({ statusCode: 406, error: err });
});
If you are trying to change status as a admin from the console. Then follow the below steps after creating the user.
In Cognito goto -> "manage user pool" ->
Goto "App client settings" under App integration section.
Check on the below items
i) Cognito User Pool ii) Authorization code grant iii) Implicit grant iv) phone v) email vi) openid vii) aws.cognito.signin.user.admin viii) profile
Enter the callback url of your application. If you are not sure enter for example: https://google.com and later you can change it to your actual callback url
click on save changes.
Once changes are saved click on the link "Launch Hosted UI"
Enter the credentials of the new created user
Reset the password with new credentials and share the same to the user
step 2
step 3 4 5 6
step 7
step 8
For Java SDK, assuming your Cognito client is setup and you have your user in the FORCE_CHANGE_PASSWORD state you can do the following to get your user CONFIRMED... and then auth'd as normal.
AdminCreateUserResult createUserResult = COGNITO_CLIENT.adminCreateUser(createUserRequest());
AdminInitiateAuthResult authResult = COGNITO_CLIENT.adminInitiateAuth(authUserRequest());
Map<String,String> challengeResponses = new HashMap<>();
challengeResponses.put("USERNAME", USERNAME);
challengeResponses.put("NEW_PASSWORD", PASSWORD);
RespondToAuthChallengeRequest respondToAuthChallengeRequest = new RespondToAuthChallengeRequest()
.withChallengeName("NEW_PASSWORD_REQUIRED")
.withClientId(CLIENT_ID)
.withChallengeResponses(challengeResponses)
.withSession(authResult.getSession());
COGNITO_CLIENT.respondToAuthChallenge(respondToAuthChallengeRequest);
Hope it helps with those integration tests (Sorry about the formatting)
Basically this is the same answer but for .Net C# SDK:
The following will make a full admin user creation with desired username and password.
Having the following User model:
public class User
{
public string Username { get; set; }
public string Password { get; set; }
}
You can create a user and make it ready to use using:
public void AddUser(User user)
{
var tempPassword = "ANY";
var request = new AdminCreateUserRequest()
{
Username = user.Username,
UserPoolId = "MyuserPoolId",
TemporaryPassword = tempPassword
};
var result = _cognitoClient.AdminCreateUserAsync(request).Result;
var authResponse = _cognitoClient.AdminInitiateAuthAsync(new AdminInitiateAuthRequest()
{
UserPoolId = "MyuserPoolId",
ClientId = "MyClientId",
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
AuthParameters = new Dictionary<string, string>()
{
{"USERNAME",user.Username },
{"PASSWORD", tempPassword}
}
}).Result;
_cognitoClient.RespondToAuthChallengeAsync(new RespondToAuthChallengeRequest()
{
ClientId = "MyClientId",
ChallengeName = ChallengeNameType.NEW_PASSWORD_REQUIRED,
ChallengeResponses = new Dictionary<string, string>()
{
{"USERNAME",user.Username },
{"NEW_PASSWORD",user.Password }
},
Session = authResponse.Session
});
}
OK. I finally have code where an administrator can create a new user. The process goes like this:
Admin creates the user
User receives an email with their temporary password
User logs in and is asked to change their password
Step 1 is the hard part. Here's my code for creating a user in Node JS:
let params = {
UserPoolId: "#cognito_pool_id#",
Username: username,
DesiredDeliveryMediums: ["EMAIL"],
ForceAliasCreation: false,
UserAttributes: [
{ Name: "given_name", Value: firstName },
{ Name: "family_name", Value: lastName},
{ Name: "name", Value: firstName + " " + lastName},
{ Name: "email", Value: email},
{ Name: "custom:title", Value: title},
{ Name: "custom:company", Value: company + ""}
],
};
let cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
cognitoIdentityServiceProvider.adminCreateUser(params, function(error, data) {
if (error) {
console.log("Error adding user to cognito: " + error, error.stack);
reject(error);
} else {
// Uncomment for interesting but verbose logging...
//console.log("Received back from cognito: " + CommonUtils.stringify(data));
cognitoIdentityServiceProvider.adminUpdateUserAttributes({
UserAttributes: [{
Name: "email_verified",
Value: "true"
}],
UserPoolId: "#cognito_pool_id#",
Username: username
}, function(err) {
if (err) {
console.log(err, err.stack);
} else {
console.log("Success!");
resolve(data);
}
});
}
});
Basically, you need to send a second command to force the email to be considered verified. The user still needs to go to their email to get the temporary password (which also verifies the email). But without that second call that sets the email to verified, you won't get the right call back to reset their password.
I know It is the same answer, but thought it might help Go developer community. basically it is initiating auth request, get the session and respond to the challenge NEW_PASSWORD_REQUIRED
func sessionWithDefaultRegion(region string) *session.Session {
sess := Session.Copy()
if v := aws.StringValue(sess.Config.Region); len(v) == 0 {
sess.Config.Region = aws.String(region)
}
return sess
}
func (c *CognitoAppClient) ChangePassword(userName, currentPassword, newPassword string) error {
sess := sessionWithDefaultRegion(c.Region)
svc := cognitoidentityprovider.New(sess)
auth, err := svc.AdminInitiateAuth(&cognitoidentityprovider.AdminInitiateAuthInput{
UserPoolId:aws.String(c.UserPoolID),
ClientId:aws.String(c.ClientID),
AuthFlow:aws.String("ADMIN_NO_SRP_AUTH"),
AuthParameters: map[string]*string{
"USERNAME": aws.String(userName),
"PASSWORD": aws.String(currentPassword),
},
})
if err != nil {
return err
}
request := &cognitoidentityprovider.AdminRespondToAuthChallengeInput{
ChallengeName: aws.String("NEW_PASSWORD_REQUIRED"),
ClientId:aws.String(c.ClientID),
UserPoolId: aws.String(c.UserPoolID),
ChallengeResponses:map[string]*string{
"USERNAME":aws.String(userName),
"NEW_PASSWORD": aws.String(newPassword),
},
Session:auth.Session,
}
_, err = svc.AdminRespondToAuthChallenge(request)
return err
}
Here's a unit test:
import (
"fmt"
"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestCognitoAppClient_ChangePassword(t *testing.T) {
Convey("Testing ChangePassword!", t, func() {
err := client.ChangePassword("user_name_here", "current_pass", "new_pass")
Convey("Testing ChangePassword Results!", func() {
So(err, ShouldBeNil)
})
})
}
I have been many times in the same situation. So wrote small CLI in golang to exactly either auth as user ( for further testing purposes ) or just administratively reset the pass.
Then all what you run as command is
$ > go-cognito-authy --profile cloudy -region eu-central-1 admin reset-pass --username rafpe --pass-new 'Password.0ne2!' --clientID 2jxxxiuui123 --userPoolID eu-central-1_CWNnTiR0j --session "bCqSkLeoJR_ys...."
Solution is available on github https://github.com/RafPe/go-cognito-authy/tree/master
You can also just use the Hosted UI of Cognito in case you have one for your application. Just login with the desired user and you will be prompted to change your password. After that the users status is confirmed and you can proceed as normal.