AWS Amplify Auth Errors - amazon-web-services

I'm using the Android Amplify library. I am having trouble finding out what kind of error would be passed back from the Amplify.Auth.signIn() function. I'm not finding the documentation for this anywhere. Right now I am just kind of guessing as to what it will return. What I want is to tell the user how to recover from the error. Does the username not exist, was the password incorrect, was it of bad format, etc. Reading the source code I am given the impression that AmplifyException.recoveryMessage is what I want but that would still be problematic as it doesn't allow me to customize the message.
/**
* Sign in the user to the back-end service and set the currentUser for this application
* #param username User's username
* #param password User's password
*/
override fun initiateSignin(username : String, password : String) {
//Sign in the user to the AWS back-end
Amplify.Auth.signIn(
username,
password,
{result ->
if (result.isSignInComplete) {
Timber.tag(TAG).i("Sign in successful.")
//Load the user if the sign in was successful
loadUser()
} else {
Timber.tag(TAG).i("Sign in unsuccessful.")
//TODO: I think this will happen if the password is incorrect?
}
},
{error ->
Timber.tag(UserLogin.TAG).e(error.toString())
authenticationRecoveryMessage.value = error.recoverySuggestion
}
)
}
Authentication recovery message is LiveData that I want to update a snackbar which will tell the user what they need to do for a successful login. I feel there must be some way to get the error from this that I just haven't figured out yet. The ideal way to handle messages to the user is with XML strings for translation possibilities so I would really like to use my own strings in the snackbar but I need to know the things that can go wrong with sign-up and what is being communicated to me through the error -> {} callback.

I couldn't find them in the documentation myself, so i decided to log the possibles cases.
try {
const signInResult = await Auth.signIn({
username: emailOrPhoneNumber,
password
});
const userId = signInResult.attributes.sub;
const token = (await Auth.currentSession()).getAccessToken().getJwtToken();
console.log(userId, 'token: ', token);
resolve(new AuthSession(userId, token, false));
} catch (e) {
switch (e.message) {
case 'Username should be either an email or a phone number.':
reject(`${AuthError.usernameInvalid}: ${e.message}`);
break;
case 'Password did not conform with policy: Password not long enough':
reject(`${AuthError.passwordTooShort}: ${e.message}`);
break;
case 'User is not confirmed.':
reject(`${AuthError.userIsNotConfirmed}: ${e.message}`);
break;
case 'Incorrect username or password.':
reject(`${AuthError.incorrectUsernameOrPassword}: ${e.message}`);
break;
case 'User does not exist.':
reject(`${AuthError.userDoesNotExist}: ${e.message}`);
break;
default:
reject(`${AuthError.unknownError}: ${e.message}`);
}
}

SignIn uses Cognito's InitiateAuth under the hood, so error codes can be found here:
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html#API_InitiateAuth_Errors
They are available in the code field of the error.

Related

What is "cognitoUser.getAttributeVerificationCode"?

I'm convinced that Amazon goes out of its way to make understanding their platform as difficult as is possible.
I've read over the documentation regarding "cognitoUser.getAttributeVerificationCode" at Amazon only to have it make me even more confused!
Verify an Attribute
The following example verifies user attributes for an authenticated user.
cognitoUser.getAttributeVerificationCode('email', {
onSuccess: function (result) {
console.log('call result: ' + result);
},
onFailure: function(err) {
alert(err);
},
inputVerificationCode: function() {
var verificationCode = prompt('Please input verification code: ' ,'');
cognitoUser.verifyAttribute('email', verificationCode, this);
}
});
Can anyone help me understand what this is (cognitoUser.getAttributeVerificationCode) and/or how I would use it? I don't understand why I would verify an email attribute w/a verification code.
The verification code is sent to the users email. The user has to properly receive that email to retrieve the code and enter it in a UI so that the email is set to verified in Cognito. That user can then reset their password using that email.
What if the user had entered an incorrect email? Or their email system didn't allow them to receive the code sent by AWS?
By sending out a verification code, and having the user send it back, Cognito verifies that the email was entered correctly and belongs to that user. This can seem like a pain but is standard on many web platforms now days...The same process is needed with phone numbers for users in your Cognito user pool.

AWS Cognito username/email login is case-sensitive

