Aws Cognito: Immediately revoke user token. Could this work? - amazon-web-services

I've read many posts online but to no avail until I came upon this post. I would like to check if my implementation has any loophole or if I've missed out anything?
My Use Case:
Admin logged out a user (UserA) using AdminGlobalSignOut
UserA will be denied immediately - not wait for token's expiration - but on the very next aws resource access
My Implementation:
I uses the Identity Pool to setup temp cred for my authenticated users and sort of as my authorization server. And the critical part when setting up Identity Pool is to set ServerSideTokenCheck = true
My user will authenticate against Cognito and get Aws temp credential via Identity Pool. My user will only have access to aws Lambda, and nothing more. The respective lambda will then have access to the rest of aws resource (e.g. DynamoDB)
Admin do a global sign out to revoke UserA's token. Now ... this is the issue coz tokens are valid for 60 minutes, and with current aws design, signed out user can still access to aws with the revoked token. But with ServerSideTokenCheck, it will always check if the token is signed out. I've 2 invoke methods which I used for my project:
[Direct Lambda Invoke]
When client invoke the lambda function directly (using the temp credential from Identity Pool), immediate the client will get NotAuthorizedException: Token is inactive - Problem solved
[Api Gateway]
a. Add a new custom lambda authorizer
b. In the lambda, call the GetCredentialsForIdentity api with the IdToken. This will give a NotAuthorizedException (code: 400) - Problem solved
I've tested my implementation and it works. The only possible issue is that GetCredentialsForIdentity has a quotas of 200 request per second. Did I missed out any other consideration factors?

Related

Why can I still authorise requests to API Gateway after using Cognito's RevokeToken?

