amazon-cognito-identity-js icon indicating copy to clipboard operation
amazon-cognito-identity-js copied to clipboard

How to implement login with OTP functionality using aws cognito JS SDK?

Open joe455 opened this issue 6 years ago • 19 comments

As part of my requirements,I crated sample app which confirms both Email and Password and using MFA too. Now, I want to build functionality like login with OTP.

  1. First on clicking button I will ask them to enter Email / phone.
  2. Next I want to send the OTP and navigate them to profile on success case.

Can somebody give a basic idea how can I implement this? (I am developing this app Angular 4 and please note that my Email/phone attributes are already verified!!!)

joe455 avatar Dec 18 '17 21:12 joe455

There is an example in Tim Hunt's re:invent presentation of how to do this using the custom authentication flow (he is the last presenter):

https://www.youtube.com/watch?v=8DDIxqIW1sM

itrestian avatar Dec 19 '17 07:12 itrestian

Hi @itrestian I understood it but sample app/doc will make me bit more clear. Can you please provide that?

joe455 avatar Dec 19 '17 17:12 joe455

@itrestian I tried custom auth flow.But getting error: Incorrect username or password. My code is below.

`let authenticationData = {
            Username: username,
            Password: ''  
        };
        let authenticationDetails = new AuthenticationDetails(authenticationData);
        let userData = {
            Username: username,
            Pool: this.cognitoUtil.getUserPool()
        };
        let cognitoUser = new CognitoUser(userData);
        console.log("cognitoUser",cognitoUser);
        cognitoUser.setAuthenticationFlowType('CUSTOM_AUTH');
        cognitoUser.authenticateUser(authenticationDetails,{
            onSuccess: (result) => {
                console.log("sucuess");
            },
            onFailure: (err) => {
                console.log("err",err);
            },
            customChallenge: (ChallengeParameters) => {
                var challengeResponses = 'challenge-answer'
                cognitoUser.sendCustomChallengeAnswer(challengeResponses, (err,result) => {

                });
            }
        })

    }`

May be this error because of password, But It should not expect the pass word in passwordless authentication flow.

joe455 avatar Dec 19 '17 18:12 joe455

Actually you would have to call initiateAuth such as in use case 25 in the readme. The authenticateUser call is used for SRP.

itrestian avatar Dec 19 '17 19:12 itrestian

I tried that but I am getting error like "Property 'initiateAuth' does not exist on type 'CognitoUser'. @itrestian I am using same lambda functions form tim's video.

joe455 avatar Dec 19 '17 19:12 joe455

You're probably using an older version of this?

itrestian avatar Dec 19 '17 19:12 itrestian

I am using 1.28.0 version. Is it something issue from lambda function? I am using same lambda functions form tim's video.

joe455 avatar Dec 19 '17 19:12 joe455

My lambda functions.

DefineAuth:

`exports.handler = (event, context, callback) => {
    // TODO implement
    console.log("In lambda");
    //callback(null, 'Hello from Lambda');
    if(event.request.session.length === 0){
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE'
    } else if(event.request.session.length === 1
    && event.request.session[0].challengeName === 'CUSTOM_CHALLENGE'
    && event.request.session[0].challengeResult === true){
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
    } else {
        event.response.issueTokens = false;
        event.response.failAuthentication = true;
    }
    callback(null, event);
};
`
createAuth:
`console.log("Loading Function");
let AWS = require("aws-sdk");
exports.handler = (event, context, callback) => {
    // TODO implement
    if(event.request.session.length === 0
    && event.request.challengeName === 'CUSTOM_CHALLENGE'){
        //CreateCode
        let answer = Math.random().toString(10).substr(2,6);
        
        //send the code via amazon SNS Global SMS
        let sns = new AWS.SNS();
        sns.publish({
            Message: answer + 'is your login code',
            PhoneNumber: event.request.userAttributes.phone_number
        }, (err, data) => {
            if(err){
                console.log(err.stack);
                return;
            }
            console.log('SMS Sent');
        });
        
        //Set the return parameters --Including correct answer
        event.response.publicChallengeParameters = {};
        event.response.privateChallengeParameters = {};
        event.response.privateChallengeParameters.answer = answer;
        event.response.challengeMetaData = 'PASSWORD_CHALLENGE';
    }
    callback(null, event);
};`

