Homeβ€Ί Blogβ€Ί AWS Architecture Series #78 β€” Revoked, and still accepted…
AWS Architecture AWS Architecture Series

AWS Architecture Series #78 β€” Revoked, and still accepted

Removing a user from a group, or revoking their token, reads like an action that takes effect now. Both are recorded in Cognito. Neither reaches a resource server that validates the JWT locally, which is what a JWT is for and what every API gateway does by default.

Verified against current vendor documentation on 10 October 2026. Pricing, limits and API behaviour were checked against the official docs on that date. Cloud services change fast — if you are reading this much later, treat the specifics as a starting point and re-check the linked sources.

Business Challenge

The identity posts in this series have been about what a policy evaluates — #35 on the evaluation order, #70 on where a trust boundary actually sits. This one is about a credential that carries its own answer, so there is nothing left to evaluate.

AWS says it in one sentence, in the revocation documentation, and it is the whole post:

“User pool JWTs 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 Amazon 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.”

1Revocation is addressed to Cognito, not to your API

Read the two halves of that sentence against how a token is normally checked. Local verification — fetch the JWKS, check the RS256 signature, check exp, check aud and iss — is the recommended pattern, it is what an API Gateway JWT authorizer does, and it involves no call to Cognito at all. That is the point of a JWT.

Which means revocation changes nothing for that path. The signature is still good. exp is still in the future. The library returns valid, correctly, because the token is valid by every property it carries.

So "we revoked their token" and "they can no longer call our API" are two different statements, and only the first one is true at the moment you perform the revocation.

Fix

Decide deliberately whether your resource server checks revocation at all. If it must, that is an introspection call or a cache of revoked origin_jti values on the request path — and that is a real latency and availability decision, not a configuration toggle.

2A group membership is a claim, so removing it changes nothing already issued

“cognito:groups An array of strings. Each string is the name of a user pool group that has your user as a member.”

An array, written into the token at issuance. Remove the user from the group and every token already in their browser still lists it, because a claim is a record of what was true when the token was signed.

The obvious mitigation is a short token life, and AWS allows a genuinely short one: “You can set the ID token expiration to any value between 5 minutes and 1 day”, per app client. Five minutes of stale authorisation is an acceptable exposure for most applications.

Except that it does not do what it looks like it does, which is the next card.

Fix

Treat group membership in a token as authorisation cached for the token's lifetime, and put anything that must revoke promptly behind a check your application performs rather than a claim it reads.

3A five-minute token does not give you a five-minute session

Important, in AWS's own callout: “When your user signs in with managed login, Amazon Cognito sets session cookies that are valid for 1 hour. If you use managed login for authentication in your application, and specify a minimum duration of less than 1 hour for your access and ID tokens, your users will still have a valid session until the cookie expires.”

And the part that closes the loop: “If the user has tokens that expire during the one-hour session, the user can refresh their tokens without the need to reauthenticate.”

So with managed login, a five-minute ID token buys you a token that is replaced every five minutes from a session that lasts an hour. Each replacement is freshly issued, which does help with group changes — and the session floor underneath it is the cookie, not the token.

Fix

Size the token TTL for how stale a claim may be, and understand the managed login cookie as the real minimum session length. They are different numbers answering different questions.

Architecture

Three clocks, one revocation identifier tying two of them to the third, and four operations with very different reach.

