Is storing aws cognito JWT key in frontend javascript insecure? - amazon-web-services

I was reviewing a website's javascript files to find an endpoint i wanted,during this i found a line like this.
DASHBOARD_API_Token=eyJraWQiOiJVS2paTnB3SWdBK292QzB3RWdxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
When i decoded the Above JWT i found entities like kid, username, useremail, iss = http://cognito-idp.us-east-1.amazonaws.com/{pool-id}
I have read few amazon resources which tell that its possible to fetch aws credentials using the above token. Given that i only have the jwt token what is possible and is it that i have to contact the website to revoke the current token?

Well, AWS Cognito is quite an interesting beast when it comes to its JWT tokens and what you can do with them.
Firstly, when you authenticate the user against Cognito User Pool, you get 3 different tokens: AccessToken, IdToken, and RefreshToken. Both AccessToken and IdToken are valid for exactly 1 hour (and you can't change it). This is why you get RefreshToken, which you can use to get new pair of AccessToken and IdToken by calling, for example, InitiateAuth. RefreshToken is by default valid for 30 days, but you can change it in the settings of your Cognito User Pool.
AccessToken
This is the most powerful token that is given by Cognito. If you possess a valid AccessToken, you can call several quite sensitive Cognito APIs.
One very surprising example is that you can call DeleteUser API by providing AccessToken and it will completely remove your account from Cognito User Pool. The "nicest" thing here is that app developer can't stop you from doing it if you've got a valid AccessToken. AWS devs are saying that "its by design". That was one of the strongest reasons why we decided not to pass Cognito Access Token onto the application frontend.
Speaking of the payload of AccessToken, the only potentially sensitive claim is the client_id, which contains the Cognito Client Id, which was used to authenticate the user. If this client does not have a client secret (which should probably be considered a security flaw in the first place), then the client_id can be used to call quite wide range of Cognito APIs. However, Cognito considers client_id to be public information. All other claims are considered public, you can see the full list of claims here.
IdToken
Contains the information about the player. It may contain some personal information about the player depending on what standard claims and custom attributes were configured in your Cognito User Pool, however it is also possible to suppress or override those claims using Pre Token Generation Lambda Trigger. Using this trigger allows one to hide or mask player attributes, which are considered sensitive and should not appear in IdToken.
As far as I'm aware, you cannot user IdToken to call Cognito APIs.
RefreshToken
Can only be used to request new AccessToken and IdToken, cannot be used to call Cognito APIs. In our particular use case we had to avoid passing RefreshToken to the app frontend, since it can be used to get AccessToken against public Cognito API (meaning that we cannot stop the user from doing that).
Using Cognito tokens to get AWS credentials
This is only possible if Cognito User Pool has been explicitly integrated with Cognito Identity Pool (which assumes that app developers knew what they were doing). When you exchange your Cognito JWT tokens, you get temporary AWS credentials with permissions bounded to a specific IAM role configured in Cognito Identity Pool. It is completely legitimate flow and intended behaviour. Moreover, if Cognito Identity Pool is configured to allow unauthenticated access, you can even get temporary AWS credentials without having Cognito JWT tokens (but those will be tied to another, presumably, more restrictive IAM role).
Using Cognito tokens with API Gateway
AWS API Gateway resources can be integrated with Cognito User Pools. Depending on how the integration is done, you can use either an AccessToken or IdToken from the integrated Cognito User Pool.
Revoking the tokens
You can revoke all tokens issued for your account in Cognito User Pool by calling GlobalSignOut API and providing a valid AccessToken. As this API is public, you don't need to contact the website if you possess a valid AccessToken.

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.

Lookup cognito userpool uset based on cognito dentity pool identity in API Gateway

I am an AWS newb, so please go easy on me :)
I have setup a proof of concept to proove out an authenticated API backed by lambda with the following components.
API Gateway -> backed by Lambda
Federated Identities backed by AWS Cognito UserPool
I have the authorizer setup in the API gateway to use the IAM role which is being provided by the Federated Identity pool.
I can see the identity (ap-southeast-2:<GUID>) coming through into the gateway ( using this in my integration request mapping template "$context.identity.cognitoIdentityId" ) from https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference
From either the gateway or lambda how can I resolve the 'ap-southeast-2:<GUID>' back to the source identity which resides in the user pool. (E.g. Pull one of the custom attributes from it)
Other information if relevant, i'm using Amplify-AWS for the client calling into the API Gateway.
TIA.
As you're noticing, Cognito as an identity provider is not the same as Cognito as a user pool.
Federated Identities provide a way of giving someone identified access to your AWS resources. The identity_id the identity provider gives you can almost be thought of as a tracking code. CIP (Congito [Federated] Identity Provider) allows you to get an identity id by signing in through any number of providers (not just the user pool), and even by not signing in at all.
User Pools give you a way of managing users for your application (i.e. a set of usernames, emails, passwords, etc).
This is the reason getting from identity_id back to the user pool user is hard (because, there's no guarantee it is a user pool user, it could well be someone from Facebook).
From what you've said, however, the assumption that said identity_id came from a UserPool authentication is safe. This means you have two options:
The official way will be to use identity:GetOpenIdToken to convert identity_id (you can ignore the logins part of the request) into an OpenId token. You can then use this token against the userpools:GetUser end point. There's a few pitfalls here, like ensuring you authenticate with a scope that allows you to see all the attributes you care about.
Curiously, however, the value of cognitoAuthenticationProvider is not opaque, and can (unoffically) be decoded:
// Cognito authentication provider looks like:
// cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx,cognito-idp.us-east-1.amazonaws.com/us-east-1_aaaaaaaaa:CognitoSignIn:qqqqqqqq-1111-2222-3333-rrrrrrrrrrrr
// Where us-east-1_aaaaaaaaa is the User Pool id
// And qqqqqqqq-1111-2222-3333-rrrrrrrrrrrr is the User Pool User Id
The above example, with more details about how you can then use this with userpools:AdminGetUser can be found here: https://serverless-stack.com/chapters/mapping-cognito-identity-id-and-user-pool-id.html

How to get AWSCredentials given a AWS Cognito access_token

In an android app, I receive a JWT access_token from http://<domain>.auth.<region>.amazoncognito.com/login once the user is done authenticating to a Cognito User Pool. That User Pool is linked to a Cognito Identity Pool.
What API should I call with that access_token to get an AWSCredentials object.
The closest one I found would be AssumeRoleWithWebIdentity, but that is an STS API, and some of what I've read on the web seems to recommend developers not use STS directly but rely on Cognito.
Moreover, I do not expect the API I need to require specifying a role name. Cognito Identity Pools are already configured to give authenticated users a specific role. And AssumeRoleWithWebIdentity takes a role name as input to the API. Hence that does not look like right.
I've looked at Cognito Identity Pool API Reference, and can't find an API that takes access_token and return AWS credentials.
UPDATE:
The following answer which uses GetCredentialsForIdentity throws ResourceNotFoundException saying it cannot find the specified IdentityId.
string access_token = ...
var jwtAccessToken = System.IdentityModel.Tokens.Jwt.JwtSecurityToken(access_token);
var client = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials(),REGION);
var response = await client.GetCredentialsForIdentityAsync(new GetCredentialsForIdentityRequest
{
IdentityId=String.Format("{0}:{1}", REGION, jwtAccessToken.id),
Logins=new Dictionary<string,string>
{
{String.Format("cognito-idp.{0}.amazonaws.com/{1}", REGION, USER_POOL_ID),
access_token}
}
});
After much investigation, I found the answer.
1- One needs an id_token not an access_token to authenticate to Cognito, as misleading as this might sound. AWS's documentation which says you ask for id_token when you need to have user attributes like name / email etc... and ask for an access_token when you don't need that information and just want to authenticate is wrong, or at the very least misleading.
2- And here's how you use an id-token to get AWS Credentials:
var credentials = CognitoAWSCredentials(<identity pool Id>, region);
credentials.AddLogin(
"cognito-idp.<region>.amazonaws.com/<user_pool_id>",
id_token); // the raw token
Note that you do not need AssumeRoleWithIdentity, or GetCredentialsWithIdentity, you do not even need a AmazonCognitoIdentityClient.
To get the credentials you can use GetCredentialsForIdentity method by passing the JWT token. This method is implemented in AmazonCognitoIdentityClient class in the AWS Android SDK.
IAM Role should be defined in the Cognito Federated Identities. This limits the assuming role to be handled internally, by Cognito not allowing the mobile app to assume any other role than the one configured. In addition you shouldn't give this role IAM permission, allowing the Android SDK to assume different roles (Unless its a superuser kind of a user who is logging in).

