amplify-flutter icon indicating copy to clipboard operation
amplify-flutter copied to clipboard

Preventing multiple simultaneous logins with Cognito

Open miguelflores1993 opened this issue 4 years ago • 15 comments

We have React Native app that uses Cognito for authentication. We would like to prevent the same user ID from logging in simultaneously from multiple devices.

The other idea was to reject the login if the user is logged in elsewhere. But we can't see a reliable way to tell whether the user is already logged in. We can see if there are valid tokens issued for that user but not if they are currently associated with an active session.

miguelflores1993 avatar Dec 13 '21 13:12 miguelflores1993

One way (perhaps slightly inefficient but probably nobody will notice) to achieve this, is to do a global sign out (1) every time the user signs out, (2) before each sign-in. (Global sign out signs out the user from all devices).

  await Amplify.Auth.signOut(options: SignOutOptions(globalSignOut: true));

dorontal avatar Dec 13 '21 16:12 dorontal

Hi @flutteresbolivia thanks for starting this discussion. I think @dorontal 's recommendation is reasonable in some occasions.

You may also leverage the "remember devices" feature provided by Cognito.

The basic idea is, when a user signs in in a device, you can invoke Amplify.Auth.rememberDevice() to remember currently signed in device (this requires configuration in the user pool, please refer to the document).

The service will keep a list of remembered devices for the same user. You can implement a post-authentication Lambda hook with user pool, to count how many remembered devices that have been associated with the signing in user, if it's more than 1 (reference here, see the "Limiting devices per user" section), you can fail the call. And send an error back to your client to prompt user either cancel signing in, or sign out other devices manually or automatically using the global sign out API.

For this use case, if your App invokes Amplify.Auth.rememberDevice() on sign in, don't forget to invoke Amplify.Auth.forgetDevice() on sign out.

HuiSF avatar Dec 13 '21 17:12 HuiSF

@dorontal I've asked for a similar feature in issue #418

it would be amazing to have. we are currently using the workaround that @HuiSF has mentioned but its messy and requires action from the end user. having this feature would mean it would automatically sign them out of all other devices once they login to a different device.

fdarsot avatar Dec 16 '21 02:12 fdarsot