Diagram: what an Amazon Cognito user pool token asserts, when it stops asserting it, and which revocation operations reach a whole user rather than a single session. The central statement from the revocation documentation is that user pool JSON Web Tokens are self-contained with a signature and expiration time assigned when the token was created, that revoked tokens cannot be used with any Amazon Cognito API calls that require a token, but that revoked tokens will still be valid if they are verified using any JWT library that verifies the signature and expiration of the token. The consequence is that local verification, which fetches the JWKS and checks the RS256 signature along with the expiry, audience and issuer, involves no call to Cognito and therefore cannot observe a revocation, so a revoked token continues to pass verification at a resource server until it expires on its own. Three clocks govern lifetime. The ID token expiration can be set to any value between 5 minutes and 1 day, per app client. The access token expiration can likewise be set between 5 minutes and 1 day. The refresh token expires 30 days after sign-in by default and can be configured to any value between 60 minutes and 10 years, so the maximum refresh lifetime of 3,650 days is three thousand six hundred and fifty times the maximum one-day lifetime of an ID or access token. Authorisation data travels as claims: the cognito:groups claim is an array of strings naming each user pool group that has the user as a member, and it is always an array even for a single group, so removing a user from a group does not alter any token already issued, because a claim records what was true when the token was signed. A short token life is the obvious mitigation but does not shorten the session, because when a user signs in with managed login Cognito sets session cookies valid for one hour, and specifying a token duration shorter than one hour still leaves the user with a valid session until the cookie expires, during which tokens that expire can be refreshed without the need to reauthenticate. Revocation is keyed on the origin_jti claim, described as a token-revocation identifier associated with the user's refresh token, and when a token is revoked Cognito invalidates all access and ID tokens sharing the same origin_jti value, so revocation operates on a refresh token and its children rather than on a user. Four operations differ in reach: RevokeToken revokes all access tokens for a given refresh token including the initial access token from interactive sign-in, and does not affect the user's other refresh tokens or their ID and access token children; the revocation endpoint behaves equivalently for a given refresh token; GlobalSignOut is a self-service operation a user authorizes with their own access token and revokes all of that user's refresh, ID and access tokens; and AdminUserGlobalSignOut is a server-side operation an administrator authorizes with IAM credentials and revokes all of the target user's refresh, ID and access tokens. So only the two global operations cover a user, and a revocation request must include the client ID that was used to obtain the token. Prerequisites and side effects are recorded: token revocation is enabled by default for a newly created user pool client and refresh tokens can be revoked only in app clients where it is enabled; enabling it adds the origin_jti and jti claims to access and ID tokens, which increases their size; disabling token revocation in a client where it was previously enabled does not make revoked tokens active again; and disabling a user account, which revokes refresh and access tokens, does not reactivate those tokens if the account is later re-enabled. On refresh token rotation, AWS states that rotation reissues a refresh token within the same fixed validity window and does not extend that window, with each new refresh token valid only for the time remaining on the original RefreshTokenValidity period no matter how many times it rotates, illustrated by a 30-day validity where a session rotating daily still expires 30 days after first sign-in rather than 30 days after the most recent rotation, so keeping a user signed in beyond that window requires reauthentication. When rotation is disabled, token-refresh requests return new access and ID tokens only and the original refresh token remains valid. A grace period of up to 60 seconds can be configured for the original refresh token to allow retries, and rotation is incompatible with the REFRESH_TOKEN_AUTH authentication flow, which cannot be used in app clients that have rotation enabled. Two parsing cautions are recorded: the set of claims in a Cognito ID token grows over time as new features add claims, so a token's claims can differ from the documented example, and Cognito generates the sub claim in a Cognito-specific format that does not conform to RFC UUID, so its format should not be strictly validated.
Revocation acts on a refresh token and its children. Local verification never hears about it.

The three clocks

TokenConfigurable rangeDefault
ID token“any value between 5 minutes and 1 day”, per app client—
Access token“any value between 5 minutes and 1 day”—
Refresh token“any value between 60 minutes and 10 years”“expires 30 days after your application user signs into your user pool”

Ten years is 3,650 days against a one-day ceiling on the other two, so the refresh token is the credential that actually determines how long an account stays usable. It is also the one that is least often reviewed, because it is invisible in normal operation — the thing in the browser that keeps working.

Rotation does not extend the window, and AWS is explicit because the opposite is the natural assumption

“Rotation reissues a refresh token within the same fixed validity window. It doesn't extend the window. Each new refresh token is valid only for the time remaining on the original RefreshTokenValidity period, no matter how many times the token rotates.”

With the worked example: “suppose you set RefreshTokenValidity to 30 days and a session rotates its refresh token every day. The session still expires 30 days after the first sign-in, not 30 days after the most recent rotation. To keep a user signed in past that window, they must reauthenticate.”

So rotation is a credential-theft control, not a session-extension mechanism. If you turned it on expecting sliding sessions, the session length did not change β€” and users will be signed out on the original schedule regardless of how active they were.

