amplify-js: cognito.user.signOut() does not invalidate tokens

Describe the bug On calling state.cognito.user.signOut(), session tokens are just removed localstorage. The actual access tokens and refresh tokens are still valid for the lifecycle of the token.

Expected behavior This is a security issue. Best practice dictates session tokens should be invalidated server side on a logout request not just deleted on the client. (OWASP session management)

Source Code found here https://github.com/aws-amplify/amplify-js/blob/68a5ad2fe2b1d9f03cce80d1bf449e454b621760/packages/aws-amplify-react/src/Auth/SignOut.jsx

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 118
  • Comments: 114 (11 by maintainers)

Most upvoted comments

We are all waiting for it to be fixed.

@jiachen247 Cognito issues short lived bearer access tokens (valid up to 1 hour). The access tokens are short lived (up to 1 hour) and Cognito has GlobalSignOut Api to invalidate all tokens issued in past. If you are using the cognito-identity-js sdk directly, then the globalSignOut method will invalidate all sessions (see use case #15 here: https://github.com/aws-amplify/amplify-js/tree/master/packages/amazon-cognito-identity-js). This i believe is what you are looking for to invalidate the server side. The user logout is specifically there as to not invalidate other sessions and just sign out the current local user.

cognitoUser.globalSignOut(callback);

@mlabieniec Thanks for your response! yes and no actually. Its closer but its still different. Global sign out invalidates all open sessions. For example, lets say I log into the webapp and a mobile app both use Cognito for authentication at the same time. On global signout, both will get logged out. However, signout should just invalidate that particular session on the client side as well as the server side and not touch the other open sessions. That makes the most sense on reading the docs for the function is intended for.

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.

We’re at nearly 9 months since any update from AWS on this (@sammartinez on Jan 24). As others have stated, this is not something that can properly be fixed other than directly by the AWS team. ProdigySim hit the nail on the head further up the thread on this:

Developers using Cognito are forced to choose between good security and good user experience here.

We’re a multi-platform app, and the inability to handle this token revocation on a case-by-case basis is extremely detrimental to our users’ experience. Sadly, there are many things missing with Cognito that force us to either offer a poor UX, or compromise on security in someway; the other example we’ve run across is that there is no endpoint for re-sending a MFA code. Suggestion is simply ‘hang on to the username and password and call login again’, which just shouldn’t be necessary.

Any chance of an update for this? I’m not sensing any commitment to actually fix this issue. This is a commercial product (admittedly with a rather generous free tier currently!), not an open source community library, so I would expect a little more interest from AWS on this… not least because Cognito is one of the ‘gateway drugs’ into the AWS ecosystem. If there’s this little attention paid to basic functionality in Cognito, it worries me the amount of workarounds and step outs we’re going to need for basic functionality in other parts of our AWS infrastructure moving forwards.

TLDR; grumble grumble. Any chance of an update? 😄

Hello all! I wanted to provide an update on this feature. We are actively working on it and will release this feature in the coming months. While I can’t give an exact date when this feature will be out, I just want to state that were actively working on it for everyone. Stay tuned for more updates!

Any news on this? It seems ridiculous that you can’t invalidate a single set of tokens on the back end.

Is there some API endpoint we can hit to invalidate a single Refresh token? Auth.signOut({ global: true }) revokes all refresh tokens, and Auth.signOut() revokes no refresh tokens…

There must be some middleground available…

Thanks, @pinpointpanda! We’re very committed to addressing the concerns on this thread. We’re currently working behind the scenes on this. Sorry for saying stay tuned, but, please stay tuned! We will provide an update here as soon as we can.

Is there someone to actually answer these issues/questions? Too frequently I see only tags added and more people writing they have the same problem. No actual support whatsoever and that’s sad.

I also need the ability to:

  • sign out a single user session (only this one with which the sign out is requested) followed by the token invalidation
  • get information (an event?) that the session was invalidated (for example with a global sign out), so I could send the user back to the login screen, as the token is not usable nevertheless, but all Amplify functions don’t seem to care, just throw errors.

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.

I could not agree more with @jiachen247. I have the same situation of mobile and app usage and the session should be invalidated for each one separately. What actually concerns me more is the fact that I can still use the token after using the “signOut” method of @aws-amplify/auth.

Yes, i am facing the same issue. I am using Auth.SignOut({global: ture}) and it is kicking user to login page of that particuler window. If i open the app in another tab in the same window / access the app from other browser or machine they still find a valid user object from id token. I can still get hold of the application.

I am looking for a solution where if i logout from other tab and come back to another tab and do any operation, it should kick me back to login page.

Any workaround solution for this?

any updates from?

We still need a way to invalidate an individual refresh token. I don’t want to use Global Sign Out because a user might want to be signed in on multiple devices but just want to sign out of one specific device. It would be a poor user experience.

The current system relies solely on each device just “forgetting” the refresh token if a user logs out on that device. This is not a very secure or reliable way to discard refresh tokens because the tokens themselves remain usable. If you do the forgetting wrong, then the user stays logged in without realizing it. API calls using the “logged out” session still return 200, and so forth.

We encountered the same problem with the AWS Cognito PHP SDK. It seems the documentation is clear for the AdminUserGlobalSignOut function :

Signs out users from all devices, as an administrator. It also invalidates all refresh tokens issued to a user. The user’s current access and Id tokens remain valid until their expiry. Access and Id tokens expire one hour after they are issued.

https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminUserGlobalSignOut.html

A much-needed feature would be to invalidate all tokens on AdminUserGlobalSignOut. Our use case : a user password as been changed by an admin or user, thus the user should be logged out automatically. By the documentation, the user would only be disconnected one hour after…

@owenashurst Statements about the Cognito team working on issues are also no guarantee that the issue will be resolved in any sort of timeframe, or at all. There are many many basic features that aren’t supported in Cognito (Removing custom attributes, password expiry etc) - that have “Been raised to the Cognito team and are being worked on internally” that are still undelivered after 4 years of disappointment. Some of them are fundamental security items (like logging out properly) Based on previous experience - you don’t keep this issue open - don’t expect it to ever be done.

+1, it is unbelievable that such a critical feature is still not available, even though it’s now been raised publicly almost a year ago, and received a significant amount of interest from your customers.

@s1mrankaur my experience is consistent with the AWS documentation at https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html

JWT tokens are self-contained with a signature and expiration time that was assigned when the token was created. Revoked tokens can’t be used with any Cognito API calls that require a token. However, Revoked tokens will still be valid if they are verified using any JWT library that verifies the signature and expiration of the token.

So if you try to use an access token associated with the revoked refresh token to make a Cognito API call like GetUser, it will fail. However, if you make a REST API call to an API Gateway that uses the built-in Cognito authorizer, and you pass the ID token as a bearer token in the Authorization header, it will succeed - the API Gateway authorizer isn’t doing revocation checks on the JWT.

This is unbelievable! API Gateway 😦(

I am keeping this thread alive because it is AWS’s job to implement this. They are the only ones who can implement a true invalidation measure for refresh tokens because it is possible to call the token refresh API directly if you know the user pool and client id (and client secret if needed). Global sign out is a poor user experience, period. It is a copout to say we should use it as a workaround indefinitely and keep inconveniencing users. It would be dead simple for AWS to put invalidated refresh tokens in a DB belonging to each user pool and check against it for each token refresh request. They just do not care enough to implement it. I first opened a ticket about this more than 2 years ago. All it takes is one general XSS attack script aiming at refresh tokens to gain renewable user sessions for many/most Cognito users, lasting between 1 day and 10 years. Sounds lucrative to me. Only those who keep their user pool etc. totally secret are safe, but you have to have your own API wrapper around login etc. to do that.

I’m not sure if anyone mentioned this already, but it appears you can limit the token expiration now in Cognito all the way down to 5 minutes. Can anyone at Amazon confirm that this would override the default 60 minutes? While it’s not a fix for this thread, it should help limit the security vulnerability for sensitive apps.

Screen Shot 2021-03-08 at 8 51 01 AM

Thank you to everyone with the feedback and concerns presented in this issue. I apologize that we have not made comments on this issue and I do want to state that we are working internally with the AWS Cognito team on this specific issue. Once we have an update from Cognito regarding this issue, we will provide one. Again, thank you for your concern and feedback.

At this point, AWS should just let us in and do it for them.

It’d be great if the Cognito SDK supported the strategy for mobile apps described in the top answer in this SO question: https://stackoverflow.com/questions/26739167/jwt-json-web-token-automatic-prolongation-of-expiration.

Basically have a way to invalidate a specific refresh token given the device ID where it was generated from. Looking at the current Cognito API it seems like it wouldn’t be that hard to support right? Given that globalSignOut already exists.

Tokens should be invalidated once they are known to be unused.

A user authenticates against my user pool across multiple devices: browsers, mobile phones, public terminals. They get a separate Refresh Token for each device. If they manually click “log out” on one of those devices, the user will drop its reference to the refresh token. The token will be unused.

If an attacker manages to sniff the refresh token from a public machine, our users should be able to click “Log Out” manually to mitigate having their token compromised.

There is a workaround to do this today: It’s to use the signOut({ global: true }) option. This will revoke ALL refresh tokens for the user. This accomplishes the security goals of revoking the unused token, but it simultaneous logs the user out across all devices.

Developers using Cognito are forced to choose between good security and good user experience here.

Is there some reason Cognito is unable to revoke a single refresh token? It sounds like adding a new API to revoke a token could solve this problem, backwards compatibly. I can only imagine there must be some blocking issue in Cognito’s end. Perhaps token lifetime for Cognito is handled by some other upstream project that does not give visibility into individual tokens?

By their nature, there is no mechanism to invalidate an access or id token.

That’s my understanding of JWTs as well. However, I’ve seen evidence that Cognito tracks individual access tokens.

If you do a global signout, but save your JWT tokens, and then try to hit another Cognito endpoint (like “global signout” again), you’ll get a 400 with the message Access Token has been revoked. This suggests that Cognito is in fact tracking revocation of individual access tokens in some way.

This is not correct, after global signout, if you send the idToken to an API using Cognito authorizer, the request is still valid.

@RubberChickenParadise that’s an excellent, clear description of the inherent design challenge here, and it highlights the big remaining gap in the Cognito feature set - the ability to explicitly check a token for revocation when the risk is high. Obviously this is possible, since the Cognito APIs reject JWTs associated with revoked refresh tokens. I’d love to see an API that let the rest of us check revocation as well.

@s1mrankaur my experience is consistent with the AWS documentation at https://docs.aws.amazon.com/cognito/latest/developerguide/token-revocation.html

JWT tokens are self-contained with a signature and expiration time that was assigned when the token was created. Revoked tokens can’t be used with any Cognito API calls that require a token. However, Revoked tokens will still be valid if they are verified using any JWT library that verifies the signature and expiration of the token.

So if you try to use an access token associated with the revoked refresh token to make a Cognito API call like GetUser, it will fail. However, if you make a REST API call to an API Gateway that uses the built-in Cognito authorizer, and you pass the ID token as a bearer token in the Authorization header, it will succeed - the API Gateway authorizer isn’t doing revocation checks on the JWT.

@sammartinez Any updates on this issue?

By their nature, there is no mechanism to invalidate an access or id token.

That’s my understanding of JWTs as well. However, I’ve seen evidence that Cognito tracks individual access tokens.

If you do a global signout, but save your JWT tokens, and then try to hit another Cognito endpoint (like “global signout” again), you’ll get a 400 with the message Access Token has been revoked. This suggests that Cognito is in fact tracking revocation of individual access tokens in some way.

A much-needed feature would be to invalidate all tokens on AdminUserGlobalSignOut. Our use case : a user password as been changed by an admin or user, thus the user should be logged out automatically. By the documentation, the user would only be disconnected one hour after…

By their nature, there is no mechanism to invalidate an access or id token.

The likely solution for your scenario is to track any revoke token events in your app. Then, as part of your token validation process, you can check to see if you’ve revoked that specific token and if so not authorize the request. You could maintain a database table of revoked tokens, cache the values for performance and have a hygiene process to remove expired, revoked tokens.

Your use-case / scenario is different from the original issue.

@leonardopaiva the issue people are running into now is an issue inherent to the design or Json Web Tokens. The design goal was to have something that could be issued by a Security Token Service and then validated without a micro service needing to call the STS to validate the token. To do that JWTs are cryptographically signed with an expiration date. The micro service can validate the signature, check the expiration, and say “yep token is still good, off you go to what ever protected resource”.

The “fix” is to identify super critical resources and have that service validate the JWT with the STS. Most of the time, a lag of 5 to 60 min won’t matter. Adding items to a cart for example. Checking out is an example where you would want to validate the token with the STS.

Is there an answer to this? We want to immediately invalidate all tokens and not wait an hour. We want people signed out of all devices.

any good news about this?

Any updates?!

Thanks for the update @jpignata - will keep my 👀 on the thread!

This is a long thread, so sorry if someone has already written about this.

What I wanted to share is the the opposite of this issue is also a problem: If a user is programatically added to a Cognito group, the permissions don’t take effect until the user logs off/on.

In brief, if a graphQL type is setup with an @auth directive, like this (in schema.graphql):

type User
  @model
  @auth(
    rules: [
      { allow: owner }
      { allow: groups, groups: ["Managers"], operations: [read, update] }
    ]
  )
  @key(name: "byCompany", fields: ["companyId", "id"]) {
...

And then the user is added to the “Managers” group via a (node) lambda, like this:

const addToManagerGroup = (userName, userPoolId) => {
  return new Promise((resolve, reject) => {
    try {
      const params = {
        GroupName: "Managers",
        UserPoolId: userPoolId,
        Username: userName
      };
      const data = cognito.adminAddUserToGroup(params).promise();
      resolve(data);
    } catch (error) {
      reject(error);
    }
  });
};

If the user in the “Managers” group tries something like the following, without first logging off, after being added to the “Managers” group, the query is empty (unless they’re the owner, which they won’t be):

let { data } = await API.graphql(
      graphqlOperation(queries.listUsers, {
        filter: {
          companyId: { eq: companyId }
        },
        limit: 50
      })

After logging off/on, the the above query returns values as desired/expected.

If there is a way to allow this, without forcing a signOut, I’d love to learn what that is.

In that case this thread needs to be closed to stop the spam since it’s now been verified that’s it’s being worked on internally.

This repo uses a stale bot to close issues. So, if we stop bumping the issue, it gets closed. I’d rather see the issue stay open personally, but I am not a maintainer.

JWT tokens are self-contained with a signature and expiration time that was assigned when the token was created. Revoked tokens can’t be used with any Cognito API calls that require a token. However, Revoked tokens will still be valid if they are verified using any JWT library that verifies the signature and expiration of the token.

Working workaround: GlobalSignOut invalidates refreshToken and accessToken, but not idToken that’s why API Gateway calls with cognito authorizer still work. I send idToken at the Authorization header but in places where i need high security I send accessToken in the body/param too, and I do a cognito call of GetUser method which takes accessToken as param and returns user info, but if the token is invalidated it returns An error occurred (NotAuthorizedException) when calling the GetUser operation: Access Token has been revoked So if the response tells you it has been revoked, you will sign out the device directly.

(even if the Cognito team would invalidate idToken, the API Gateway team does simple checks of the sign and expiration date of the token so you would still need the workaround)

I’m having a problem where a user logs out in a browser extension, invalidating their access token which is shared by the website associated with the extension. The token is successfully revoked, however the Auth API doesn’t seem to know what to do when it gets an error from the API.

Screen Shot 2021-11-29 at 11 46 09 AM

Ideally the amplify/cognito/auth library would react to this error automatically by removing the access token from storage and logging the user out, since they clearly aren’t logged in anymore. Instead my user is stuck in an in-between state where they’re “logged in” but calls to Auth.currentAuthenticatedUser() et al fail. User can’t do anything and can’t log out.

Any updates?

A related issue (see the closed issue above, I was referred here), if I remove a user from a user group, the JWT they have is still valid and showing that they are a member of that group. As a result, Amplify/AppSync continue to give them access to data and operations that they should not. I need some way to invalidate the JWT server side.

I wrote an article approaching this, so if someone has reached this page, the article may help.

https://medium.com/javascript-in-plain-english/serverless-things-i-wish-i-had-known-before-i-started-part-1-aws-cognito-cf5d3a0c3d9d

If you do a global signout, but save your JWT tokens, and then try to hit another Cognito endpoint (like “global signout” again), you’ll get a 400 with the message Access Token has been revoked. This suggests that Cognito is in fact tracking revocation of individual access tokens in some way.

I thought that when you do Global Signout, secret key(or private key and with that also public key) is changed and every token that was created before calling Signout is invalid. That would mean that validation of each issued token fails. But only thing that is invalid is refresh token. Access token and ID token remain valid until their expiration. More here: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html

Beside that, there is no way to invalid token. You can only wait for token to expire and become invalid. There is a way to track by yourself which tokens should be revoked(after user logout put that token in database) and to check every time if token that is being used currently is in that database(that means it’s invalid) or not(it is valid). But by doing this, you are loosing tokens stateless feature. More about this you can find here(maybe best approach gave Ashtonian): https://stackoverflow.com/questions/21978658/invalidating-json-web-tokens

This is the best way to deal with everything in Cognito: https://dzone.com/articles/aws-cognito-user-pool-access-token-invalidation-1

Can anyone on this thread confirm that “Amazon Cognito now supports token revocation and Amplify (from version 4.1.0) will revoke Amazon Cognito tokens if the application is online.” as per here actually works? After upgrading amplify/auth to

npm i aws-amplify@4.1.0 npm i @aws-amplify/auth

there is no invalidation.

Also:

Is there an answer to this? We want to immediately invalidate all tokens and not wait an hour. We want people signed out of all devices.

Yes this would be extremely helpful from a security perspective.

Hello all, thank you for the continued feedback on this feature request. We’ve communicated it as a priority to the Cognito team and they have now released Token Revocation this week.

We’ve partnered with them to implement it inside Amplify libraries at launch when calling Auth.signOut(). You can read more about this in the documentation. We are continuing to work with the Cognito team to address any additional feedback. If there are additional feature requests related to this please do open up a new issue.

A reply from AWS Technical Support.

Hello,

Thank you for contacting AWS Premium Support. I am Ayushi and I will be assisting you today.

I understand you want to know if we are aware of the  issue that`globalSignOut()` or`signOut()` does not immediately invalidate the token of the Cognito user. Therefore, the user would still be able to access the protected resources using existing tokens. Please correct me if I misunderstood.

From the documentation[1] of the `globalSignOut()`, "This method signs out users from all devices. It also invalidates all refresh tokens issued to a user. The user's current access and Id tokens remain valid until their expiry. Access and Id tokens expire one hour after they are issued". 

As the documentation clearly states that after the token is invalidated, the current tokens are still be valid for 1 hour from the time the tokens are issued. This means that When you perform signOut() or `globalSignOut()`, it just clears the tokens from local cache. However, if the tokens stored somewhere else, it is possible that you could still access the protected resources using the tokens until the current token expires. ie, after 60 minutes of token issue time. I understand this causes some issues such as user would still be able to access the protected resources after they are logged out. Unfortunately, at this moment there are no potential workarounds available to get around this behavior.

As one of our member from service team commented on the issue[2], Cognito service team is working on this feature internally. I understand that the lack of the feature is causing inconvenience. I have added your voice to it along with the rest of our customers that are being actively requesting on this. Please be assured that the service team will take the feedback seriously and work towards improving the Cognito service for better customer experience. However, I will not be able to provide any ETA at this moment as a support engineer, I do not have visibility into service team priorities. Meanwhile, You could keep an eye on our what's new blog [3] for information regarding new feature releases.

Please feel free to reach out to us if you have further queries and I'll be happy to assist you on the same.

References
-------------
[1] https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_GlobalSignOut.html

[2] https://github.com/aws-amplify/amplify-js/issues/3435#issuecomment-578206493

[3] https://aws.amazon.com/new/

Waiting this issue to be resolved. AWS API needs to be have a single token revoked instead of forcing the global sign out for all the tokens.

This is correct, JWT tokens are signed and this check is valid until they have expired. Invalidation means an out of band disabling of a token before the expiration time has passed even with a valid signature. This feature is not available in Cognito today however we have passed this request to the service team and they are evaluating it in their roadmap. I’ll mark this issue as a Feature Request for the time being.

@murdaneta I cannot send the RevokeTokenCommand, there is an Error: Credential is missing. I’m using userpool of Cognito, the signUp, signIn, refreshToken works fine but error occurs at signOut function Screen Shot 2023-06-20 at 19 13 58

This is providerClient variable: Screen Shot 2023-06-20 at 19 16 23

This is signOut function Screen Shot 2023-06-20 at 19 16 37

Do I miss something?

Thanks for this @ProdigySim

I’ve looked at that, and, frankly, I couldn’t find any solid docs or examples that enabled me to learn exactly what I should do with InitateAuth().

At the risk of sounding overly whiney, I wish that Amplify would provide an auth method, like Auth.refreshTokens(), but I assume there are reasons why they haven’t (yet).

Anyhow, while potentially off-topic to this thread, what I would up doing–and it appears to work thus far–is to call the following after any changes are made to a user in Cognito that would require the user to have updated permissions based on those changes:

const cognitoUser = await Auth.currentAuthenticatedUser();
const session = await Auth.currentSession();
const refreshToken = session.getRefreshToken();
cognitoUser.refreshSession(refreshToken, (error, session) => {
  if (error) {
    console.warn(error);
  } else {
    console.log("🙆 Tokens refreshed!", session);
  }
});

This prevents me from having to force a signOut.

@kimfucious Have you tried using the InitiateAuth action with REFRESH_TOKEN auth to generate new Identity & Access Tokens? Groups & other claims should be on the Identity token, which can be refreshed on demand with this endpoint. You may want to open a separate issue if that does not work.

What do you think about this workaround? - https://forums.aws.amazon.com/thread.jspa?messageID=879594

+1 waiting for some news as well

Facing the same issue with cognito-identity for JS. user.signout() just removes the token from local storage. Anyone who had preserved the id token can still connect to the API gateway with it. Any updates or resolution around this ?

Only way(for now) that is verified from AWS technical support team is explained here: https://dzone.com/articles/aws-cognito-user-pool-access-token-invalidation-1

So, idea is to user CognitoIdentityServiceProvider. More about all its methods here: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html

CognitoIdentityServiceProvider.globalSingOutUser(accessToken) revokes access token CognitoIdentityServiceProvider.getUser(accessToken) either return error if token is revoked or users data if token is valid(not expired or revoked)

So, in your case, each time when user sings out(logs out) from application, you should call this globalSingOutUser(accessToken) method. And in case you want to check if user is singed out or not(if token is revoked or not) you should be checking also access token using this getUser(accessToken) method. That should be done before calling API Gateway. And if you are worried about someone calling API Gateway with just ID token, you can add this check inside for example Lambda(if API Gateway is calling Lambda afterwards). I don’t know whole scenario that you are facing with, but you can find some workaround with this.