AWS Cognito federated identities + permanent access key for programmatic API

I have an API service, which I'm going to deploy using AWS API Gateway with Cognito authorizer + Lambda as backend. This service will be used by our javascript client. Also, it should be exposed to end users as raw endpoints for programmatic access.
While it was quite easy to enable signup/login in js client using federated identities, I can't figure out the way to provide users with private access token to include directly in http headers.
Here are two authentication flows, I'd like to get in the end:
The flow for js client user:
User signs up with Facebook or Google.
User verifies his identity.
After login, user goes to the Profile/API Keys section in the interface.
User copies access token and can include it in http request header in any http client (httpie, curl, language libraries whatever)
The flow for admin created user:
Admin creates user.
Access token is generated for that user.
Admin passes generated access token the user.
User can include it in http headers to make request, as in previous flow.
An access token should be permanent, and can be regenerated by user at any time (think of Stripe API access keys).
The point here is to eliminate additional steps for the user to start using service programmatically. The closest thing in AWS docs so far is developer-authenticated-identities, but user should utilize AWS sdk anyway.
One possible way to accomplish this task is to use Custom authorizer instead of Cognito authorizer in API Gateway. Custom authorizer could implement logic based on e.g. auth header name and decide to either authorize in Cognito or to user API access token in database. I'm not sure, if it is possible, and if it is the major drawback is to reimplement Cognito authentication flow in lambda function.
The question is how can I accomplish such API access token (re)generation using Cognito or API Gateway?
The first flow should be possible with User Pools. Cognito User Pools now has a federation feature where you can federate using Facebook/Google and receive access token/refresh token depending on the flow used.
For admin created user, the user would need to authenticate before tokens are issued but this can be achieved by creating the user with a temporary password and signing the user in with that password, after which it can be changed and logged in again to receive access/refresh token.
The refresh token use case is that it can be used against the Cognito APIs to receive a new access token. When the refresh token expires (default is 30 days but it is configurable), the user would have to authenticate again.

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.