aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.45k stars 2.13k forks source link

Increase InitiateAuth Session Limit #3478

Open KristineTrona opened 5 years ago

KristineTrona commented 5 years ago

Which Category is your question related to? Cognito, CustomAuth flow

What AWS Services are you utilizing? Amazon Cognito Identity JS

Provide additional details e.g. code snippets

Hey, I am trying to implement passwordless login with cognito, where user receives a confirmation code via e-mail. The code is randomly generated in the createAuthChallenge lambda.

Following Use case 25 from https://github.com/aws-amplify/amplify-js/tree/master/packages/amazon-cognito-identity-js:

My app client sends a post request tomy backend /login, which triggers the following function:

export const loginUser = (req, res) => {
  const { email } = req.body;

  const poolData = {
    UserPoolId: MY_USER_POOL_ID,
    ClientId: MY_USER_CLIENT_ID
  };
  const userPool = CognitoUserPool(poolData);
  const userData = { Username: email, Pool: userPool };
  const cognitoUser = new CognitoUser(userData);

  const authenticationData = { Username: email };
  const authenticationDetails = new AuthenticationDetails(authenticationData);

  cognitoUser.setAuthenticationFlowType('CUSTOM_AUTH');

  cognitoUser.initiateAuth(authenticationDetails, {
    onSuccess: function(result) {
      console.log('SUCESS', result);
    },
    onFailure: function(err) {
      console.log('ERROR', err);
    },
    customChallenge: function(challengeParameters) {
      let challengeResponse = challengeParameters.ANSWER;
      cognitoUser.sendCustomChallengeAnswer(challengeResponse, this);
      res.json(challengeParameters);
    }
  });
};

The problem is in customChallenge callback - this way I get successfully authenticated, onSuccess callback gets called and tokens are logged to the console. However, I do not want to already automatically sendCustomChallengeAnswer based on challangeParamaters. This should depend on the answer user fills in.

If I would send another post request to /loginwith a code parameter in the request body, then I will always receive an error:

{
   code: 'NotAuthorizedException',
   name: 'NotAuthorizedException',
   message: 'Incorrect username or password.'
}

This happens because the lambda initiates a new session with a new challange Answer code which is not equal to the one im trying to pass in the second POST request.

How can I implement custom auth flow using Amazon Cognito Identity sdk? I do not want to use Amplify.

I also tried this with initiateAuth & respondToAuthChallenge, but then I had an issue with the fact that respondToAuthChallenge() requires a Session parameter which is return by the initiateAuth() method (even though documentation says this is optional) - the Session token is only valid for 3 minutes, so unless there is a way to increase that limit it wont work for my use case:

// Gets triggered when a post request is made to /login:

export const loginUser = (req, res) => {
  const {email} = req.body
    const params = {
      AuthFlow: 'CUSTOM_AUTH',
      ClientId: MY_USER_CLIENT_ID,
      AuthParameters: {
        USERNAME: email
      }
    };

    const identityProvider = new CognitoIdentityServiceProvider();

    return identityProvider.initiateAuth(params, (err, data) => {
      if (err) {
        return next(err);
      } else {
        res.json(data);
      }
    });
  }

// Gets triggered when a POST request is made to /authorize:

  export const respondToChallenge = (req, res, next) => {
  const { username, session, code } = req.body;

  const params = {
    ChallengeName: 'CUSTOM_CHALLENGE',
    ClientId: MY_USER_CLIENT_ID,
    ChallengeResponses: { USERNAME: username, ANSWER: code },
    Session: session
  };

  const identityProvider = new CognitoIdentityServiceProvider();

  identityProvider.respondToAuthChallenge(params, (err, data) => {
    if (err) {
      return next(err);
    } else {
      res.json(
        data.AuthenticationResult
      );
    }
  });
};

Any help or suggestions are very much appreciated, thank you!

sammartinez commented 5 years ago

@KristineTrona Not sure if you stumbled upon this at all but here is a blog post on Cognito for customizing your user pool for authentication flow. This may be the direction to go if you're not looking to use Amplify.

KristineTrona commented 5 years ago