Setup
I am using AWS Cognito to manage the user registration and user access for my web application. Specifically I am using the Cognito hosted UI. This means that Cognito presents a UI for my users to register, I do not have access to modify the user sign-up or login pages for my application (other than the controls provided by Cognito). I am using email addresses as usernames, so new users are simply asked to provide an email address and password.
Problem
Cognito treats email addresses as case sensitive. If a user signs up with the email address JOHN_smith#randommail.com, they cannot then sign in using john_smith#randommail.com.
I want user email addresses for sign-up and login to be case insensitive.
What I have tried
Usually this would be trivial to deal with by setting the email address to the lowercase in the client before sending it to the server. However I do not have access to the client UI as it is hosted by Cognito.
My plan therefore was to try using a Lambda function invoked by a Cognito pre-signup trigger to lowercase the email supplied by the user.
Pre sign-up
Amazon Cognito invokes this trigger when a user attempts
to register (sign up), allowing you to perform custom validation to
accept or deny the registration request.
Here is the lamdba function I wrote:
'use strict';
console.log('Loading function');
exports.handler = (event, context, callback) => {
console.log('Received event:', JSON.stringify(event, null, 2));
var triggerSource = event.triggerSource;
console.log('Received triggerSource:', triggerSource);
var email = event.request.userAttributes.email;
console.log('Received email:', email);
var modifiedEvent = event;
if (email !== null) {
var lowerEmail = email.toLowerCase();
modifiedEvent.request.userAttributes.email = lowerEmail;
console.log('Set email in request to', lowerEmail);
console.log('Modified event:', JSON.stringify(modifiedEvent, null, 2));
} else {
console.log('Email evaluated as NULL, exiting with no action');
}
// Return result to Cognito
callback(null, modifiedEvent);
};
This 'worked' in the sense that the email address in the event request was modified to be lowercase (john_smith#randommail.com). However, it seems the account has already been created in the userpool by the time my Lambda function receives this event. Changing the email address in the request had no effect - the original email address (JOHN_smith#randommail.com) still appears in my user pool. I suspect the only fields in the event that have any effect are the response fields. Here is what my modified event looks like:
{
"version": "1",
"region": "us-east-1",
"userPoolId": "us-east-1_xxxxxxx",
"userName": "xxxxxx-xxxx-xxxx-xxxx-xxxxxxx",
"callerContext": {
"awsSdkVersion": "aws-sdk-java-console",
"clientId": "xxxxxxxxxxxxxxxxxxxxxx"
},
"triggerSource": "PreSignUp_SignUp",
"request": {
"userAttributes": {
"email": "john_smith#randommail.com"
},
"validationData": null
},
"response": {
"autoConfirmUser": false,
"autoVerifyEmail": false,
"autoVerifyPhone": false
}
}
My question
I'm looking for ideas or examples to make my user registration and login case insensitive. This might include changes to my lambda trigger approach or something else entirely.
Please note I know I could implement my own UI, which I will only do as a last resort.
Fixed on new user pools. You can turn off case sensitivity now.
https://aws.amazon.com/about-aws/whats-new/2020/02/amazon-cognito-user-pools-service-now-supports-case-insensitivity-for-user-aliases/
You could trigger a Lambda function after sign-up to change the email to lowercase. Without actually testing it, you should be able to trigger a Lambda post confirmation. That Lambda could use AdminUpdateUserAttributes API, called from your SDK of choice, to change the email to lowercase.
Note that user names are also case sensitive.
Since username is case sensetive: why not just use the username as the email address and have the prehook populate the email field with the username?
Make username lowercase when creating the account. Make username lowercase when logging in. At least this way you only have to re-create users that have uppercase characters. Or you can rebuild your entire user pool and migrate the users, but who knows what affect that will have on passwords and MFA. It doesn't seem worth the trouble with an existing user pool. Hopefully you are doing a brand new project and you can select the case insensitive option when creating the user pool.
After multiple attempts, I have tried adding a small css for username field on login page and forgot password page.
#amplify-id-8,#amplify-id-0{
text-transform: lowercase;
}
#amplify-id-0::placeholder,#amplify-id-8::placeholder{
text-transform: capitalize;
}
This worked for me on the amplify-authenticator , I used as a login component.
another possibility is if you can create a user signup api, which would create the user in the cognito. after trimming email along with performing lower case on email. you can perform your custom steps before that.
you are right that pre-signup trigger basically get invoked after signup is performed.
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html#aws-lambda-triggers-pre-registration-tutorials

Facebook Matching API - Returns empty data for any other user

I have a problem when I use the Facebook Checkbox Plugin in order to connect my users to a Facebook chatbot. When they click and the checkbox is checked, I get their user reference, and sending him/her a message, I get the user page-scoped id.
Using this user page-scoped id, I should be able to get the user app-scoped id, that I need to get more information from this user.
In order to to this, I use the facebook Matching API, and it works great for my administrator user, but as soon as I login using any other user, even if it is registered as a developer, the data that I get from the matching API is empty.
[https://developers.facebook.com/docs/messenger-platform/identity/id-matching]
Anybody has an idea about what could be happening here? My app is live (not approved), and I believe the permissions and tokens are right... If there is a problem, it should be about tokens, but I'm not sure about this.
Here, some of my code:
const accessToken = config.facebook.unlimitedPageAccessToken;
const clientSecret = config.facebook.clientSecret;
const appsecretProof = CryptoJS.HmacSHA256(accessToken, clientSecret).toString(CryptoJS.enc.Hex);
request({
url: 'https://graph.facebook.com/v2.10/'+ recipientId +'/ids_for_apps',
qs: { access_token: accessToken, appsecret_proof: appsecretProof }
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
body = JSON.parse(body);
console.log("data --> " + JSON.stringify(body, null, 4));
const userAppId = body.data[0].id;
return userAppId;
} else {
console.error("Error trying to translate ID's.");
}
});
As I said, when I log in with any other user than the administrator, I get this:
{
"data": []
}
For every Facebook page, a user has a different psid. So until you get that page scoped id, you won't be able to send them a message. So may be what you can do is link the users to the page first to initialize the conversation.