What origin_jti is for

“origin_jti A token-revocation identifier associated with your user's refresh token. Amazon Cognito references the origin_jti claim when it checks if you revoked your user's token with the Revoke endpoint or the RevokeToken API operation. When you revoke a token, Amazon Cognito invalidates all access and ID tokens with the same origin_jti value.”

That is a clean design: one identifier binds a refresh token to every access and ID token descended from it, so revoking the parent marks the whole family. The word doing the work is invalidates, and its scope is Cognito's own view — which is where the first challenge card came from.

It also tells you what to cache if you decide your API must honour revocation: origin_jti, not the token.

Four operations, two of which cover a user

OperationReachAuthorised by
RevokeTokenOne refresh token and its children. “doesn't affect any of the user's other refresh tokens”Client ID plus the token
Revocation endpoint“a given refresh token and all ID and access tokens that the refresh token generated”Same
GlobalSignOut“all of the requesting user's refresh, ID, and access tokens”The user's own access token
AdminUserGlobalSignOut“all of the target user's refresh, ID, and access tokens”IAM credentials

The first two are per-session. A user signed in on a laptop, a phone and a CI job has three refresh tokens, and RevokeToken on one of them leaves the other two working — which is correct for "sign out this device" and wrong for "this person has left".

For that, the operation is AdminUserGlobalSignOut, because it is the only one of the four an administrator can perform against a user without holding that user's token. And note what GlobalSignOut requires: “a user authorizes with their access token” — so it is a self-service operation and not an offboarding tool.

One operational detail that will fail a first attempt: “Your request to revoke a refresh token must include the client ID that was used to obtain the token.” In a pool with several app clients, revoking means knowing which client issued the credential.

Why This Architecture Holds Up

Revocation state is one-way, which is the right choice

Two statements that together make the feature trustworthy:

“When you disable token revocation in an app client where it was previously enabled, revoked tokens don't become active again.”

“When you disable a user account (which revokes refresh and access tokens), the revoked tokens don't become active if you enable the user account again.”

Both close a reactivation path that would otherwise be a genuine hole — re-enabling a suspended account would have resurrected whatever was in the browser at suspension time. It is worth noting because the opposite behaviour is easy to implement by accident, and AWS went out of its way to document that it did not.

Enabling revocation changes the token, and the docs say so

“After you enable token revocation, new claims are added in the Amazon Cognito JSON Web Tokens. The origin_jti and jti claims are added to access and ID tokens. These claims increase the size of the application client access and ID tokens.”

A small thing with a real failure mode: tokens travel in headers, and headers have limits. A pool that turns on revocation and starts sending slightly larger JWTs through a proxy sized for the old ones has a change with no obvious cause. The remedy is to know it happened, which is all this paragraph is for.

Worth checking rather than assuming: “When you create a new user pool client, token revocation is enabled by default”, and “You can revoke refresh tokens only in app clients with token revocation enabled.” New clients are covered; a pool that predates the feature may have clients where the operation simply is not available, and the time to find that out is not during an incident.

Rotation forecloses an authentication flow, and the incompatibility is mutual

“Refresh token rotation isn't compatible with the authentication flow REFRESH_TOKEN_AUTH”, and from the other direction: “You can't authenticate with REFRESH_TOKEN_AUTH in app clients with refresh token rotation enabled.”

REFRESH_TOKEN_AUTH through InitiateAuth is how a great deal of existing SDK code refreshes a session. Turning on rotation in an app client those SDKs use breaks that path, and the replacement is GetTokensFromRefreshToken. This is a client-code change, not a setting β€” so rotation is a migration rather than a toggle.

The compensating control for the race it introduces: “To allow for retries for a brief duration, you can also configure a grace period for the original refresh token of up to 60 seconds.” Which exists because two concurrent requests both refreshing at once is a real pattern, and without a grace period one of them loses.

With rotation off, the refresh token is long-lived and unchanging

“When this setting is disabled, token-refresh requests return new access and ID tokens only and the original refresh token remains valid.”

