AWS Compute Blog

Introducing custom authorizers in Amazon API Gateway

by Stefano Buliani | on | in Amazon API Gateway | | Comments

Today Amazon API Gateway is launching custom request authorizers. With custom request authorizers, developers can authorize their APIs using bearer token authorization strategies, such as OAuth using an AWS Lambda function. For each incoming request, API Gateway verifies whether a custom authorizer is configured, and if so, API Gateway calls the Lambda function with the authorization token. You can use Lambda to implement various authorization strategies (e.g., JWT verification, OAuth provider callout). Custom authorizers must return AWS Identity and Access Management (IAM) policies. These policies are used to authorize the request. If the policy returned by the authorizer is valid, API Gateway caches the returned policy associated with the incoming token for up to 1 hour so that your Lambda function doesn’t need to be invoked again.

Configuring custom authorizers

You can configure custom authorizers from the API Gateway console or using the APIs. In the console, we have added a new section called custom authorizers inside your API.

An API can have multiple custom authorizers and each method within your API can use a different authorizer. For example, the POST method for the /login resource can use a different authorizer than the GET method for the /pets resource.

To configure an authorizer you must specify a unique name and select a Lambda function to act as the authorizer. You also need to indicate which field of the incoming request contains your bearer token. API Gateway will pass the value of the field to your Lambda authorizer. For example, in most cases your bearer token will be in the Authorization header; you can select this field using the method.request.header.Authorization mapping expression. Optionally, you can specify a regular expression to validate the incoming token before your authorizer is triggered and you can also specify a TTL for the policy cache.

Once you have configured a custom authorizer, you can simply select it from the authorization dropdown in the method request page.

The authorizer function in AWS Lambda

API Gateway invokes the Lambda authorizer by passing in the Lambda event. The Lambda event includes the bearer token from the request and full ARN of the API method being invoked. The authorizer Lambda event looks like this:

{
    "type":"TOKEN",
    "authorizationToken":"<Incoming bearer token>",
    "methodArn":"arn:aws:execute-api:<Region id>:<Account id>:<API id>/<Stage>/<Method>/<Resource path>"
}

Your Lambda function must return a valid IAM policy. API Gateway uses this policy to make authorization decisions for the token. For example, if you use JWT tokens, you can use the Lambda function to open the token and then generate a policy based on the scopes included in the token. Later today we will publish authorizer Lambda blueprints for Node.js and Python that include a policy generator object. This sample function uses AWS Key Management Service (AWS KMS) to decrypt the signing key for the token, the nJwt library for Node.js to validate a token, and then the policy generator object included in the Lambda blueprint to generate and return a valid policy to Amazon API Gateway.

var nJwt = require('njwt');
var AWS = require('aws-sdk');
var signingKey = "CiCnRmG+t+ BASE 64 ENCODED ENCRYPTED SIGNING KEY Mk=";

exports.handler = function(event, context) {
  console.log('Client token: ' + event.authorizationToken);
  console.log('Method ARN: ' + event.methodArn);
  var kms = new AWS.KMS();

  var decryptionParams = {
    CiphertextBlob : new Buffer(signingKey, 'base64')
  }

  kms.decrypt(decryptionParams, function(err, data) {
    if (err) {
      console.log(err, err.stack);
      context.fail("Unable to load encryption key");
    } else {
      key = data.Plaintext;

      try {
        verifiedJwt = nJwt.verify(event.authorizationToken, key);
        console.log(verifiedJwt);

        // parse the ARN from the incoming event
        var apiOptions = {};
        var tmp = event.methodArn.split(':');
        var apiGatewayArnTmp = tmp[5].split('/');
        var awsAccountId = tmp[4];
        apiOptions.region = tmp[3];
        apiOptions.restApiId = apiGatewayArnTmp[0];
        apiOptions.stage = apiGatewayArnTmp[1];
       
        policy = new AuthPolicy(verifiedJwt.body.sub, awsAccountId, apiOptions);

        if (verifiedJwt.body.scope.indexOf("admins") > -1) {
           policy.allowAllMethods();
        } else {
          policy.allowMethod(AuthPolicy.HttpVerb.GET, "*");
          policy.allowMethod(AuthPolicy.HttpVerb.POST, "/users/" + verifiedJwt.body.sub);
        }

        context.succeed(policy.build());

      } catch (ex) {
        console.log(ex, ex.stack);
        context.fail("Unauthorized");
      }
    }
  });
};

You can also generate a policy in your code instead of using the provided AuthPolicy object. Valid policies include the principal identifier associated with the token and a named IAM policy that can be cached and used to authorize future API calls with the same token. The principalId will be accessible in the mapping template.

{
  "principalId": "xxxxxxx", // the principal user identification associated with the token send by the client
  "policyDocument": { // example policy shown below, but this value is any valid policy
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "execute-api:Invoke"
        ],
        "Resource": [
          "arn:aws:execute-api:us-east-1:xxxxxxxxxxxx:xxxxxxxx:/test/*/mydemoresource/*"
        ]
      }
    ]
  }
}

To learn more about the possible options in a policy, see the public access permissions reference for API Gateway. All of the variables that are normally available in IAM policies are also available to custom authorizer policies. For example, you could restrict access using the ${aws:sourceIp} variable. To learn more, see the policy variables reference.

Because policies are cached for a configured TTL, API Gateway only invokes your Lambda function the first time it sees a token; all of the calls that follow during the TTL period are authorized by API Gateway using the cached policy.

Conclusion

You can use custom authorizers in API Gateway to support any bearer token. This allows you to authorize access to your APIs using tokens from an OAuth flow or SAML assertions. Further, you can leverage all of the variables available to IAM policies without setting up your API to use IAM authorization.

Custom authorizers are available in the API Gateway console and APIs now, and authorizer Lambda blueprints will follow later today. Get in touch through the API Gateway forum if you have questions or feedback about custom authorizers.