OAuth Cognito ID token unauthorized - amazon-web-services

I have a cognito user pool/app client setup with OAuth flow and standard scopes (email, admin, etc.) and several 3rd party providers (google, azure, etc.). I would like to get the identity data in the request context which mean I would need to use the id token but using the id token gets me an unauthorized response from api gateway while the access token works as expected. I remember reading somewhere that custom scopes doesn't allow for id token auth but I don't have custom scopes setup. Any insight into what's going on here?

From Integrate a REST API with an Amazon Cognito user pool:
With the COGNITO_USER_POOLS authorizer, if the OAuth Scopes option
isn't specified, API Gateway treats the supplied token as an identity
token and verifies the claimed identity against the one from the user
pool. Otherwise, API Gateway treats the supplied token as an access
token and verifies the access scopes that are claimed in the token
against the authorization scopes declared on the method.
OAuth Scopes are only present in access tokens. Based on the fact that access tokens work, you have specified one or more OAuth scopes for your endpoint.
If you have a valid access token, you can use its value to get information about the authenticated user using the USERINFO endpoint.

Related

Using Identity Token instead of Access Token for authorization on resource server

We are working on switching to Cognito as the 3rd party OAuth 2.0 provider for our backend services.
Our legacy system had its own Spring Authorization Server (deprecated) for generating and verifying access tokens which enabled us to add custom claims to Access Token itself, there was no need to carry around Id Token to extract information about the authorised user.
But Cognito pushes us to use OAuth 2.0 standards naturally, and there is no possibility to add custom claims to Access Tokens. Id Tokens are used for extracting custom claims and user information throughout an authorised session.
This brings us to a situation where we need to have Id tokens in the backend service sessions for the endpoints that needs some user info in order to process requests. But to fetch the Id token from the Cognito provided /userInfo endpoint, Access Tokens need to have openid scope, but to have the openid scope in the Access token, we need to use Cognito Hosted UI which is not applicable for the mobile app and our legacy Web app.
So either we need to use Identity Token as the Bearer token(not advised by standards), or we need to expect ID token with a custom HTTP header along with the Access token. Is there a huge security risk if ID tokens are used also for enabling access to REST API's ?

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.

wso2 apim role validation issue

I'm using WSO2 IS (5.11.0) as the key manager of APIM 4.0.0). I have published a graphql API which is secured by a scope (say 'test_scope' based on a role named 'test'). I have subscribed to the API with the Default Application and have generated the keys as well. When I generate the access token with the scope ('test_scope') and invoke the secured API I'm getting the valid response even though the required role ('test') is not assigned to the user. Below is the curl command which I'm using.
The API Manager will perform the Role-based validation during the token generation. So, if you are generating the JWT Token with a user that has the respective role assigned, then the API Manager will generate the access token with the requested scopes (that are bound to the role).
I believe that you have generated the JWT token using either Client Credentials or Password grant (not able to find the cURL in the question :)), and the token has been generated with a user who has the respective role assigned. To give more clarification, when using the Client Credentials grant, the API Manager will use the Application Owner as the user to verify the Roles and to provide the scopes. Whereas the Password grant, uses the respective username (that is sent), to verify the roles and scopes.
During the runtime, the Gateway will decode the JWT token and verifies whether the required scopes are available. If yes, the Gateway will proceed with the request, and if not it fails.
So to check the process, you can use another user in the same tenant who hasn't been assigned with the specific role and generate the Token with Password grant and invoke the API (in this scenario, the token will be generated with the default scope and not with the mentioned test_scope).
Hope this clarifies and provides a brief understanding of the Token generation and scope validation process.

API Gateway unauthorized when using token

I am trying to test an API Gateway endpoint, using Postman.
I'm trying to make use of the Hosted UI, and whilst I can get an access_token returned in the URL, using this in the Authorization Bearer Token, I simply get 401 Unauthorized responses.
The specific endpoint has the correct authorizer attached to it.
I even tried removing the authorizer all together, but still got the 401 response, so I'm quite confused, as that should have meant I don't need to pass any token with the request?
Turns out, despite the naming convention, access_token was the wrong token to use. I tried th id_token and that works.
Solution: Add an OAuth scope if you want to use the access token otherwise use an id token.
From the documents
With the COGNITO_USER_POOLS authorizer, if the OAuth Scopes option isn't specified, API Gateway treats the supplied token as an identity token and verifies the claimed identity against the one from the user pool. Otherwise, API Gateway treats the supplied token as an access token and verifies the access scopes that are claimed in the token against the authorization scopes declared on the method.

Does Cognito User Pool Authorizer in Gateway API require all requests to be signed?

The documentation for using Cognito User Pool Authorizer with Gateway API says only that I should:
Call API methods configured with a user pool authorizer, supplying the unexpired token in the Authorization header or another header of
your choosing.
This is echoed by some other texts on the web.
However, when I try using the token, I get an error message that informs about missing Credentials, Signature, Signed Headers params (and the Date header). The token I am using is most likely correct as passes the test in the authorizer's web gui.
My question has two parts:
Does that mean that using the Cognito User Pool Authorizer requires
me to sign each request? Is there some way to configure it to just
accept a valid token?
If I want to keep my HTTP calls to Amazon Gateway simple and
authorize them with just the token (so that they can easily be
performed by hand, from Python backend etc.), am I forced to write a
custom authorizer using Lambda? Or is there some better option?
Cognito User Pool authorizer does not require a signature on the request. You simply have to pass the JWT version of the OpenID Connect identity token produced by Cognito in the authorization header of each request.
result.getIdToken().getJwtToken()
This should answer both your questions.
I think you are getting confused with the Cognito Identity service, which exchanges a valid identity from a public identity provider (Facebook, Amazon, User Pools, etc) for temporary AWS Credentials. You can use the AWS credentials from the Cognito Identity service to sign requests.
If you are only using User Pools, the result of a successful authentication are an OIDC identity token and a JWT access token. API Gateway, when configured with a User Pool authorizer, uses the identity token to authenticate a request.