awslabs / aws-jwt-verify

JS library for verifying JWTs signed by Amazon Cognito, and any OIDC-compatible IDP that signs JWTs with RS256, RS384, and RS512
Apache License 2.0
598 stars 43 forks source link

Validating that the Identity ID passed in an open ID token matches an identity in the specified Identity Pool #89

Closed ryantomaselli closed 1 year ago

ryantomaselli commented 1 year ago

Hey guys; thanks for this very useful library!

Question I am currently using an Cognito Identity Pool to handle users that haven't been authenticated yet. The app is a multi-tenanted app with each tenant having its own User Pool and Identity Pool.

I currently generate the token on the client side like so:

const { Token: openIdToken } = await new AWS.CognitoIdentity().getOpenIdToken({
  IdentityId: identityId
}).promise()

When I decode the token in my Lambda Authorizer it looks like so:

{
  sub: ''us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
  aud: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
  iss: 'https://cognito-identity.amazonaws.com',
  amr: [ 'unauthenticated' ],
  exp: 1662753731,
  iat: 1662753131
}

The sub value is the Identity ID for the user and the aud value is the Identity Pool ID.

If I want to make sure that this token is valid I need to make sure that the Identity ID in the token matches an identity in the Identity Pool specified by aud

Am I correct in thinking that I need to call the CognitoIdentity.listIdentities method, which I need potentially page through, in order to determine whether there is and identity that matches the Identity ID in the token?

For reference: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentity.html#listIdentities-property

Thanks for any pointers.

Versions Which version of aws-jwt-verify are you using? ^3.1.0 Are you using the library in Node.js or in the Web browser? Node.js If Node.js, which version of Node.js are you using? (Should be at least 14): 16 If using TypeScript, which version of TypeScript are you using?: ^4.7.4

ottokruse commented 1 year ago

To verify an identity pool JWT you would follow the usual JWT verification steps such as: check signature is valid, check not expired, check iss is what you expect, check aud is what you expect. These steps are all carried out for you by the JwtRsaVerifier from aws-jwt-verify. (You can't use the CognitoJwtVerifier as that is for User Pool JWTs, not Identity Pool JWTs)

Checking that the sub actually is an existing user, i.e. that it exists in the identity pool -- I recommend to not do this, you want to trust that it is, if the JWT is indeed valid. The JWT is created and signed by the identity pool, and it will only do so for identities known to the identity pool.

Also, listIdentities is a control plane operation, subject to smaller rate limits. You don't want to call that as part of normal authentication verification flows, as those usually have higher TPS than the rate limit allows.

Note the IETF says this link:

Similarly, when the JWT contains a "sub" (subject) claim, the application MUST validate that the subject value corresponds to a valid subject and/or issuer-subject pair at the application. This may include confirming that the issuer is trusted by the application. If the issuer, subject, or the pair are invalid, the application MUST reject the JWT.

Which means you should not test whether the sub exists at the IDP (the identity pool) but whether the sub was indeed registered in your application. (In some DB or whatever that you as application developer own) --> But since anonymous users in Identity Pools get new subs every time you call GetIdentity, this is not applicable to anonymous users. I.e. skip sub check for them.

Hope that makes sense, let me know if it helps.

ryantomaselli commented 1 year ago

@ottokruse thanks for this detailed response...it clears things up and certainly helps.

Cheers