Cognito change phone_number before confirm via Phone

I want to change phone_number attribute of user before they confirm via phone. My flow step:
User register by username, password, and phone number
User must be enter confirmation code received by the phone. In this step user want to change the phone number (wrong number or change the phone...)
2.1 In case the 1st phone number be wrong, the next phone number is correct -> only one confirmation code had been sent -> it works!
2.2 In case the 1st phone number and the next are correct -> have two confirmation code had been sent(1st - xxx, 2nd - yyy) -> User enter 2nd confirmed code, Cognito throws CodeMismatchException: Invalid verification code provided, please try again. error. User enter 1st code, user had been confirmed, but in Cognito system the user has phone_number is 2nd number and phone_number_verified is true.
I use adminUpdateUserAttributes to change phone_number of a user who has status is UNCONFIRMED. Confirmation code auto send after me call change phone number.
How to fix this?
!!!Update
Currently, I removed the feature User can update their phone_number before they confirmed via phone from my application.
It takes me about 5 days, I just want to memo my case.
When you try to update phone_number (or email) attribute, Cognito will send a confirmation to your phone (or email) in automatically, this is the first code - (1st - xxx), the code to confirm your new attribute value (not for user confirmation).
In the same time, logic code calls resendConfirmationCode function, it send the second code - (2nd - yyy), this is main reason only the second code working (we use confirmSignUp function to handle the code).
I am on the Cognito team, same as behrooziAWS. After looking at your scenario, it does seem to be a bug on our side. I will mention it within the team so that we prioritize it accordingly.
This question was asked awhile ago but some people still having issues with verification code being sent and no way to verify the code on an account not confirmed yet so I found a solution that works for us.
Our auth flow is:
SignUp -> OTP Screen -> Confirmed OTP -> Cognito Account confirmed -> Custom email sent to user to verify email address -> Update attribute email_verified = true
On the OTP screen, we display the number OTP has been sent to, if it's the incorrect number, we allow the user to go back to signup page and change number and resubmit signup. We use a UUID for the user on cognito so as to allow a user to signup again without causing errors where account already exists but not confirmed.
This means we get two accounts with UUID in cognito, one being confirmed and one being unconfirmed with the only difference in the accounts is the phone number. We then get rid of unconfirmed accounts after a certain period. eg 7 days
For anybody else seeking the answer to this, what I ended up doing was writing a lambda that essentially checks if a user is unconfirmed, delete's the user and then signs them up again. I originally went the updateUserAttributes route but it felt insecure in case a bad actor got access to the lambda and updated a confirmed users phone number to theirs. If a user signups with a different username but the same number from a different account, it will invalidate the others users account. Hence the logic below.
try {
const userParams = {
UserPoolId: process.env.userpool_id,
Username: event.args.username
};
const { UserStatus } = await identity.adminGetUser(userParams).promise();
if (UserStatus === 'UNCONFIRMED') {
const deletedIdentity = await identity.adminDeleteUser(userParams).promise();
if (deletedIdentity) {
const signupParams = {
ClientId: process.env.client_id,
Password: event.args.password,
Username: event.args.password,
UserAttributes: [
{
Name: 'phone_number',
Value: event.args.phoneNumber
}
]
}
const newSignUp = await identity.signUp(signupParams).promise();
if (newSignUp) {
response.send(event, context, response.SUCCESS, {
newSignUp
});
callback(null, newSignUp)
}
}
} else {
response.send(event, context, response.ACCESSDENIED, {
error: 'User not authorized to perform this action'
});
callback({error: 'User not authorized to perform this action'}, null)
}
} catch (error) {
response.send(event, context, response.FAILURE, {
error
});
callback(error, null)
}

AWS Cognito User Pool without a password

