Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper Principal ID Value for API Gateway Custom Authorizer via Lambda?

I am using a new feature of the API Gateway with Lambda functions to use a Custom Authorizer (https://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html).

The authorizer uses JWT Tokens to validate the token for the current user context and scope(s). All is working fine but there is one concept regarding AWS Policies that I cannot quite figure out from documentation.

The output of a Custom Authorizer function has to be an object containing two things:

  1. principalId - in question
  2. policyDocument - a valid Policy document with statements to allow user authorized access to Lambda Resources, stages, etc.

Now, the examples for Custom Authorizers currently show almost arbitrary values for the principalId variable. But if I am thinking correctly, this principalId should be unique for each user? And perhaps have a user specific unique value associated with it (such as token.userId or token.email).

If this is true, then for my provided code below, if the JWT Token is not valid, then I do not have access to the userId or email, and have no clue what to set the principalId to. I am setting it temporarily to user just to have something return for a Deny policy to ensure that the response is 403 Forbidden.

Anyone have any clue on best practices for setting principalId for a Custom Authorizer?

var jwt = require('jsonwebtoken');
var JWT_SECRET = 'My$ecret!';


/**
 * Implicit AWS API Gateway Custom Authorizer. Validates the JWT token passed
 * into the Authorization header for all requests.
 * @param  {Object} event   [description]
 * @param  {Object} context [description]
 * @return {Object}         [description]
 */
exports.handler = function(event, context) {
  var token = event.authorizationToken;
  try {
    var decoded = jwt.verify(token, JWT_SECRET);
    context.done(null, generatePolicy(decoded.id, 'Allow', 'arn:aws:execute-api:*:*:*'));
  } catch(ex) {
    console.error(ex.name + ": " + ex.message);
    context.done(null, generatePolicy('user', 'Deny', 'arn:aws:execute-api:*:*:*'));
  }
};

function generatePolicy(principalId, effect, resource) {
  var authResponse = {};
  authResponse.principalId = principalId;
  if (effect && resource) {
    var policyDocument = {};
    policyDocument.Version = '2012-10-17'; // default version
    policyDocument.Statement = [];
    var statementOne = {};
    statementOne.Action = 'execute-api:Invoke'; // default action
    statementOne.Effect = effect;
    statementOne.Resource = resource;
    policyDocument.Statement[0] = statementOne;
    authResponse.policyDocument = policyDocument;
  }
  return authResponse;
}
like image 662
Tom Pennetta Avatar asked Feb 17 '16 17:02

Tom Pennetta


1 Answers

The principalId is intended to represent the long term identifier for whatever entity is being authorized to make the API call. So if you have an existing database of users, each user presumably has a unique identifier or username. You mentioned 'user', which is probably fine. Functionally, the principalId is logged if you enable CloudWatch Logs, and is also what you can access in the $context for mapping templates.

In terms of design for your function, you have two options for dealing with an 'invalid' token.

  1. If you return a valid policy that denies access, this helps you by caching the policy associated with the token in case it's used again, so you get fewer Lambda invocations. However the client may receive a 403 and think that the token was valid but they don't have access to the resource they requested.

  2. context.fail("Unauthorized") will send a 401 response bad to the client, which should indicate to them that the token was invalid. This would help the client, but also result in more invocations on the function if the client repeatedly replayed the bad token. Negative caching is currently not available on the feature, but another way to provide moderate protection is to use the 'identityValidationExpresion' -> http://docs.aws.amazon.com/apigateway/api-reference/resource/authorizer/#identityValidationExpression

Also, I'd strongly recommend that you migrate this to a new Lambda function based on the apigateway-authorizer-nodejs blueprint, since the code sample in the docs is minimal and intended for illustration only. The blueprint has lots of comments that document various uses, like the fail("Unauthorized") functionality.

like image 166
jackko Avatar answered Nov 12 '22 20:11

jackko