Custom auth flow
Amazon Cognito User Pools supports customizing the authentication flow to enable custom challenge types, in addition to a password in order to verify the identity of users. These challenge types may include CAPTCHAs or dynamic challenge questions.
To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito.
For more information about working with Lambda Triggers for custom authentication challenges, please visit Amazon Cognito Developer Documentation.
Custom Authentication in Amplify
To initiate a custom authentication flow in your app, specify authenticationFlowType
as CUSTOM_AUTH
in the awsconfiguration.json file. Note that is not currently supported by the CLI and developers must manually update the awsconfiguration.json to specify authenticationFlowType
as follows :
{ "CognitoUserPool": { "Default": { "PoolId": "XX-XXXX-X_abcd1234", "AppClientId": "XXXXXXXX", "Region": "XX-XXXX-X" } }, "Auth": { "Default": { "authenticationFlowType": "CUSTOM_AUTH" } }}
Next, in the app code call signIn
with a dummy password. Any custom challenges needs to be answered using the confirmSignIn
method as follows:
public void signIn() { AWSMobileClient.getInstance().signIn(username, password, null, new Callback<SignInResult>() { @Override public void onResult(final SignInResult signInResult) { runOnUiThread(new Runnable() { @Override public void run() { Log.d("APP", "Sign-in callback state: " + signInResult.getSignInState()); switch (signInResult.getSignInState()) { case DONE: Log.d(TAG, "Sign-in done."); break; case SMS_MFA: Log.d(TAG, "Please confirm sign-in with SMS."); break; case NEW_PASSWORD_REQUIRED: Log.d(TAG, "Please confirm sign-in with new password."); break; case CUSTOM_CHALLENGE: confirmSignIn(); break; default: Log.d(TAG, "Unsupported sign-in confirmation: " + signInResult.getSignInState()); break; } } }); }
@Override public void onError(Exception e) { Log.e(TAG, "Sign-in error", e); } });}
public void confirmSignIn() { Map<String, String> res = new HashMap<String, String>(); res.put(CognitoServiceConstants.CHLG_RESP_ANSWER, "<CHALLENGE_RESPONSE>"); AWSMobileClient.getInstance().confirmSignIn(res, new Callback<SignInResult>() { @Override public void onResult(final SignInResult signInResult) { runOnUiThread(new Runnable() { @Override public void run() { Log.d(TAG, "Sign-in callback state: " + signInResult.getSignInState()); switch (signInResult.getSignInState()) { case DONE: Log.d(TAG, "Sign-in done."); break; case SMS_MFA: Log.d(TAG, "Please confirm sign-in with SMS."); break; case NEW_PASSWORD_REQUIRED: Log.d(TAG, "Please confirm sign-in with new password."); break; default: Log.d(TAG, "Unsupported sign-in confirmation: " + signInResult.getSignInState()); break; } } }); }
@Override public void onError(Exception e) { Log.e(TAG, "Confirm Custom auth Sign-in error", e); } });}
Lambda trigger setup
Amplify CLI can be used generate lambda triggers required by a custom authentication flow. See documentation for details. Amplify CLI creates a custom auth flow skeleton that you can manually edit. More information on each of the triggers can be found in Cognito documentation.
AWSMobileClient
assumes that custom auth flows start with username and password. If you want a passwordless custom authentication flow, modify your Define Auth Challenge
Lambda trigger to bypass the initial username/password verification and proceed to the custom challenge, as in the code below.
exports.handler = (event, context) => { if (event.request.session.length === 1 && event.request.session[0].challengeName === 'SRP_A') { event.response.issueTokens = false; event.response.failAuthentication = false; event.response.challengeName = 'CUSTOM_CHALLENGE'; } else if ( event.request.session.length === 2 && event.request.session[1].challengeName === 'CUSTOM_CHALLENGE' && event.request.session[1].challengeResult === true ) { event.response.issueTokens = true; event.response.failAuthentication = false; } else { event.response.issueTokens = false; event.response.failAuthentication = true; } context.done(null, event);};