Thanks for the follow up @fdarsot , we will look into providing an API does this process automatically. I will close the linked issue (https://github.com/aws-amplify/amplify-flutter/issues/418) in favor of this issue. Will track the progress here.

Summary Looking into providing an API does "global sign in" that prevents signing in on multiple devices.

HuiSF avatar Dec 16 '21 17:12 HuiSF

Thank you! @HuiSF is there a timeline we can expect? i raised that feature suggestion back in March, 2021

fdarsot avatar Dec 17 '21 04:12 fdarsot

I get to the apartment at 2:30-3pm after which I'm free. Just read that the numbers are going up super fast. It's a bus trip.  maybe it's time to be more careful? Let's touch base tomorrow.  -D -------- Original message --------From: fdarsot @.> Date: 12/16/21 11:25 PM (GMT-05:00) To: aws-amplify/amplify-flutter @.> Cc: Doron Tal @.>, Mention @.> Subject: Re: [aws-amplify/amplify-flutter] Preventing multiple simultaneous logins with Cognito (Issue #1206) Thank you! @HuiSF is there a timeline we can expect? i raised that feature suggestion back in March, 2021

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>

dorontal avatar Dec 17 '21 04:12 dorontal

Hi @fdarsot thanks for the follow up, we will need to look into this feature a bit more (to learn about the scope, impact, performance, configurability etc.) before giving an estimated date. Thanks for your patience and suggestion! In the meantime the workaround I described above is still effective as it's a recommended implementation by Cognito.

We will keep update the progress.

HuiSF avatar Dec 17 '21 17:12 HuiSF

@HuiSF @offlineprogrammer hi there, was wondering if there was any progress to this feature request? any timelines, etc. thank you

fdarsot avatar Mar 24 '22 06:03 fdarsot

Hi @fdarsot , we are not prioritizing this feature request in the short term, and we do not currently have timelines to share. Did you try the recommendation provided above by @HuiSF ?

abdallahshaban557 avatar Mar 24 '22 17:03 abdallahshaban557

@abdallahshaban557 @HuiSF yes we're currently using the workaround by @HuiSF however it is slightly messy and requires client input. Any way we can prioritize this feature? it would help out greatly. i raised a ticket for it long ago as well back in March of 2021.

fdarsot avatar Apr 18 '22 03:04 fdarsot

@fdarsot - we will inform you on this issue once it is prioritized. Thank you for the feedback!

abdallahshaban557 avatar Apr 18 '22 16:04 abdallahshaban557

@fdarsot - Have you tried doing this from the server using a pre-authentication trigger and calling global sign out using the AWS SDK? https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-authentication.html

dnys1 avatar Apr 18 '22 16:04 dnys1

One way (perhaps slightly inefficient but probably nobody will notice) to achieve this, is to do a global sign out (1) every time the user signs out, (2) before each sign-in. (Global sign out signs out the user from all devices).

  await Amplify.Auth.signOut(options: SignOutOptions(globalSignOut: true));

@dorontal This wouldn't really work if you called global sign out before sign in, since at that point the user wouldn't have a valid access token issued yet, or am I missing something?

Vladivost avatar Jun 28 '22 09:06 Vladivost

One way (perhaps slightly inefficient but probably nobody will notice) to achieve this, is to do a global sign out (1) every time the user signs out, (2) before each sign-in. (Global sign out signs out the user from all devices).

  await Amplify.Auth.signOut(options: SignOutOptions(globalSignOut: true));

@dorontal This wouldn't really work if you called global sign out before sign in, since at that point the user wouldn't have a valid access token issued yet, or am I missing something?

Yes this is correct. I am trying to implement this with the c# aws sdk, but to no avail. When I call the global sign out method, it revokes the tokens of the currently authenticated user. I would assume this is something that would come standard with the API as multi sign in is a common problem with authentication.

ImJustThatGuy avatar Sep 11 '22 01:09 ImJustThatGuy

The solution I had in mind uses the AdminUserGlobalSignOut command, which does not require an access token, in the post-authentication trigger (I originally said pre-authentication, but this was incorrect). My post authentication trigger simply looks like this:

const cognito = require("@aws-sdk/client-cognito-identity-provider");

const REGION = process.env["REGION"];
const CLIENT = new cognito.CognitoIdentityProviderClient({ region: REGION });

/**
 * @type {import('@types/aws-lambda').APIGatewayProxyHandler}
 */
exports.handler = async (event, context) => {
  const { userName, userPoolId } = event;

  // Global sign out the user so that this login is unique
  try {
    await CLIENT.send(
      new cognito.AdminUserGlobalSignOutCommand({
        Username: userName,
        UserPoolId: userPoolId,
      })
    );

    console.log(`Successfully signed out ${userName} from all devices.`);
  } catch (err) {
    console.error(`Could not sign out user: ${userName}. `, err);
  }

  return event;
};

Note: This requires setting permissions correctly such that the trigger has permission to call AdminUserGlobalSignOut

Logging in on one device will work then every login after that will invalidate all previous logins. Those users would start receiving NotAuthorizedExceptions when trying to use their access or ID tokens for any operations.

Can you let me know if something like this would work for you? I don't believe we will see any built-in feature for this considering it is already possible via triggers.

dnys1 avatar Sep 12 '22 20:09 dnys1

Hi @dnys1 , thank you for posting this it has been really helpful for me, but I'm afraid that if you use it on the post-authentication trigger then the session that was just created also gets signed out, so if I add this code on post-auth I never get an actual session. Is there anyway to check if theres's already a session open to only run the AdminUserGlobalSignOutCommand inside an if?

For other people looking at this, here's the custom policies I added to get access to AdminUserGlobalSignOut:

[
  {
    "Action": ["cognito-idp:AdminUserGlobalSignOut"],
    "Resource": ["arn:aws:cognito-idp:*:*:userpool/*"]
  }
]

jerocosio avatar Sep 26 '22 23:09 jerocosio

Hi @dnys1 , thank you for posting this it has been really helpful for me, but I'm afraid that if you use it on the post-authentication trigger then the session that was just created also gets signed out, so if I add this code on post-auth I never get an actual session. Is there anyway to check if theres's already a session open to only run the AdminUserGlobalSignOutCommand inside an if?

For other people looking at this, here's the custom policies I added to get access to AdminUserGlobalSignOut:

[
  {
    "Action": ["cognito-idp:AdminUserGlobalSignOut"],
    "Resource": ["arn:aws:cognito-idp:*:*:userpool/*"]
  }
]

I concur, I tried with post auth and it invalidates the tokens as well.

ImJustThatGuy avatar Sep 27 '22 03:09 ImJustThatGuy

Hi @jerocosio @imjustthatguy - sorry for the misinformation. I guess some wires got crossed in my initial testing. After further testing, I agree that the postAuthentication trigger does not provide the proper support to perform a global signout. Oddly enough, neither does the preTokenGeneration trigger. Despite being called before token generation, calling global signOut from there also results in the tokens being invalid on the client.

I will follow up with the Cognito team to see what I am doing wrong or if they have a better suggestion. Thanks for your patience.

dnys1 avatar Sep 27 '22 23:09 dnys1

It appears this is a known limitation of Cognito, but that it is by design. Here is the answer I received:

Token based authentication model (like what Cognito is doing) is meant to be stateless and there is no concept of session tracking like in legacy session-based authentication which tracks sessions with cookies. in other words, there is no way to know that user has signed in already without storing this information and doing your own session management solution. In addition to this, token is self-contained and even after sign-out or revoking tokens, they are still valid until expired (since majority of services will verify token without calling the issuer, token will be verified by just checking the signature and expiration).

The short answer is that, if you want to enforce single-session per user then you need to fall-back to session-based authentication and maintain a server-side managed session. One way to do that with Cognito is to store some information that user has an active session (for example in Cognito Post-Auth trigger store some mapping in DynamoDB that user XYZ has an active session that will expire at time ABC, or store this information in Cache layer with expiration period that match token expiration, don't store the token itself or any sensitive data). Then in Pre-Auth trigger you can check if username has an active session and fail the authentication attempt. You need then to think of how to invalidate this session if user sign-out or would like to switch to another device before active session expiry.

I hope this helps and I apologize that there is not a more efficient solution available. I will be closing this issue for now since it appears to not be in Cognito's purview to change the behavior. Please feel free to continue to the discussion here, though.

dnys1 avatar Sep 29 '22 21:09 dnys1

@dnys1 Has there been any movement/new development or anything that can be done on this?

fdarsot avatar Feb 08 '23 23:02 fdarsot

I wish we had an API to handle this directly. This is a very important feature that needs to be prioritized.

ayobamiseun avatar May 07 '24 06:05 ayobamiseun