I am using an AWS Lambda function (Node.js 14.x) to call Cognito revokeToken function to revoke a refresh token.
According to the official document, "revokeToken" will:
Revokes all of the access tokens generated by the specified refresh token. After the token is revoked, you can not use the revoked token to access Cognito authenticated APIs.
While I can successfully revoke the refresh token & associated access tokens, previously created access tokens can still be used to access API Gateway resources (with Cognito User Pool authentication enabled).
Why can I still authorise requests to API Gateway after using Cognito's RevokeToken method to revoke access tokens?
Your refresh token & access tokens created using that refresh token are being revoked however, API Gateway will not pick up on this & still allow access.
The reason for this is that API Gateway uses the identity token to authorize API calls, not the access token.
The documentation (very unclearly I must admit) mentions:
After the token is revoked, you can not use the revoked token to access Cognito authenticated APIs.
In this case, what is meant by the above is that the token is revoked for API actions that actually belong to Cognito, not services which use Cognito for authentication.
This is why you can still authenticate with API Gateway as it will internally use the not-revoked ID token.
To demonstrate this, the typical Cognito authentication result will look like this:
{
"AuthenticationResult":{
"AccessToken":"eyJraWQiOiJvZ2JFM2xXN0FWeEpPZjJWRU50MW9RNnRrY3ZOdVRJUUNTZkJpczlBWDFBPSIsImFsZyI6IlJTMjU2In0.eyJvcmlnaW5fanRpIjoiOThjM2QyNTgtYWU1NC00MzBiLWEwNjktYjg4OGZhNWQ5YzkwIiwic3ViIjoiZWE5MGQ2MjktMWI3NS00Y2ZiLThkOWQtZGRmZWJhZGZlZGYzIiwiZXZlbnRfaWQiOiJlODMxZWI1MS1mMjM3LTRhNDctYTM3YS1jNTk5MzBkNzFkZDgiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIiwiYXV0aF90aW1lIjoxNjM0NDcxOTA1jsda56dsf8sd7f7sdf9sdf6hdf8gfdf8dg89dfg8dfgIiwiZXhwIjoxNjM0NTU4MzA1LCJpYXQiOjE2MzQ0NzE5MDUsImp0aSI6IjNlMWQ1ZmJjLTUwNjMtNGZmNS1iZDIxLWFlZTk1ZjAyZTVjZCIsImNsaWVudF9pZCI6IjNiZHBsZzNxYmwyNWJhczM1aWRuOWU4YWdqIiwidXNlcm5hbWUiOiJxbDAzIn0.FU8fv7bXDFLhUku_A11bLiw2kCdLCIepZ0l4E5t8okC_KgABGE4G_VFZ5E34VYAokuy-npWQaP84PKksnPR-S17phEQ-CWyoL5OM7t5sqkJseikqrgxzMoAgnSn34RUY4FJDhmuM9F5ejNhaKp-uDhDnvYaWe8Qcuz1TfBlgLUwARE1eBMaxqusmPOyJpZOvKcaeiOfqduv_rnN36UjIRaOeeDkht54n0066H9vBYnE1kwkVLlLagCI7kF2agHV6Kkl-cTVZTZjqCYzhOuAba_ZhdedsLn9xrQcY14-qgxfYiBxc-m1CVSZ-ZUTlmRShFrG6aHZDYSlWP38bRgQD8g",
"ExpiresIn":86400,
"TokenType":"Bearer",
"RefreshToken":"eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.ODcmlgBQ4TWaUudE5edD5Y0Fx4LQQ6LnmNjfBBcfS14s4Q9MmiD9YQZZSyKTu9UoeR3SpQkGEVvkI6t7rhtyheMqwtJiCxmitCcKVm8RhLrjgEIq2wbBTegINEOKFQKpf3IHsolssm2UuebYpaXxxB68swMwDdBC-By51aTdAaZpJWGiviqZiCNnuzvuKNfhnrZVk482ctrBdu0AgGj-YxKsnVEQozXvCHiojnQE7YfJW048ctYUBgti0wpKvNI2_PbavT11W8cD0x093uQhARZtSBazv4mkqtbPpdv6GSzIE6PHETfKIxJIMaDzLJKAnbOHCEquHfYD1KouZO93Cw.IopkTsaLXan0zWOW.jWxoHQPORSNVHQKfypBQ23BWtq3hzwkDDasd65asd7a8sfg8sd8gsd78gs9gdf8asdc9asdfdsfFXw-hfLA1uhCIeZebRNcTzmVcR_Kd0g75tCzH8FJw4TXMPvQ9Qg9NcJI1JsA_DLC1B9m0hwPXtUib0Pz7k5Z2l7fwCUUfFfT4VNiPrhsuz35XlbNJc1fq1kfN88f5sGZ0UocYwl_CtPf-0FwMeGJkhyyKIWAguV0z5FsfaVWojDPcGkw5JqILUwYxKZUW3mSORI5tXrgVloLoF48xaFoXpK5T2xPHfSaUZMJBsFMK24MdDRgLIfy6XS-21upJsi30O6yyc96A1vYYpk-eD871WC9156AlB3BskCsmgPKRSjPaDQ6Dfuc_xDR4ZLYb5XSaFtEC8q5eGeq-N2DjS0eDQbsUyMFY8ddY7BVNWIv0X1_HRKz6Nilrveimmc4OfaQ3aTHj32VDkxJb5BZgylEgLtaO_HRnqjnPD1Ic-XlxH0oXLgVm2aN3SSdXuEr3BdtCoRtnGfAkLAlK686L-3Ryo2Xg1oR61gNJXBljeFQVeTeUSNuYBKyc_swv4pwBUW_Ff_iOiR_ddhMuzqattTEtGxyXDKIVls3tDoyerJsD5e-_igpKkw5cks9Il6XI1I0Mq2jnso7xFeZEBztt3qcXJ5w8OdV1Qsc79SnAIA8yF9K_8zwZvMfFU5ODJSkvY56lReBiHDImQfDiImkgShDSKu-4y8IP10Ba3jr55b7rebgk3fO9yV9JcZOx9C2JtAHKFaTVCYz2YRA3fChUXlRHcqJfc2cYYAx5wD9fJLR4FiVkgkapaNadYT0pc4LPnylyijtUXgxL7tpDG4i9yJiQ1hT3kHV4o9NZeXyPV-VDN2XWeCOEYhpXASnj7nyRpzH4wPmD9xa6N-4mDzqDVXel7c527eecN7ZfU3MPXJ2fHnTGTzDjPCCPG1Ur0LE-9CJMWMMxbFDV9RR6paKMU8fno-cgczR-HZsWIgGzXudYEyb03OVDmRgdKxW_oWL8yWx0KEMtibHH.5jIZnrXB7RFLn3LS1bJqGA",
"IdToken":"eyJraWQiOiJlZFRQVFU0cXNOdFQyOEd2TWlXakg1aUExZjZFOVwvekw3OEZLYzdWU1VoWT0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJlYTkwZDYyOS0xYjc1LTRjZmItOGQ5ZC1kZGZlYmFkZmVkZjMiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImlzcyI6Imh0dHBzOlwvXC9jb2dadfgsdf6asdf87sd6fsd8f89fjpITrZUOH58mhfm-ToGQznHcWxd5I48W-uHckz1F9dLP9YLZpI0e3BGPPPeEOf2lAIa0dSfzKhl-SZgSLRBd5qX066jx1_6SvnTMpDgRE9JZmFM_n3cI2_jGoShxUwr7NpGaUCo2r2LPXx9Rs-KqmU33mqTFD8F-CezracE9xuAuDiTNCwlBA9_LB1FPQhvzkkgSR5vouIyUYzg"
},
"..."
}
This contains 3 parts:
AccessToken - used for checking access to Cognito authenticated APIs
RefreshToken - used for refreshing the access token
IdToken - a bearer JWT token generally used by services outside of CUP (Congito User Pools)
AccessToken & RefreshToken are being revoked but the JWT ID token (that API GW uses) is not being revoked.
This is confirmed by an AWS employee in this GitHub issue:
Because IdToken is represented as a JSON Web Key Token, it's signed with a secret or private/public key pairs, which means even if you revoke the IdToken, there is no way to revoke the distributed public key.
Others also have indicated this problem, here, here & here for example.
This is not really Amazon's fault as the nature of JWT tokens means that they are stateless. The authorizing server doesn't need any state stored to be able to validate the JWT token - the token itself is all that is needed to verify a token bearer's authorization.
This is perhaps highlighted a bit clearer in another section of the docs:
Revoked tokens will still be valid if they are verified using any JWT library that verifies the signature and expiration of the token.
Essentially, the bad news is that you've hit an Amazon Cognito limitation.
The slightly good news is that if your solutions permits, you can set the expiry of the ID token to a minimum of 1 hour for Cognito ID tokens. While the Cognito Quotas page mentions a minimum of 5 minutes for ID tokens, the console will actually throw a Cannot be greater than refresh token expiration. error for 5 minutes. You will have to set it to the minimum refresh token expiration duration which currently sits at 60 minutes.
A better way to say this is how the documentation for [AdminUserGlobalSignOut][9] has worded it:
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.
The good news is that an update was given by an AWS Amplify contributor in February that they were working on it (the same "fix" would apply to API Gateway hopefully as a service external to Congito).
The "best" news is that it is possible to implement "real" token revocation yourself to prevent the ID tokens from still being used. You will need to have a custom API Gateway Lambda authoriser (also this guide may be useful) to validate the token with Cognito but then to also check the token to see if it's been revoked by e.g. checking a blocklist DynamoDB table. Read more about the methodology in my answer here.
Until Amazon allows you to "revoke" the ID token too i.e. manages adding of ID tokens associated with the revoked refresh token to a block list on their side which other services can use for verification, you will have to implement this yourself.