Combine that with a configured validity measured in months or years and the shape is clear: one stolen refresh token is a durable credential that mints working access tokens for as long as the window lasts, and nothing about using it looks anomalous. That is the case rotation exists for, and it is why the migration cost above is usually worth paying.

Two parsing cautions, both unusual to see documented

“The set of claims in a Amazon Cognito ID token grows over time as new features add new claims, so the claims that your tokens carry can differ from this example.” So a parser that rejects unexpected claims will eventually break on an AWS feature release rather than on a change of yours.

And: “Amazon Cognito generates sub in an Amazon Cognito-specific format that doesn't conform to a specific UUID format, including RFC UUID. You shouldn't strictly validate the format of sub.” A UUID-shaped string that is not a UUID, with an explicit instruction not to validate it as one. Worth obeying literally.

Key Architecture Decisions

Decision Choice Reasoning
Whether your API honours revocation Decide it explicitly, and write it down Local JWT verification cannot see a revocation; AWS states revoked tokens “will still be valid” to a verifying library.
If it must honour it Cache revoked origin_jti values, not tokens One origin_jti covers a refresh token and every child of it.
Offboarding a person AdminUserGlobalSignOut The only operation an administrator can run against a user's whole token set with IAM credentials.
Signing out one device RevokeToken or the revocation endpoint Scoped to one refresh token; other sessions are explicitly unaffected.
Group-based authorisation Treat cognito:groups as cached for the token lifetime It is a claim fixed at issuance, so removal is not retroactive.
ID and access token TTL Size it as "how stale may a claim be" 5 minutes to 1 day, and it is the staleness bound, not the session length.
Managed login sessions Treat the 1-hour cookie as the session floor A shorter token still leaves “a valid session until the cookie expires”, refreshable without reauthentication.
Refresh token validity Review it — it is the real session length Default 30 days, configurable to 3,650; the one-day ceiling applies only to the other two.
Refresh token rotation Enable it, and plan it as a client migration It forecloses REFRESH_TOKEN_AUTH, so SDK code must move to GetTokensFromRefreshToken.
Rotation grace period Set one, up to 60 seconds Concurrent refreshes are normal, and without it one request loses.
Expecting rotation to extend sessions Do not — it is explicitly fixed-window “It doesn't extend the window”, with a worked 30-day example.
Token parsing Tolerate unknown claims; do not validate sub as a UUID Claims “grows over time”, and sub is documented as not RFC-conformant.
Pre-existing app clients Confirm EnableTokenRevocation before relying on it Default on for new clients only; revocation is unavailable where it was never enabled.

The audit worth running

For each app client, write down four numbers and one boolean: ID token TTL, access token TTL, refresh token validity, rotation grace period, and whether token revocation is enabled. The refresh validity is the one most likely to be a default nobody chose, and it is the number that answers "how long can a departed employee's browser keep working".

Then answer the question this post exists for: when your API receives a token, does anything on that path consult Cognito? If the answer is no — and for a JWT authorizer it is no — then your revocation latency is exactly the access token TTL, whatever your runbook says.

Closing Thought

None of this is Cognito behaving badly. A self-contained token that can be verified without a network call is the entire value proposition of JWTs, and the cost of that property is that nothing can be withdrawn mid-flight. AWS implements the only revocation that is possible under those constraints, documents its boundary in a single clear sentence, and provides origin_jti so you can build the rest yourself if you need it.

The gap is linguistic rather than technical. "Revoke" and "remove from group" are verbs that sound instantaneous, and both are recorded instantly — in the user pool. Whether they take effect anywhere else is a property of the verifier, which is code you wrote and probably did not think of as part of your revocation story.

Which is a different shape from the four posts before it. #74, #75, #76 and #77 were all a number answering a question next to the one being asked. This one is an action taking effect in a system next to the one you meant — and it is the more dangerous variety, because a number that reads wrong gets argued about, while an action that reads done gets closed.

Next in this series

Security & Identity — Amazon Verified Permissions: what Cedar evaluates that an IAM policy cannot, why a policy store's schema is the thing that actually constrains authorisation, and how a deny is reached when no policy matches.

Comments

How was your experience?
Your feedback helps improve this site.
PoorExcellent