I want to use a phone number as the username for my app and i want to be able to make it simple to sign up by just having to verify the phone number each time they want to login - no messy password remembering business.
How to do this with AWS Cognito User Pool as its asking me to mandatorily configure a password for each user.
I thought of using a dummy password for each user and configure mandatory user verification. Everytime the user sign out i can "Unverify" the user so that next time they would automatically be asked to verify the phone number. Also i would wire up my app to only "login" if the user is verified.
Please let me know if the is the best approach :( I'm new to AWS and i could't find any posts for this scenario.
Thanks !!
Since AWS Cognito is not currently supporting passwordless authentication you need to implement a workaround with random password stored externally. You can implement the authentication flow as follows.
After user Signup (Also ask for mobile number and make it mandatory), store the Mobile number, Username and Password also in Dynamodb encrypted with AWS KMS (For additional security).
You can use MFA with mobile number for authentication challenge so that after the user enters mobile number and press login(In frontend), in the backend you can automatically do username password matching(Passthrough) and trigger the MFA to send a code for user's mobile and verify that using the AWS Cognito SDK (Without implementing custom mobile message and challenge).
If you plan to implement the flow manually(Without MFA) to send the SMS & validation, you can use AWS SNS for the purpose.
Check the following code sample to understand the insight of MFA and refer this link for more details.
var userData = {
Username : 'username',
Pool : userPool
};
cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
var authenticationData = {
Username : 'username',
Password : 'password',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
alert('authentication successful!')
},
onFailure: function(err) {
alert(err);
},
mfaRequired: function(codeDeliveryDetails) {
var verificationCode = prompt('Please input verification code' ,'');
cognitoUser.sendMFACode(verificationCode, this);
}
});
Note: Here the MFA with mobile number is not used for the purpose of MFA but as a workaround to meet your requirement.
This is a slightly different spin to what the OP is requesting as it uses a single secret, but I think it may help others who land on this question.
I was able to do this by creating custom lambdas for the Cognito triggers: Define Auth Challenge, Create Auth Challenge & Verify Auth Challenge.
My requirement was that I wanted my backend to use a secret to then get access & refresh tokens for any Cognito user.
Define Auth Challenge Lambda
exports.handler = async event => {
if (
event.request.session &&
event.request.session.length >= 3 &&
event.request.session.slice(-1)[0].challengeResult === false
) {
// The user provided a wrong answer 3 times; fail auth
event.response.issueTokens = false;
event.response.failAuthentication = true;
} else if (
event.request.session &&
event.request.session.length &&
event.request.session.slice(-1)[0].challengeResult === true
) {
// The user provided the right answer; succeed auth
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
// The user did not provide a correct answer yet; present challenge
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
return event;
};
Create Auth Challenge Lambda
exports.handler = async event => {
if (event.request.challengeName == 'CUSTOM_CHALLENGE') {
// The value set for publicChallengeParameters is arbitrary for our
// purposes, but something must be set
event.response.publicChallengeParameters = { foo: 'bar' };
}
return event;
};
Verify Auth Challenge Lambda
exports.handler = async event => {
if (event.request.challengeName == 'CUSTOM_CHALLENGE') {
// The value set for publicChallengeParameters is arbitrary for our
// purposes, but something must be set
event.response.publicChallengeParameters = { foo: 'bar' };
}
return event;
};
I was then able to use some JS, using amazon-cognito-identity-js, to provide the secret and get the tokens:
var authenticationData = {
Username : 'username'
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = {
UserPoolId : '...', // Your user pool id here
ClientId : '...' // Your client id here
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username : 'username',
Pool : userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.setAuthenticationFlowType('CUSTOM_AUTH');
cognitoUser.initiateAuth(authenticationDetails, {
onSuccess: function(result) {
// User authentication was successful
},
onFailure: function(err) {
// User authentication was not successful
},
customChallenge: function(challengeParameters) {
// User authentication depends on challenge response
var challengeResponses = 'secret'
cognitoUser.sendCustomChallengeAnswer(challengeResponses, this);
}
});
this may work, but storing password in dynamoDB may have issues, considering security. instead we can try like this:
option#1: - user sign ups with username and password.
setup cognito triggers - we can use lambda functions.
A. Create Auth Challenge
B. Define Auth Challenge
C. Verify Auth Challenge Response
client app should implement CUSTOM_CHALLENGE authentication flow.
ask user to enter registered phone number, pass this in username field. trigger B will understand the request and passes flow to trigger A, Trigger A will generate random code 5. use AWS SNS service to send SMS to user mobile number
Trigger C will validate OTP and allows login
points to consider:
a. setup phone number as alias (select Also allow sign in with verified phone number)
b. make phone number field as verifiable (this allows user to receive OTP)
option#1: - user sign ups without username and password.
cognito setup
setup phone number as alias (select Also allow sign in with verified phone number)
make phone number field as verifiable (this allows user to receive OTP)
during signup don't ask user to provide username and password, just ask phone number
generate UUID for both username and password to be unique and pass these to cognito along with phone number
Cognito sends OTP code to user for account confirmation.
for phone number with OTP login setup triggers as explained in above option.
for triggers code,refer
aws cognito pool with multiple sign in options