Invalidate credentials on Logout with IAM secured AWS API Gateway

I'm using an AWS IAM secured AWS API Gateway in conjunction with AWS Cognito Identity Pool, similar to the Enhanced Simplified Flow described by AWS with an external (OpenId) Login Provider.
Retrieving the credentials is done by:
redirect to the Login Provider and obtaining the Id-Token upon successful user authentication,
authorize by AWS Cognito Identity Pool and assign an authenticated IAM Role,
receive the credentials (accessKeyId, secretKey, sessionToken) and
access all AWS IAM secured api endpoints with these credentials in an aws-signed request is working fine.
The problem is in the logout process:
How can I ensure, that upon logging out the user, the credentials received by the above mentioned process are properly and promptly not usable anymore to access the AWS IAM secured endpoints?
I don't see anything related to that in the documentation and to just wait for the credentials to expire is just NOT an option and a quite severe security problem:
Logging out a user has to prevent user's access to the API-Gateway right away!
Thanks for your help!
We are running into the same set of problems as yours and the AWS Team told us that it is not possible to revoke the tokens immediately after signout/globalSignout. In simple words, signout only invalidates the tokens to prevent uses in Cognito only, other services like API GW or S3 there they check for the expiration of the token in token itself rather than pinging the Cognito for user state verification. Let me know if you managed to solve it!

AWS Cognito get new id_token