VerifyAuth: exports.handler = (event, context, callback) => { // TODO implement if(event.request.privateChallengeParameters.answer === event.request.challengeAnswer){ event.response.answerCorrect = true; }else { event.response.answerCorrect = true; } callback(null, event); };

joe455 avatar Dec 19 '17 19:12 joe455

If you are using the typescript typings, I guess the definition for initiateAuth might not be there yet.

itrestian avatar Dec 19 '17 19:12 itrestian

What does it mean? I am using angular4 with Typescript

joe455 avatar Dec 19 '17 19:12 joe455

`declare module "amazon-cognito-identity-js" {

import * as AWS from "aws-sdk";

export type NodeCallback<E,T> = (err?: E, result?: T) => void;

export interface IAuthenticationDetailsData {
    Username: string;
    Password: string;
}

export class AuthenticationDetails {
    constructor(data: IAuthenticationDetailsData);

    public getUsername(): string;
    public getPassword(): string;
    public getValidationData(): any[];
}

export interface ICognitoStorage {
    setItem(key: string, value: string): void;
    getItem(key: string): string;
    removeItem(key: string): void;
    clear(): void;
}

export interface ICognitoUserData {
    Username: string;
    Pool: CognitoUserPool;
    Storage?: ICognitoStorage;
}

export class CognitoUser {
    constructor(data: ICognitoUserData);

    public setSignInUserSession(signInUserSession: CognitoUserSession): void;
    public getSignInUserSession(): CognitoUserSession | null;
    public getUsername(): string;

    public getAuthenticationFlowType(): string;
    public setAuthenticationFlowType(authenticationFlowType: string): string;

    public getSession(callback: Function): any;
    public refreshSession(refreshToken: CognitoRefreshToken, callback: NodeCallback<any, any>): void;
    public authenticateUser(authenticationDetails: AuthenticationDetails,
                            callbacks: {
                                onSuccess: (session: CognitoUserSession, userConfirmationNecessary?: boolean) => void,
                                onFailure: (err: any) => void,
                                newPasswordRequired?: (userAttributes: any, requiredAttributes: any) => void,
                                mfaRequired?: (challengeName: any, challengeParameters: any) => void,
                                customChallenge?: (challengeParameters: any) => void
                            }): void;
    public confirmRegistration(code: string, forceAliasCreation: boolean, callback: NodeCallback<any, any>): void;
    public sendCustomChallengeAnswer(answerChallenge: any, callback:NodeCallback<any, any>):void;
    public resendConfirmationCode(callback: NodeCallback<Error, "SUCCESS">): void;
    public changePassword(oldPassword: string, newPassword: string, callback: NodeCallback<Error, "SUCCESS">): void;
    public forgotPassword(callbacks: { onSuccess: (data: any) => void, onFailure: (err: Error) => void, inputVerificationCode?: (data: any) => void }): void;
    public confirmPassword(verificationCode: string, newPassword: string, callbacks: { onSuccess: () => void, onFailure: (err: Error) => void }): void;
    public setDeviceStatusRemembered(callbacks: { onSuccess: (success: string) => void, onFailure: (err: any) => void }): void;
    public setDeviceStatusNotRemembered(callbacks: { onSuccess: (success: string) => void, onFailure: (err: any) => void }): void;
    public getDevice(callbacks: {onSuccess: (success: string) => void, onFailure: (err: Error) => void}): any;
    public sendMFACode(confirmationCode: string, callbacks: { onSuccess: (session: CognitoUserSession) => void, onFailure: (err: any) => void }): void;
    public completeNewPasswordChallenge(newPassword: string,
                                        requiredAttributeData: any,
                                        callbacks: {
                                            onSuccess: (session: CognitoUserSession) => void,
                                            onFailure: (err: any) => void,
                                            mfaRequired?: (challengeName: any, challengeParameters: any) => void,
                                            customChallenge?: (challengeParameters: any) => void
                                        }): void;
    public signOut(): void;
    public globalSignOut(callbacks: { onSuccess: (msg: string) => void, onFailure: (err: Error) => void }): void;
    public verifyAttribute(attributeName: string, confirmationCode: string, callbacks: { onSuccess: (success: string) => void, onFailure: (err: Error) => void }): void;
    public getUserAttributes(callback: NodeCallback<Error, CognitoUserAttribute[]>): void;
    public updateAttributes(attributes: ICognitoUserAttributeData[], callback: NodeCallback<Error,string>): void;
    public deleteAttributes(attributeList: string[], callback: NodeCallback<Error, string>): void;
    public getAttributeVerificationCode(name: string, callback: { onSuccess: () => void, onFailure: (err: Error) => void, inputVerificationCode: (data: string) => void | null }): void;
    public deleteUser(callback: NodeCallback<Error, string>): void;
    public enableMFA(callback: NodeCallback<Error, string>): void;
    public disableMFA(callback: NodeCallback<Error, string>): void;
    public getMFAOptions(callback: NodeCallback<Error, MFAOption[]>): void;
}

export interface MFAOption {
    DeliveryMedium: "SMS" |"EMAIL";
    AttributeName: string;
}

export interface ICognitoUserAttributeData {
    Name: string;
    Value: string;
}

export class CognitoUserAttribute {
    constructor(data: ICognitoUserAttributeData);

    public getValue(): string;
    public setValue(value: string): CognitoUserAttribute;
    public getName(): string;
    public setName(name: string): CognitoUserAttribute;
    public toString(): string;
    public toJSON(): Object;
}

export interface ISignUpResult {
    user: CognitoUser;
    userConfirmed: boolean;
    userSub: string;
}

export interface ICognitoUserPoolData {
    UserPoolId: string;
    ClientId: string;
    endpoint?: string;
    Storage?: ICognitoStorage;
}

export class CognitoUserPool {
    constructor(data: ICognitoUserPoolData);

    public getUserPoolId(): string;
    public getClientId(): string;

    public signUp(username: string, password: string, userAttributes: CognitoUserAttribute[], validationData: CognitoUserAttribute[], callback: NodeCallback<Error,ISignUpResult>): void;

    public getCurrentUser(): CognitoUser | null;
}

export interface ICognitoUserSessionData {
    IdToken: CognitoIdToken;
    AccessToken: CognitoAccessToken;
    RefreshToken?: CognitoRefreshToken;
}

export class CognitoUserSession {
    constructor(data: ICognitoUserSessionData);

    public getIdToken(): CognitoIdToken;
    public getRefreshToken(): CognitoRefreshToken;
    public getAccessToken(): CognitoAccessToken;
    public isValid(): boolean;
}

export class CognitoIdentityServiceProvider {
    public config: AWS.CognitoIdentityServiceProvider.Types.ClientConfiguration;
}

export class CognitoAccessToken {
    constructor({ AccessToken }: { AccessToken: string });

    public getJwtToken(): string;
    public getExpiration(): number;
}

export class CognitoIdToken {
    constructor({ IdToken }: { IdToken: string });

    public getJwtToken(): string;
    public getExpiration(): number;
}

export class CognitoRefreshToken {
    constructor({ RefreshToken }: { RefreshToken: string });

    public getToken(): string;
}

export interface ICookieStorageData {
    domain: string;
    path?: string;
    expires?: number;
    secure?: boolean;
}
export class CookieStorage implements ICognitoStorage {
    constructor(data: ICookieStorageData);
    setItem(key: string, value: string): void;
    getItem(key: string): string;
    removeItem(key: string): void;
    clear(): void;
}

} ` index.d.ts file not consisting of initiateAuth method.

joe455 avatar Dec 19 '17 19:12 joe455

I added some thing like below in index.d.ts file. Now my error is Unreconizable lambda output. public initiateAuth(authenticationDetails: AuthenticationDetails, callbacks: { onSuccess: (session: CognitoUserSession, userConfirmationNecessary?: boolean) => void, onFailure: (err: any) => void, newPasswordRequired?: (userAttributes: any, requiredAttributes: any) => void, mfaRequired?: (challengeName: any, challengeParameters: any) => void, customChallenge?: (challengeParameters: any) => void }): void;

What is happening here?

joe455 avatar Dec 19 '17 19:12 joe455

@itrestian kindly let me the exact issue here.

joe455 avatar Dec 19 '17 20:12 joe455

Not entirely sure, the lambdas look ok to me. You can check the lambda console to debug lambda failures and see lambda inputs and outputs.

itrestian avatar Dec 19 '17 21:12 itrestian

@itrestian Is any example available for this flow? Somebody please help me. :(

joe455 avatar Dec 21 '17 17:12 joe455

The video I mentioned should have the example you need.

itrestian avatar Dec 22 '17 00:12 itrestian

Yes. But anyway I am not having intiateAuth method yet!! Waiting...

joe455 avatar Dec 22 '17 08:12 joe455

@itrestian Can you please provide any example achieve custom authentication flow? Not the video One. The methods used in the video are not there in the current version SDK. Or Please host the example shown in video somewhere. I will debug it.

joe455 avatar Dec 28 '17 21:12 joe455

any updates on updated typings? there are a few methods missing

diegolacarta avatar Jan 25 '18 13:01 diegolacarta