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
Related
We are implementing MFA using Microsoft Azure. We run wso2 identity server 5.10.4.
What I would like to see happen, is when a user logs in, if they have certain memberOf AD roles, Federation happens. If not, it just uses basic auth/normal log in.
I've been approaching this problem using Adaptive Authentication custom scripting. On a service provider, I created two login steps. Step 1 is basicauth. Step 2 is our MS federated authenticator.
var arrayOfRoles = ["employee","student"];
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function (context) {
// Extracting authenticated subject from the first step
var memberOfClaim = 'http://wso2.org/claims/employeeType';
var user = context.currentKnownSubject;
var roles = context.currentKnownSubject.localClaims[memberOfClaim];
foundRole = '-1';
var arrayOfRolesLen = arrayOfRoles.length;
for (var i = 0; i < arrayOfRolesLen; i++) {
searchRole = roles.indexOf(arrayOfRoles[i]);
if (searchRole >= 0) {
foundRole = searchRole;
}
}
if (foundRole >= 0 ) {
Log.info(user.username + ' found a role, indexof=' + foundRole);
// Step 2 is MFA
executeStep(2);
}
}
});
};
The script correctly finds the person's AD memberOf values, and I'm able to execute step 2 using this script. The problem is that a person logs in once to wso2, then if the roles are matched and exectuteStep(2) is invoked, they are prompted with another log in screen for wso2.
How can I prevent a second log in when a person matches the conditions for step 2? Or is this the wrong approach to making a role based decision about when to authenticate using basicauth and when to authenticate using federation?
edit:
Responding to some comments below about using identity-first.
If I setup three steps 1) id first, 2) basic 3) federated, I am prompted for the username again. Step one, a username prompt from wso2. Step two, a username and password prompt. Ditto with step 3.
One difference I see in the below screenshots, is that there is a 'username and password' authenticator. I don't have that available to me in the drop down boxes. Just jwt-basic, and basic. My script using three auth steps looks like this:
var arrayOfRoles = ["PCC_EMPLOYEE_ACTIVE","PCC_STUDENT_CREDIT"];
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function (context) {
// Extracting authenticated subject from the first step
var memberOfClaim = 'http://wso2.org/claims/employeeType';
var user = context.currentKnownSubject;
var roles = context.currentKnownSubject.localClaims[memberOfClaim];
foundRole = '-1';
var arrayOfRolesLen = arrayOfRoles.length;
for (var i = 0; i < arrayOfRolesLen; i++) {
searchRole = roles.indexOf(arrayOfRoles[i]);
if (searchRole >= 0) {
foundRole = searchRole;
}
}
Log.info('found role is equal to: ' + foundRole);
if (foundRole >= 0 ) {
Log.info(user.username + ' found a role, indexof=' + foundRole);
// Step 3 is Azure idp.
executeStep(3);
} else {
// Step 2 is basic auth.
executeStep(2);
}
}
});
};```
You may try "identifier First" login with hasRole() to implement your flow.
i.e.
Step1 = Identifier
Step2 = Basic
Step3 = Azure
Let me explain what you have written here using the script.
So you have two auth steps.
Basic auth (username and password)
Federated IDP (Azure)
If you have not enabled adaptive script, after the basic auth step (step1), the user needs to authenticate against the FIDP (step2).
According to the current script that you have written, all the users will have a username and password authentication step (step1). Upon the success of that step, FIDP authentication will be enabled (step2) if the user has a certain set of roles.
Now consider a user who has those specified roles. When that user logins, first the user will see the username password login page. After that, the user will see the azure login page if the user has no active session in Azure (If SSO is enabled, the user might not see the second screen). So it is expected to see two login screens.
So what you have done here is correct, but the user experience is kind of weird. A similar scenario is explained here.
Edited: The better approach is to use the identifier first login method. You can read it here. Please find the approach that I have tried.
I have added 3 steps as follows.
Next I have added a role based adaptive script.
With this approach if I have the role admin, I will only see a input box to provide my password.
But for google it will show google sign-in. If you already have a session then only the consent will be seen. We cannot skip this since this is not in our control.
I've currently integrated an OTP authentication signin flow to a React Native app with Amplify. The flow is as follows.
(I followed the guide provided in this article)
But right now I'm in the need of providing the option of sending the OTP code to a user entered email address in case if it is not received to the mobile number.
Initial thought was to trigger the create auth challenge lambda function via Amplify Auth.signUp by sending email attribute.
import Auth from '#aws-amplify/auth';
const sendVerificationToEmail = async (phone: number, email: string, password: string) => {
await Auth.signUp({
username: phone,
password,
attributes: {
email
} });
}
Then the lambda function can be remodified as follows to either send an email or SMS,
...
const email = event.request.userAttributes.email;
if (email) {
// Logic to send OTP verification code via SES
} else {
// Logic to send OTP via SNS
}
but this is not possible since we already have a user created in the cognito user pool. This attribute will not be passed to the lambda function.
What is the correct approach of achieving this ?
Easiest approach is, Setup lambda function for custom flow.
Create custom method to generate an OTP and save it to into a table and
then use SES to send OTP to email .
Create custom method to verify OTP.
Check OTP from table and verify it.
if(SUCCESS)
use admin to mark as verified
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminConfirmSignUp.html
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)
}
I'm currently working on using AWS Cognito to manage user authentication for our application. One snag I'm running into is figuring out a good way to implement a "test" or "qa" environment.
I have a lot of automated tests for my APIs that will create users with random data. Obviously I don't want to Cognito to send out actual SMS or Email messages in this environment. Also when doing manual testing we will be creating users a lot with fake phone numbers and emails. Is there any way to turn the User Pool in "development" mode where all messages simply get logged some way?
You can write a pre sign up lambda function and auto confirm the user in the lambda function by setting the autoConfirmUser flag. In that case, Cognito doesn't send any SMSes or emails with confirmation codes. Example lambda below from the documentation (http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html#aws-lambda-triggers-pre-registration-example).
exports.handler = function(event, context) {
// This Lambda function returns a flag to indicate if a user should be auto-confirmed.
// Perform any necessary validations.
// Impose a condition that the minimum length of the username of 5 is imposed on all user pools.
if (event.userName.length < 5) {
var error = new Error('failed!');
context.done(error, event);
}
// Access your resource which contains the list of emails of users who were invited to sign up
// Compare the list of email IDs from the request to the approved list
if(event.userPoolId === "yourSpecialUserPool") {
if (event.request.userAttributes.email in listOfEmailsInvited) {
event.response.autoConfirmUser = true;
}
}
// Return result to Cognito
context.done(null, event);
};
Here is what I did to create a "staging" environment User Pool in AWS Cognito that does not send real notifications to users. There were actually a couple different pieces involved, but I think I was able to get everything covered. That being said, it would sure be nice if Cognito simply provided a User Pool setting to turn off all notifications, that way I don't have to write environment specific logic into my code.
Prevent User Invitations
In our app we use the AdminCreateUser function to create users who get invited by other users. This function will normally send an invitation message to the new user's phone number or email. In order to prevent those invitations, you can provide MessageAction: 'SUPPRESS' as a parameter to the function arguments. Like so:
let params = {
UserPoolId: config.cognitoUserPoolId,
Username: uuid.v4(),
MessageAction: 'SUPPRESS', /* IMPORTANT! */
TemporaryPassword: user.phone_number.slice(-6),
UserAttributes: [
{ Name: 'given_name', Value: user.first_name },
{ Name: 'family_name', Value: user.last_name },
{ Name: 'phone_number', Value: user.phone_number }
]
};
cognito.adminCreateUser(params).promise().then(data => {
console.log(data);
});
Official docs for that here: http://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminCreateUser.html
Prevent User Attribute Update Verfications
In our production app, we want users to have to re-verify their phone number or email if it changes. But in our staging environment we don't actually need this. So uncheck the boxes for email and phone under the "Do you want to require verification of emails or phone numbers?" section in your User Pool settings.
My goal : Have a mobile app that does not require users to ever sign in. Have these unauthenticated users hit my server.
What I have : My server is using the AWS API Gateway / AWS Lambda setup. The custom authorizer I used for AWS API Gateway was designed using this example. I Also pasted the code from this example below (A).
My Question : From the code block below (A), I get the impression I should use JWT. How can I use JWT to validate unauthenticated users when these tokens expire? If JWT is not the best thing to use, what would be?
Thanks!
(A)
var nJwt = require('njwt');
var AWS = require('aws-sdk');
var signingKey = "CiCnRmG+t+ BASE 64 ENCODED ENCRYPTED SIGNING KEY Mk=";
exports.handler = function(event, context) {
console.log('Client token: ' + event.authorizationToken);
console.log('Method ARN: ' + event.methodArn);
var kms = new AWS.KMS();
var decryptionParams = {
CiphertextBlob : new Buffer(signingKey, 'base64')
}
kms.decrypt(decryptionParams, function(err, data) {
if (err) {
console.log(err, err.stack);
context.fail("Unable to load encryption key");
} else {
key = data.Plaintext;
try {
verifiedJwt = nJwt.verify(event.authorizationToken, key);
console.log(verifiedJwt);
// parse the ARN from the incoming event
var apiOptions = {};
var tmp = event.methodArn.split(':');
var apiGatewayArnTmp = tmp[5].split('/');
var awsAccountId = tmp[4];
apiOptions.region = tmp[3];
apiOptions.restApiId = apiGatewayArnTmp[0];
apiOptions.stage = apiGatewayArnTmp[1];
policy = new AuthPolicy(verifiedJwt.body.sub, awsAccountId, apiOptions);
if (verifiedJwt.body.scope.indexOf("admins") > -1) {
policy.allowAllMethods();
} else {
policy.allowMethod(AuthPolicy.HttpVerb.GET, "*");
policy.allowMethod(AuthPolicy.HttpVerb.POST, "/users/" + verifiedJwt.body.sub);
}
context.succeed(policy.build());
} catch (ex) {
console.log(ex, ex.stack);
context.fail("Unauthorized");
}
}
});
};
My Question : From the code block below (A), I get the impression I
should use JWT. How can I use JWT to validate unauthenticated users
when these tokens expire? If JWT is not the best thing to use, what
would be?
Why do you need any validation at all? If you want to allow unauthenticated access to your API you should just do that and save yourself some effort.
Update
If you want to restrict access and not require a user to login, you may want to consider simply using Cognito Identity. It supports unauthenticated identities and migration to authenticated access either via Cognito Your User Pools or another federated provider. It is worth noting that if you go this route, you will want to secure/obscure your identity pool id properly within your app to minimize the chance of it being extracted.
Using Cognito Identity, you would be able to get AWS credentials to sign the requests to your API using standard Signature version 4 and avoid the cost and overhead of managing a custom authorizer.