|
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 17K readers and level up you AWS game with just 5 mins a week.
Lambda Durable Functions makes it easy to implement business workflows using plain Lambda functions. Besides the intended use cases, they also let us implement ETL jobs without needing recursions or Step Functions. Many long-running ETL jobs have a time-consuming, sequential steps that cannot be easily parallelised. For example: Fetching data from shared databases/APIs with throughput limits. When data needs to be processed sequentially. Historically, Lambda was not a good fit for these...
Step Functions is often used to poll long-running processes, e.g. when starting a new data migration task with Amazon Database Migration. There's usually a Wait -> Poll -> Choice loop that runs until the task is complete (or failed), like the one below. Polling is inefficient and can add unnecessary cost as standard workflows are charged based on the number of state transitions. There is an event-driven alternative to this approach. Here's the high level approach: To start the data migration,...
Lambda Durable Functions comes with a handy testing SDK. It makes it easy to test durable executions both locally as well as remotely in the cloud. I find the local test runner particular useful for dealing with wait states because I can simply configure the runner to skip time! However, this does not work for callback operations such as waitForCallback. Unfortunately, the official docs didn't include any examples on how to handle this. So here's my workaround. The handler code Imagine you're...