I'm using implicit grant for one particular client (web-based admin portal) for an application that uses Cognito-integrated auth for other parts of the application (API Gateway, etc).
The problem is, the id token I get from Cognito Oauth expires after one hour, but I still need that token to send to API Gateway to signify that I have an authenticated user.
How do I get a new id token (remember, I'm using implicit grant, so I don't have a refresh token)?
Do I have to re-send the user's credentials to Cognito's auth endpoint every hour? That seems extravagantly un-secure for several reasons.
What am I missing?
Thanks in advance!
If you use implicit grant there is no way of refreshing the tokens (Storing credentials and using it to get new id tokens doesn't count here since its takes away the purpose of using OpenID Connect).
This is why the code grant is there. Unless you use code grant, refreshing the tokens are not possible.
You can use AWS Amplify library to simplify the login where it takes cares of the refreshing and other heavy liftings if you use the code grant including using federated identities to access AWS resources such as S3.

Why do Cognito tokens expire so quickly (15 mins) when used with federated identities

I want to use Cognito & Google login for my applications. I think the way to use that is federated identities?
I think I need to use GetId and GetOpenIdToken to get a Cognito token for my Google user? This works but
The OpenId token is valid for 15 minutes.
Why is this token expiry so short? Is it meant for such a use case - a normal REST API? I intend to use it with API Gateway.
UPDATE
I found http://serverless-stack.com/chapters/cognito-user-pool-vs-identity-pool.html to provide a useful explaination of Federated Identity vs User Pools. Its not the same thing like I thought.
User Pool: handles authentication, forgot password etc
Federated Identity: Gives access to users from user pool, social logins access to AWS resources
It's short lived because it's primarily intended to be passed back to get AWS credentials. If you're using the '3 hop', old flow, it's the response from GetOpenIdToken and given to AssumeRoleWithWebIdentity.
The recommended flow is to use the 'enhanced flow', which takes that out of the equation. The API GetCredentialsForIdentity gets the token and gets credentials in one API, never giving the token back. You can integrate with APIGW this way, via the credentials themselves.

Why does AWS CognitoSync ListDatasets require IdentityId?

If a user can only access his or her dataset, why is IdentityId a field which has to be set on this API call (ref). Couldn't the IdentityId be inferred from the AWS credentials? Or if they don't have to match, then technically wouldn't that mean that any malicious user could access that dataset, if he or she had the origin user's Cognito ID? Or does the call to ListDatasets re-validate the Cognito ID against the identity provider's token? In which case, what should I cache in my website as a cookie, so that a user won't have to log in every time?
Right now I am caching the CognitoId, but I am worried that if exposed that data will be compromised (or brute force attack), and if it isn't a security hole, then will I have to have the user re-authenticate against their provider each time they want to sync their data? What is the solution here?
Any help would be appreciated.
Edit 1:
I think what I have found is a caching problem with CognitoSync being used in the web browser and this question which may point to the same issue. Here is the auth flow I am expecting:
Login to Web Federated Auth Provider (OIDC, such as Google) => returns temporary token.
Use token to get temporary credentials and IdentityId from Cognito.
Cache Something.
Activate CognitoSync Manager to synchronize.
Some Time Later:
Using the cached something, rerun synchronization.
Problems:
If I cache the OIDC provider token, I can regenerate the AWS credentials, except it expires after 1 hour.
If I cache the AWS credentials, I can reissue Sync, except the credentials als expire.
If I cache the IdentityId I can't synchronize, So what do I cache?
Conclusion:
If AWS Credentials are needed to issue synchronization call, why does the listDatasets require IdentityId again in the API call, can't AWS CognitoSync reverse lookup the credentials to determine what the IdendityId assigned to those credentials are?
Does the [cognito sdk](https://github.com/aws/amazon-cognito-js) actually work?
Am I forced to use DynamoDB directly to do synchronization, if I want to avoid the Auth flow every time a user wants to synchronize?
There is a [Caching Provider](http://docs.aws.amazon.com/AWSAndroidSDK/latest/javadoc/com/amazonaws/auth/CognitoCachingCredentialsProvider.html) for Android SDK, does something like this not exist for the browser? (Does that even work?)
[Developer docs](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityCredentials.html) reference refreshing the token, this can only be done when the current token is still active :(.
It's not quite either. Cognito will validate the identity id given against the identity id for which the AWS credentials were created. So, for your concern about spoofing, it would not allow user A to get user B's sync data just by knowing their identity id.
There seems to be two issues:
Cognito validates the AWS Credentials against the IdentityId, so there is no security hole. However it still doesn't make sense for the API to contain that as a parameter, Amazon probably will needed to update that API.
The problem that you are running into is that identity provider should provide a way to get the necessary access token for each request authorization without requiring the user to grant permissions everytime. Authorize with the provider and then take the access token (or id_token) and send it Cognito as the Login token for that provider each time you want to synchronize. You actually don't need to cache anything.
For Google the request looks like:
var nonce = createNonce();
setCookie('GoogleAuthNonce', nonce, 1);
var paramString = $.param({
response_type:'id_token',
scope: 'openid', //email, profile',
client_id: GOOGLE_CLIENT_ID,
nonce: nonce,
login_hint: 'service_email_address',
prompt: 'none',
redirect_uri: 'http://service.com'
});
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${paramString}`;