Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

user.sendCustomChallengeAnswer is not a function #3373

Closed
beige90 opened this issue May 31, 2019 · 17 comments
Closed

user.sendCustomChallengeAnswer is not a function #3373

beige90 opened this issue May 31, 2019 · 17 comments
Labels
Auth Related to Auth components/category Cognito Related to cognito issues question General question

Comments

@beige90
Copy link

beige90 commented May 31, 2019

This is a long article but organized pretty neat. Please read it and help me if you have any clue.
It's easy to reproduce using simple express application.
(just separate Auth.signIn and Auth.sendCustomChallengeAnswer in different router)

Notice

Similar symptom found in old issue(#1896), but it seems to be happening in frontend, while I'm having it in backend.

=======

Which Category is your question related to?
: Usage Question

What AWS Services are you utilizing?
Cognito with Amplify Auth

Provide additional details e.g. code snippets

Current situation

  1. I'm implementing Auth.signIn in express based Node.js REST API server.
    (Please note that this is backend application, not frontend such as react or angular).

  2. So far, I've successfully implemented Auth.signUp, Auth.confirmSignUp and Auth.signIn.

  3. For custom MFA (sending verification code to user's email), I'm using CUSTOM_AUTH and my lambda functions bound to Cognito user pool are successfully triggered.

  4. Currently implemented features(Auth.signUp, Auth.confirmSignUp and Auth.signIn) were implemented in separated endpoints(i.e., those Auth methods are called by different REST APIs), so as Auth.sendCustomChallengeAnswer that I'm having trouble with.

Problem

  1. As I separated Auth.signIn and Auth.sendCustomChallengeAnswer (due to the separation of endpoints, "/login" and "/verification/login", respectively), user object returned by Auth.signIn is no longer available to pass to Auth.sendCustomChallengeAnswer.

Trial 1

  1. Here's the login code snippet I'm using now:
        // This is a method of Authentication Helper that "/login" endpoint calls.
        // "/login" endpoint is express router which receives email and password from clients.
	login(username, password, resolve, reject) {
		Auth.signIn(username, password)
		.then(user => {
			if (user.challengeName === "CUSTOM_CHALLENGE") {
                                // Here, I tried to pass user object and save it in express-session.
                                // However, when I retrieve the user object from express-session in other api (/verification/login),
                                // the retrieved user object doesn't have sendCustomChallengeAnswer method.
				resolve(user)
			} else {
				reject()
			}
		})
		.catch(error => {
			// Handling error...
		})
	}
  1. Here, passing user object and putting it in express-session doesn't work as described in the comment above. This is the failed code snippet of login verification:
        // This is a method of Authentication Helper that "/verification/login" endpoint calls.
        // argument "user" is retrieved from express-session and passed by "/verification/login" router.
	verifyLoginTrial(user, code, resolve, reject) {
		Auth.sendCustomChallengeAnswer(user, code)
		.then(signedUser => {
			console.log(signedUser)
			resolve(signedUser)
		})
		.catch(error => {
                        // Handling error...
		})
	}
  1. Using this code, I got this error
user.sendCustomChallengeAnswer is not a function

Trial 2

  1. Same code for login

  2. This time, I've tried manually creating CognitoUser but it also didn't work.

...
const CognitoUser = require("amazon-cognito-identity-js").CognitoUser
...
// Start of Authentication Helper
        constructor() {
                // Calling Amplify.configure()
                ...
		this.userPool = new CognitoUserPool({ 
			UserPoolId: poolData.UserPoolId,
			ClientId: poolData.ClientId
		})
        }

        ...

        // Here, instead of saving user object in express-session,
        // I saved username and user.Session in express-session to manually create CognitoUser,
        // which is directly imported from "amazon-cognito-identity-js" library
	verifyLoginTrial(session, username, code, resolve, reject) {
		// Manually create cognito user
		const user = new CognitoUser({
			Username: username, 
			Pool: this.userPool
		})
		user.authenticationFlowType = "CUSTOM_AUTH"
		user.Session = session

		// Send verification code
		Auth.sendCustomChallengeAnswer(user, code)
		.then(signedUser => {
			console.log(signedUser)
			resolve(signedUser)
		})
		.catch(error => {
                        // Handling error...
		})
	}
  1. Using this code, I got this error
AuthClass - Failed to get the signed in user No current user

Question

  1. How can I safely separate Auth.signIn and Auth.sendCustomChallengeAnswer?
  2. or more specifically, How can I safely pass user object returned by Auth.signIn to Auth.sendCustomChallengeAnswer in other API?
  3. if there's no solution to question 2, then how can I call Auth.sendCustomChallengeAnswer with manually created CognitoUser without getting "No current user" error?

Thank you for reading this long question.

@manueliglesias manueliglesias added Auth Related to Auth components/category Cognito Related to cognito issues question General question labels May 31, 2019
@powerful23
Copy link
Contributor

@jinjunho Hi, can you provide more info like:

  • For Solution 1, how are you using express-session to cache the user object? Can you confirm the user you get from the express-session is still a valid CognitoUser object as the same as the user object you get back from Auth.signIn?
  • For Solution 2, I want to know how did you get the session object? The session here is an item in the response context returned by Auth.SignIn not the object you get from Auth.currentSession. Because normally the error message AuthClass - Failed to get the signed in user No current user happens when calling Auth.currentAuthenticatedUser() or Auth.currentSession.

@beige90
Copy link
Author

beige90 commented May 31, 2019

Hi @powerful23, thank you for paying attention to my question.

  • Answer 1.

Short version Two user objects, one from Auth.signIn and one from express-session, are different and that was the reason why I had to try Trial 2.

Long version
First, I used express-session in this way:

// app.js
...
app.use(session({
  secret: "my-secret-string",
  resave: false,
  saveUninitialized: true
}))
...

// router/auth.js
...
router.post("/login", (req, res) => {
	...
	// This function is passed to login function in Solution 1.
        // login function in Solution 1 passes user object returned by Auth.signIn to this function.
	function resolve(user) {
                // First user object check
                console.log(user)
		req.session.username = username
		req.session.user = user
		res.status(httpStatus.OK).send()
	}
	...
})
...
router.post("/verification/login", (req, res) => {
	...
	// Second user object check
	console.log(req.session.user)
}
...

I printed user object two times: first I checked right after Auth.signIn, and secondly I checked right after /verification/login api is called.
It seems that retrieved user object from express-session differs from the original user object returned by Auth.signIn.

Here is the result:

// First check
2019-06-01 07:52:27.646 +09:00: CognitoUser {
2019-06-01 07:52:27.646 +09:00:   username: '...some string...',
2019-06-01 07:52:27.646 +09:00:   pool:
2019-06-01 07:52:27.646 +09:00:    CognitoUserPool {
2019-06-01 07:52:27.646 +09:00:      userPoolId: '...some string...',
2019-06-01 07:52:27.646 +09:00:      clientId: '...some string...',
2019-06-01 07:52:27.646 +09:00:      client:
2019-06-01 07:52:27.646 +09:00:       Client {
2019-06-01 07:52:27.646 +09:00:         endpoint: '...some string...',
2019-06-01 07:52:27.646 +09:00:         userAgent: 'aws-amplify/0.1.x js' },
2019-06-01 07:52:27.646 +09:00:      advancedSecurityDataCollectionFlag: true,
2019-06-01 07:52:27.646 +09:00:      storage:
2019-06-01 07:52:27.646 +09:00:       { [Function: MemoryStorage]
2019-06-01 07:52:27.646 +09:00:         setItem: [Function],
2019-06-01 07:52:27.646 +09:00:         getItem: [Function],
2019-06-01 07:52:27.646 +09:00:         removeItem: [Function],
2019-06-01 07:52:27.646 +09:00:         clear: [Function] } },
2019-06-01 07:52:27.646 +09:00:   Session: '...some string...',
2019-06-01 07:52:27.646 +09:00:   client:
2019-06-01 07:52:27.646 +09:00:    Client {
2019-06-01 07:52:27.646 +09:00:      endpoint: 'https://cognito-idp.ap-southeast-1.amazonaws.com/',
2019-06-01 07:52:27.646 +09:00:      userAgent: 'aws-amplify/0.1.x js' },
2019-06-01 07:52:27.646 +09:00:   signInUserSession: null,
2019-06-01 07:52:27.646 +09:00:   authenticationFlowType: 'CUSTOM_AUTH',
2019-06-01 07:52:27.646 +09:00:   storage:
2019-06-01 07:52:27.646 +09:00:    { [Function: MemoryStorage]
2019-06-01 07:52:27.646 +09:00:      setItem: [Function],
2019-06-01 07:52:27.646 +09:00:      getItem: [Function],
2019-06-01 07:52:27.646 +09:00:      removeItem: [Function],
2019-06-01 07:52:27.646 +09:00:      clear: [Function] },
2019-06-01 07:52:27.646 +09:00:   keyPrefix: '...some string...',
2019-06-01 07:52:27.646 +09:00:   userDataKey:
2019-06-01 07:52:27.646 +09:00:    '...some string...',
2019-06-01 07:52:27.646 +09:00:   challengeName: 'CUSTOM_CHALLENGE',
2019-06-01 07:52:27.646 +09:00:   challengeParam: {} }

// Second check
2019-06-01 07:43:31.043 +09:00: { username: '...some string...',
2019-06-01 07:43:31.043 +09:00:   pool:
2019-06-01 07:43:31.043 +09:00:    { userPoolId: '...some string...',
2019-06-01 07:43:31.043 +09:00:      clientId: '...some string...',
2019-06-01 07:43:31.043 +09:00:      client:
2019-06-01 07:43:31.043 +09:00:       { endpoint: '...some string...',
2019-06-01 07:43:31.043 +09:00:         userAgent: 'aws-amplify/0.1.x js' },
2019-06-01 07:43:31.043 +09:00:      advancedSecurityDataCollectionFlag: true },
2019-06-01 07:43:31.043 +09:00:   Session: '...some string...',
2019-06-01 07:43:31.043 +09:00:   client:
2019-06-01 07:43:31.043 +09:00:    { endpoint: 'https://cognito-idp.ap-southeast-1.amazonaws.com/',
2019-06-01 07:43:31.043 +09:00:      userAgent: 'aws-amplify/0.1.x js' },
2019-06-01 07:43:31.043 +09:00:   signInUserSession: null,
2019-06-01 07:43:31.043 +09:00:   authenticationFlowType: 'CUSTOM_AUTH',
2019-06-01 07:43:31.043 +09:00:   keyPrefix: '...some string...',
2019-06-01 07:43:31.043 +09:00:   userDataKey:
2019-06-01 07:43:31.043 +09:00:    '...some string...',
2019-06-01 07:43:31.043 +09:00:   challengeName: 'CUSTOM_CHALLENGE',
2019-06-01 07:43:31.043 +09:00:   challengeParam: {} }

You can ignore ...some string... parts as they are identical in both objects but hidden for security.
However, in retrieved user object CognitoUser and CognitoUserPool are missing.

Would you help me to understand why this happens and to fix this issue?

  • Answer 2.

notice You mentioned session object, but the Session property in user object (the one returned by Auth.signIn) that I used was String, not Object. (also I had never called Auth.currentAuthenticatedUser() nor Auth.currentSession())
I manually handled Session property from user object because CognitoUser object that I created manually in Trial 2 had null Session value unless I set it manually by keeping it in express-session. Otherwise I had this error

2019-06-01 08:25:34.920 +09:00: [Route:auth] POST /verification/login - { code: 'InvalidParameterException',
2019-06-01 08:25:34.920 +09:00:   name: 'InvalidParameterException',
2019-06-01 08:25:34.920 +09:00:   message: 'Missing required parameter Session' }

I simply retrieved Session property from user object (you can find this property in code example above) and saved it in express-session in the same way I did in code example above.

For better understanding, here's the code I used:

// router/auth.js
...
router.post("/login", (req, res) => {
	...
	// This function is passed to login function in Solution 1.
        // login function in Solution 1 passes user object returned by Auth.signIn to this function.
	function resolve(user) {
		req.session.username = username
		req.session.user_session = user.Session
		res.status(httpStatus.OK).send()
	}
	...
})
...
router.post("/verification/login", (req, res) => {
	...
	// req.session.user_session remains same in express-session
	console.log(req.session.user_session)
}
...

@beige90
Copy link
Author

beige90 commented Jun 1, 2019

I managed to make it work by creating new object property in Authentication Helper (where methods that call Auth.signIn or Auth.sendCustomChallengeAnswer are implemented), which holds user objects returned by Auth.signIn using usernames as their key.

Here's the new code sample that works:

// Authentication Helper
...
	constructor() {
		...
		// This is temporary store for user objects
		this.tempSession = {}
		...
	}
...
	login(username, password, resolve, reject) {
		Auth.signIn(username, password)
		.then(user => {
			if (user.challengeName === "CUSTOM_CHALLENGE") {
				// Keep user object
				this.tempSession[username] = user
				resolve(user)
			} else {
				reject()
			}
		})
		.catch(error => {
			// Handling error...
		})
	}
...
	verifyLoginTrial(username, code, resolve, reject) {
		const user = this.tempSession[username]
		Auth.sendCustomChallengeAnswer(user, code)
		.then(signedUser => {
			// It works
			console.log(signedUser)
			// Flush user object
			delete this.tempSession[username]
			resolve(signedUser)
		})
		.catch(error => {
                        // Handling error...
		})
	}

This solution works anyway. However, I'm not comfortable with this approach, it just feels too hacky.
Is there a better solution than this one?

comment I was suspicious about express-session, so I've tried different options when initializing it, but nothing really worked. Now I removed express-session.

@powerful23
Copy link
Contributor

@jinjunho Hi, For the first question I think it's because express-session can't store an instance, check this: https://stackoverflow.com/questions/23797119/store-an-instance-in-express-js-session

For the second one I think you are doing correctly by storing the Session property and pass it to the newly created user object. Can you try it again and if that error happens again, can you try to find out which function triggers it?

@stale
Copy link

stale bot commented Jul 3, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@hitenkaram
Copy link

hitenkaram commented Jul 9, 2019

Our requirement was also the same and we managed to get the customAuth flow working by creating cognitoUserPool and cognitoUser instance from localStorage/sessionStorage before calling sendCustomChallengeAnswer.

Example:

const userPoolData = {
  Attributes: values(from localStorage);
}
const cognitoUserPool = new CognitoUserPool(userPoolData);

const userData = {
  Attributes: values(from localStorage);
}
const cognitoUser = new CognitoUser(userData);

Auth.sendCustomChallengeAnswer(cognitoUser, validationCode);

@beige90
Copy link
Author

beige90 commented Jul 16, 2019

@hitenkaram It's on clientside, right?

@hitenkaram
Copy link

Sorry for the late response, I thought I already replied back as soon as I got the query notification.
Yes, it's on clientside.

@satejsarker
Copy link

satejsarker commented Jul 31, 2019

i am still having the same problem , when i sign in from API with custom_auth and try to send the custom challenge ans from different api its always giving me response user.sendCustomChallengeAnswer

@stale
Copy link

stale bot commented Aug 30, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@satejsarker
Copy link

i got the answer for back end code : i have to use aws-sdk for customer_auth challenge answer , its was hard to find from the function cz, no documentation is available for it

@taila-teq
Copy link

taila-teq commented Dec 25, 2019

My server side use PKCE for authentication, they also give us some code snippet related to this function user.sendCustomChallengeAnswer, but i found that this is for JS.
i'm just wondering but is this function support for AWS-android SDK, or how do i implement it for AWS-Android SDK

@Ashish-Nanda
Copy link
Contributor

Amplify JS doesnt currently support Node.js
Also to perform sendCustomChallengeAnswer requires you to signIn

@tantai24495 with regards to the Android SDK could you open an issue on their github repo: https://github.com/aws-amplify/aws-sdk-android

If you have additional questions/issues please open a new one

@ajit100
Copy link

ajit100 commented Aug 8, 2020

sessionStorage

did you implement that in client side ? how does the amplify framework store the session after sendCUstomChallengeAnswer?

In my case, with CUSTOM_AUTH, a otp is triggered to email after i signin without password..and then i confirm the otp using sendChallengeAnswer() . But After this the session is not persisted by Auth, so whenever i call Auth.currentAuthenticatedUser() it returns null always..so now if i reload my app it ask me to login again .. whats the way to store session ?

@anklos
Copy link

anklos commented Jan 18, 2021

@ajit100 I am having the same issue, did you find any solution? thanks!

@m98
Copy link
Contributor

m98 commented Jun 10, 2021

I was facing the same issue, and according to what I could see on my IDE, the function existed, but I was still getting that error message.

But I realized the sendCustomChallengeAnswer is there, and the issue was with the first parameter I was passing to the function.

The error message is misleading! I recommend logging both of the parameters you're passing to the function to make sure you're passing correct data to the function

@github-actions
Copy link

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Auth Related to Auth components/category Cognito Related to cognito issues question General question
Projects
None yet
Development

No branches or pull requests

10 participants