How can I pass parameters to the lambda authorizer


#1

Hi there, got the following scenario: my identity service returns a list of permissions for each tenant. My lambda authorizer calls an endpoint on identity service to check that the access token is valid and that it has the required permissions for the provided tenant. The tenant is passed via query string to the API Gateway endpoint but the authorizer has no way to know what the required permissions are, so for now I’ve been hardcoding them.

My function looks like this:

functions:
  store:
    handler: handlers/storer.handler
    events:
      - http:
          path: data/services
          method: post
          authorizer: 
            arn: arn:aws:lambda:${env:REGION}:${env:ACCOUNT_ID}:function:${env:STAGE}-authorizer
            identitySource: method.request.header.Authorization
            type: request

Is there any way to pass a parameter from the API Gateway endpoint to the Lambda Authorizer other than the usual pathParameters or queryString? I was thinking to add them to the authorizer like this:

          authorizer: 
            arn: arn:aws:lambda:${env:REGION}:${env:ACCOUNT_ID}:function:${env:STAGE}-authorizer
            identitySource: method.request.header.Authorization
            type: request
            requiredActions: an:action:write

But I can’t see the requiredAction parameter anywhere in the event object of the Lambda Authorizer.


#2

I’m running into a similar issue and I can’t find resources about this anywhere. Maybe my approach is wrong?

Here’s what I’m having to do:

functions:
  createInvoice:
    handler: hander.createInvoice
    events:
      - http:
          path: createInvoice
          method: post
          authorizer: authorizeInvoiceCreation
  getInvoice:
    handler: handler.getInvoice
    events:
      - http:
          path: getInvoice/{id}
          method: get
          authorizer: authorizeInvoiceView
  authorizeInvoiceCreation:
    handler: auth/handler.authorizeInvoiceCreation
    cors: true
  authorizeInvoiceView:
    handler: auth/handler.authorizeInvoiceView
    cors: true

Here’s what I would prefer to do:

functions:
  createInvoice:
    handler: hander.createInvoice
    events:
      - http:
          path: createInvoice
          method: post
          authorizer:
            name: authorizerFunc
            scopes: "create:invoice"
  getInvoice:
    handler: handler.getInvoice
    events:
      - http:
          path: getInvoice/{id}
          method: get
          authorizer:
            name: authorizerFunc
            scopes: "read:invoice"
  authorizerFunc:
    handler: auth/handler.authorize
    cors: true

Does this make sense? Is there a way to do this? My current method is not scalable.


#3

I iterated on my solution but it’s still far from perfect.

I have an array of mappings like so:

const authMappings = [
  {
    resourcePath: "/getInvoice",
    method: "GET",
    scopes: ["read:invoice"]
  },
  {
    resourcePath: "/createInvoice",
    method: "POST",
    scopes: ["create:invoice"]
  }
];

Once a JWT is decoded, I check the user’s permissions against the scopes defined in that mapping. Here’s my authorizer code (I omitted error-checking and irrelevant things):

const checkPermissions = (permissions, resourcePath, method) => {
  const authMapping = authMappings.find(
    x => x.resourcePath === resourcePath && x.method === method
  );
  if (authMapping != null) {
    if (authMapping.scopes == null || authMapping.scopes.length === 0) {
      return true;
    }

    if (permissions == null) {
      return false;
    }

    const valid = authMapping.scopes.every(
      scope => permissions.indexOf(scope) !== -1
    );
    return valid;
  }
  return false;
};

module.exports.authorize = (event, context, callback) => {
  const { resourcePath, httpMethod } = event.requestContext;
  const [tokenKey, tokenValue] = event.authorizationToken.split(" ");

  const options = { audience: "..." };

  jwt.verify(
    tokenValue,
    process.env.AUTH0_CLIENT_PUBLIC_KEY,
    options,
    (verificationError, decoded) => {
      if (verificationError) {
        return callback("Unauthorized");
      }

      if (!checkPermissions(decoded.permissions, resourcePath, httpMethod)) {
        return callback("Unauthorized");
      }

      return callback(null, generatePolicy(/* ... */));
    }
  );
}

While this works, it means I have to keep the function names in serverless.yml in perfect sync with my authMappings array. Additionally, that array will be uploaded to Lambda, which is not great.

Does anyone have a better solution? I’m unfortunately thinking of switching to Express just because of this :frowning_face:

Cc. @buggy