The ability to invalidate a user's session with immediate effect is a common enterprise requirement. For example:
However, this goes against how token-based authentication is designed to work. JWT tokens are stateless and are typically short-lived (for security reasons) but can be refreshed with refresh tokens. So, is it possible to invalidate Cognito-issued JWT tokens? The short answer is no. The long answer is yes, you can achieve this effect with some work and some performance overhead. Use short token validityCognito has a GlobalSignOut [1] and an AdminUserGlobalSignOut [2] API. These APIs invalidate a user's ID, access and refresh tokens, and Cognito will no longer accept the invalidated tokens. However, in token-based systems, the token contains the user's claims and is cryptographically signed by the Identity Provider (IdP). The service that receives the token doesn't need to call the IdP to validate it—it just needs to check the signature. So, the IdP has no way of telling the service that the tokens have been invalidated. As such, when you use a Cognito Authorizer with API Gateway or AppSync, the authorizer doesn't know when a user's tokens have been invalidated. Which is why you can't invalidate Cognito-issued JWT tokens. Instead, you can limit how long the JWT tokens remain valid by:
This is the best you can do when you use the built-in Cognito Authorizer with API Gateway or AppSync. Enforce token invalidation in real timeYou can implement token invalidation if you:
This repo [3] demonstrates an approach for implementing this. In this solution, we used a Lambda authorizer with API Gateway. The authorizer has the TTL cache disabled to allow us to reject invalidated tokens immediately. The authorizer uses the aws-jwt-verify [4] library to verify the auth token. To mark a token as invalidated, we set a tombstone for the token in Momento [5]. You can replace Momento with DynamoDB. However, I opted for Momento to ensure a fast (sub-millisecond) lookup, which is important because it needs to be done on every request. When a user is signed out, we set the tombstone record for its token. On subsequent API calls, we check whether the tombstone record exists. If so, we know the token has been invalidated. Here's an example flow: Regarding performance overhead, the Lambda authorizer must run on every request. On a cold start, the invocation (not accounting for init duration) takes 150-200ms. Most of which is attributed to fetching the JWKS from Cognito. You can optimize this by downloading the JWKS for your user pool and loading it from file, like this [6]. On a warm start, the invocation takes ~10-15ms. There are also additional costs to consider:
SummaryInvalidating Cognito-issued JWT tokens isn’t natively supported. But you can mitigate risks by setting short token validity and using the GlobalSignOut APIs to stop new tokens from being issued. For stricter requirements, a Lambda authorizer with a fast datastore (e.g. Momento) can reject invalidated tokens in real time. Whilst it's not a difficult solution to implement, you must weigh the cost and performance overhead of this approach against the security and compliance it provides. If the choice isn't clear, or you need help implementing a similar solution, remember, I'm just an email away. Links[1] GlobalSignOut API [2] AdminUserGlobalSignOut API [3] Demo repo for how to invalidate Cognito-issued tokens [4] aws-jwt-verify library for verifying Cognito-issued tokens [5] Momento |
Join 13K readers and level up you AWS game with just 5 mins a week. Every Monday, I share practical tips, tutorials and best practices for building serverless architectures on AWS.
2024 was the year I got back and amongst the community, and it felt great to be back! Blog I published 33 new blog posts. As a whole, my blog garnered 353k views from 255k visitors. About half of them came through Google search. This is down from 2023... but the decline is offset by more people reading my content through my newsletter nowadays. Most read blog posts: Hit the 6MB Lambda payload limit? Here’s what you can do When to use Step Functions vs. doing it all in a Lambda function How to...
One of my favourite questions from the November cohort of Production-Ready Serverless [1] is, "How do you handle e2e tests involving multiple services across bounded contexts?" In a microservices environment, testing user journeys that span across multiple bounded contexts requires collaboration and a clear delineation of responsibilities. Depending on how your organisation is structured, different teams are responsible for testing parts or the entirety of the user journey. For example... The...
So that's it for this year's re:Invent. Werner delivered his usual insightful keynote and shared some key lessons in dealing with complexity and warning signs to look out for. As Werner said, the number of moving parts is not a good measure of complexity on its own. And that's something so many people get wrong about serverless - that they judge complexity by the no. of components on an architecture diagram. Ok, on with the serverless announcements. Aurora DSQL (pronounced "dee-sequel")...