amazon-archives/aws-serverless-auth-reference-app

Is userId spoofing prevented?

Closed this issue · 2 comments

I was trying to follow the identity flow, but somehow it either disappeared or I lost track.

When booking a location, the userId is sent as part of the body request, as your video shows at
https://youtu.be/n4hsWVXCuVI?list=PLhr1KZpdzukdAg4bXtTfICuFeZFC_H2Xq&t=2191
This looks dangerous, as the user could be able to insert some other userId, not his own.

Apparently, authorizer.js does receive the real userId,
const pId = payload.sub;
does not use this for restricting permissions, but passes it along
policy.principalId = this.principalId;

The booking.js just takes the values as they come and does not check for identity.

function Create(event) {
  let input = JSON.parse(event.body);
  return BookingsTable.put(input);
}

I would assume, if a user managed to insert a wrong userId the data would be entered incorrectly into the database.

Do you agree?
If yes, what would be the best way to prevent it?

Yes. I believe you are correct. This video and app is the ABSOLUTE BEST example I've found for marrying Cognito Federated Identities and Cognito User Pools, but it still has some gaps. But it has way fewer gaps that others (e.g. https://github.com/ionic-team/ionic2-starter-aws).

In the spacefinder app, methods that use Cognito User Pools Authorizer or the Custom Authorizer have no way to know if the user ID from Cognito Federated Identities goes with the claim.

From memory, here's what I did to fix it in my app:

User Pool

  1. Add custom field "custom:identityId".
  2. Make the field readable. Making it readable will make it part of jwt id tokens.

Custom Authorizer

  1. Near the end, JSON.stringify the decoded jwt and put it in the context, so that it's available to functions that use the Custom Authorizer. I put it in .context['custom:authorizer']

IAM

  1. The lambda execution role needs a couple new permissions.
    {
    "Action": [
    "cognito-idp:AdminGetUser",
    "cognito-idp:AdminUpdateUserAttributes"
    ],
    "Resource": [
    "arn:aws:cognito-idp:us-east-1:123456789012:userpool/us-east-1_xxxxxxxxx"
    ],
    "Effect": "Allow"
    }

New API method

  1. Create New api method for /user/ UPDATE
  2. This method uses AWS_IAM Authorizer and accepts an id or session token the request body. That means API Gateway will put the identityId in event.requestContext.identity.cognitoIdentityId, and you can trust that it's valid.
  3. API gateway did not validate the jwt in the body, so you have to do that yourself. You can borrowing all the code out of the custom authorizer in this sample. If the token valid, use cognito-idp.idp:AdminUpdateUserAttributes to store the identityId in a custom attribute.

Changes to existing API methods

  1. Now methods that use User Pools Authorizer can get the identityId from event.requestContext.authorizer.claims['custom:identityId']
  2. Methods that use the Custom Authorizer can get the identityId from (in my case) JSON.parse(event.requestContext.authorizer['custom:authorizer'])['custom:identityId'] (You can't put it the same place that the Cognito User Pools Authorizer uses because API Gateway will barf for no good reason if you put anything other than string, number, boolean in the context).
  3. But there's one more catch for 1 and 2 above. Right after you set the custom attribute, it won't be on subsequent reads of the token right away. So until it shows up, you need a fall-back to use cognito-idp:AdminGetUser to read it out of the custom attribute. I have no idea if that api call is rate-limited, but it should only be occurring for brand-new users and for a short time.

Changes to ionic app

  1. On login, if the 'custom:identityId' method is missing, call the new API method /users/ UPDATE to "link" the user.

Hi there- To comment on this further, for the sake of time and due to this being a prototype reference app, we did send the userID through the payload without validating it. However, the much better way to do this, which would prevent spoofing is to have the effective IAM policy of the user (either from the IAM role assumed with Cognito Federated Identities or from the IAM policy returned by a custom authorizer with API Gateway) have only allow the user to invoke API operations on their particular user ID as a path parameter. For example, /users/userIdGuid and /users/userIdGuid/*. You can use either the identity ID from Cognito User Pools via an IAM policy variable as we have setup in our IAM policy, or if you're exclusively using Cognito User Pools with Custom Authorizers, use the unique subscriber ID of the user in the user pool.

Then, in the code of your function to do any modifications related to a user, pull the user ID out of the path parameter rather than the body, which is already being enforced via the execution policy so if the function code can even be invoked, you know that the path parameter sent through is that of the valid, actual user (unless an admin is calling operations and you've given them more broad permissions).

Hope this helps clarify a more secure method of validating the user ID. I think this is perhaps the most simple approach to solve this problem and prevent spoofing.