@sammartinez thanks for your answer. I did see this post before and implemented it as you can see in the last snippet - it works, but the issue with it is that respondToAuthChallenge() requires a 'Session' parameter, which is a token that gets generated by the initiateAuth() method and this token is only valid for 3 minutes (also mentioned in that post). So should the user take longer than 3 minutes to go to their mail and retrieve the verification code, then back to app and enter it - the code will no longer be valid.

KristineTrona commented 5 years ago

Actually, I figured out that Amplify Auth works the same way and also has the 3 minute expiration time for the Session (see Timeout of 3 minutes - https://aws.amazon.com/blogs/mobile/implementing-passwordless-email-authentication-with-amazon-cognito/).

So I am guessing my last code snippet is the way to go without amplify. But is there any way to increase the 3 minute timeout?

sammartinez commented 5 years ago

@KristineTrona There is not currently a way to increase the expiration time. I will mark this as a feature request and bring it to the Service Team's attention

KristineTrona commented 5 years ago

Any update on this and weather it could be implemented in the near future?

devTechi commented 4 years ago

Hi @KristineTrona

"expanding" of the code does work, but not with the intended way. You need to cancel the login flow, even everything works as expected within the first attempt. Save some hash in backend (DynamoDB or something like that) and send the code after the first login attempt and challenge answer.

Now you have the code and information (session + salt or something) on both sides and you can login with those information a second time and answer the challenge with given code.

This is a hack, but it works.

rothalex commented 4 years ago

Hi @KristineTrona

I totally agree with the comments above. Currently, there is no way in implementing progressive authentication with a larger TTL than 3 mins using the custom workflow. I had a long and exhausting discussion with senior security specialists from AWS.

In order to realize it I'd also suggest using the solution proposed by @devTechi . You just need to extend the custom workflow with additional logic to store the state of the login process. Thats why you need the DynamoDB. I can confirm that this solution definitely works as intended and in a sophisticated way.

KristineTrona commented 4 years ago

@devTechi @rothalex Thank you for your answers! For the project I was working on at the time it is no longer necessary, but it is good to know this workaround for the future.

nyahan commented 4 years ago

@sammartinez any update on this?

escar commented 4 years ago

Hi, I'm also trying to overcome this issue. Instead of using amplify from the webapp, I have a couple api endpoints to initiateAuth and to respondToAuthChallenge. The idea is to, if the code is expired, generate a new one and validate it immediately.

I didn't get to that point yet though, because I'm having problems authenticating the user. respondToAuthChallenge works fine, it returns the tokens, and I'm saving them manually to localStorage (amplify's auth.sendCustomChallengeAnswer does this internally). However, after this, auth.currentSession() throws with no current user. I may be missing an extra step...

One thing I notice when doing amplify's auth.sendCustomChallengeAnswer, it does a few requests to cognito, with these headers:

I do the 1rst in my api endpoint, but do I also need to the others to complete this manual signin process?

Thank you

mlabieniec commented 4 years ago

Great blog i found that runs through custom otp auth with sms/sns: https://blog.bitsrc.io/building-otp-authentication-with-reactjs-and-aws-amplify-c5fd2e517fac

s1mrankaur commented 3 years ago

Hi @KristineTrona

"expanding" of the code does work, but not with the intended way. You need to cancel the login flow, even everything works as expected within the first attempt. Save some hash in backend (DynamoDB or something like that) and send the code after the first login attempt and challenge answer.

Now you have the code and information (session + salt or something) on both sides and you can login with those information a second time and answer the challenge with the given code.

This is a hack, but it works.

can you expand on this hack a bit more, please?

"You need to cancel the login flow" - How do I cancel the login flow? "Save some hash in the backend (DynamoDB or something like that) "- Is this hash the hash for the code itself or identified for the user login attempt?

Would be great if you could share a bit more information about it.

@rothalex

@devTechi

cwomack commented 1 year ago

This issue appears to be a limitation of the Cognito service tied to the session timeout being capped at 3 min. We have added it to a list of feature requests to review with the Cognito team and will update this issue with any progress.