Integrating the Curity Identity Server with AWS API Gateway using the Split Token approach

Integrating the Curity Identity Server with AWS API Gateway using the Split Token approach

tutorials

Overview

The AWS API Gateway is very powerful and have lots of possibilities for extensions. One such extension is the ability to create a custom Lambda Authorizer.

This how-to will cover how to configure the Curity Identity Server to send a partial token or a split token to a configured instance of AWS DynamoDB. Setting up a Lambda Authorizer that the AWS API Gateway can leverage to authorize the incoming API call and pass on a JWT to the upstream API.

The details of the Split Token approach is a useful read before diving fully into how to set up this integration.

Prerequisites

  • An installation of the Curity Identity Server
  • Basic understanding of the AWS ecosystem and especially as it related to the API Gateway, DynamoDB and Lambda functions.

If you do not have an installation of the Curity Identity Server, follow this tutorial installation of the Curity Identity Server and configure the installation by running Curity Basic Setup Wizard as outlined in this tutorial Curity Basic Setup Wizard.

Configure the AWS Token Publisher

The Curity Identity Server has the capability to configure event listeners. These are plug-ins that will trigger when a specific event occurs in the system. For the purpose of integrating with the AWS API Gateway there is a specific AWS token publisher plug-in. The plug-in can be obtained from the Curity Github repo and contains full instructions on how to package and deploy the plug-in.

Curity AWS Token Publisher

The AWS Token Publisher will detect when a token is issued. In order to integrate with AWS API Gateway the Curity Identity Server needs to send the hashed signature part of the issued token along with the header and body to an instance of DynamoDB. This information will later be read by the Lambda Authorizer invoked by the AWS API Gateway.

Configure the split token procedure

In addition to the Curity Identity Server sending the token information to the DynamoBD instance when a token is issued the way the tokens are issued also needs to be modified. This can be achieved by modifying the procedure that runs when the Curity Identity Server executed the procedure for Authorization Code.

Configure token procedure

  1. Go to Profiles > Token Service > Endpoints, find the endpoint with the type oauth-token and click the select box in the Flows column.
  2. Next find the “Authorization Code” procedure and click the select box next to it. Enter a name for your procedure and click save.
  3. A popup will appear where you can type the procedure code. Replace the code in the popup with the following one:

function result(context) {
    var delegationData = context.getDefaultDelegationData();
    var issuedDelegation = context.delegationIssuer.issue(delegationData);

    var accessTokenData = context.getDefaultAccessTokenData();
    var issuedAccessToken = context.getDefaultAccessTokenJwtIssuer().issue(accessTokenData, issuedDelegation);

    var token = issuedAccessToken.split('.')[2];

    var refreshTokenData = context.getDefaultRefreshTokenData();
    var issuedRefreshToken = context.refreshTokenIssuer.issue(refreshTokenData, issuedDelegation);

    var responseData = {
        access_token: token,
        scope : accessTokenData.scope,
        refresh_token: issuedRefreshToken,
        token_type: 'bearer',
        expires_in: secondsUntil(accessTokenData.exp)
    };

    if (context.scopeNames.contains('openid')) {
        var idTokenData = context.getDefaultIdTokenData();

        var idTokenIssuer = context.idTokenIssuer;
        idTokenData.at_hash = idTokenIssuer.atHash(issuedAccessToken);

        responseData.id_token = idTokenIssuer.issue(idTokenData, issuedDelegation);
    }

    return responseData;
}
  1. Click the Update button to save changes in the procedure. You can then close the popup. Save and Commit changes to the server.

Configure DynamoDB

  1. Got to AWS Management Console
  2. Search for the DynamoDB service
  3. Click Create table
  4. Name the table, ex split-token
  5. Enter a Primary key, ex. hashed_signature. Make sure the data type is set to String
  6. Click Create

Create DynamoDB Table

Configure the lambda-authorizer

  1. In the AWS Management Console, go to Lambda

  2. Choose Create function and then Author from scratch

  3. Give the function a name, ex. curity-split-token-authorizer

  4. Choose the Node.js 12.x runtime

  5. Click Create function

    Create Lambda Authorizer

  6. In the default index.js, replace all the code with the following code:


var aws = require('aws-sdk');
var crypto = require('crypto');

var dynamodb = new aws.DynamoDB();
const table_name = process.env.TABLE_NAME;
const key_name = process.env.KEY_NAME;

var token_pattern = /^Bearer[ ]+([^ ]+)[ ]*$/i;

exports.handler = function(event, context, cb) {
  const token = extractAccessToken(event.headers.Authorization);
  /* Hash the incoming token and use as key to retrieve header and body from dynamodb */
  const hash = crypto.createHash('sha256');
  hash.update(token);
  const hashed_token = hash.digest('base64');

  /* Create parameters for DynamoDB query */
  var params = {
    KeyConditionExpression: key_name + '= :key_name',
    ExpressionAttributeValues: {
      ':key_name': {'S': hashed_token}
    },
    TableName: table_name
  };

  /* Execute the DynamoDB query to fetch the header and body */
  dynamodb.query(params, function(err, data)
  {
    if (err)
    {
      cb(err);
    } else
    {
      /* If no token data is found a Deny policy is returned */
      if (data.Items[0] === undefined)
      {
        cb(null, generatePolicy('user', 'Deny', event.methodArn));
      } else
      {
        /* "Glue" together the token */
        var complete_token = reconstructToken(data.Items[0].head_and_body.S, token);

        var token_expiration_time = data.Items[0].expiration.S;
        var current_time = Math.floor(new Date() / 1000);

        /* If the token has expired a Deny policy is returned */
        if(token_expiration_time <= current_time)
        {
          cb(null, generatePolicy('user', 'Deny', event.methodArn));
        }
        /* Token data is ok, token is "glued" together and the token has not expired, a Allow policy is returned along with the complete token */
        else if(token_expiration_time > current_time)
        {
          cb(null, generatePolicy('user', 'Allow', event.methodArn, complete_token));
        }
      }
    }
  });
};

/* Extract the token from the Authorization header */
function extractAccessToken(authorization)
{
  if (!authorization)
  {
    /* No access token available */
    return null;
  }

  var extracted_token = token_pattern.exec(authorization);

  /* If the Authorization header does not match the pattern */
  if (!extracted_token)
  {
    /* No valid access token */
    return null;
  }

  /* Return the token */
  return extracted_token[1];
}

function generatePolicy(principalId, effect, resource, token) {
  return {
    'principalId': principalId,
    'policyDocument': {
      'Version': '2012-10-17',
      'Statement': [{
        'Action': 'execute-api:Invoke',
        'Effect': effect,
        'Resource': resource
      }]
    },
    "context": {
    "token": "Bearer " + token
  }
  };
}

function reconstructToken(head_and_body, signature)
{
  return head_and_body.concat('.', signature);
}
  1. In the Environment variables section, click Edit
  2. Click Add environment variable
  3. Add, Key TABLE_NAME with the Value configured in DynamoDB, ex. split-token
  4. Add, Key KEY_NAME with the Value configured in DynamoDB, ex. hashed_signature
  5. Click Save

Lambda Environment Variables

Configure AWS API GW

In the AWS Management Console, go to API Gateway, choose the API to protect or create a new API.

Create & configure the Authorizer

  1. Select Authorizers.
  2. Click Create New Authorizer
  3. Give it a name, ex. curity-authorizer
  4. Make sure the type is set to Lambda
  5. Under the Lambda Function section, make sure the correct Zone is selected, the previously configured Lambda Authorizer with the name curity-split-token-authorizer should be available in the text field
  6. In the Lambda Event Payload section choose Lambda Event Payload
  7. For Identity Sources choose Header and enter Authorization in the field
  8. If Authorization Caching is left enabled the API Gateway will not invoke the Lambda function if the same Authorization header is used within the time span of the cache configuration (300 seconds by default) Lambda Environment Variables
  9. Click Create
  10. Then click Grant & Create

Configure API

  1. For the API to protect, go to Resources
  2. Select the Resource and the Method to protect
  3. Click Method Request
  4. Under Settings -> Authorization, choose the previously created Authorizer, curity-authorizer. NOTE: The drop-down list is populated when the API method is created. If the Authorizer does not show up, delete the method and re-create it and the Authorizer should show up.
  5. In the HTTP Request Headers section, click Add header and enter Authorization then click the grey checkbox to the right. Configure Method Request
  6. Go back to Method Execution
  7. Click Integration Request
  8. Set the Integration type to HTTP
  9. In the HTTP Headers section, click Add header, for Name enter Authorization and for Mapped from enter method.request.header.Authorization Expand Mapping Templates
  10. Choose When there are no templates defined (recommended)
  11. Click Add mapping template
  12. Under Content-Type, enter application/json and click the checkbox
  13. In the template editor, on line 1, enter #set($context.requestOverride.header.Authorization = $context.authorizer.token)
  14. Click SaveConfigure Method Request

Test using oauth.tools

  1. In oauth.tools, create a flow, for example the code flow. The Access token created should now be the signature part of the split-token and will be much longer than a traditional access token provided.
  2. Now create a Call External API flow. Enter the URL for the AWS API Gateway exposed API. Configure the method according to what’s possible with the API. Select the token retrieved in the previous flow from the drop down list. Add a header with the name Content-Type and the value application/json and click Send.

Oauth Tools

Resources

Useful resources that covers the topics of this article.

Let’s Stay in Touch!

Get the latest on identity management, API Security and authentication straight to your inbox.

Keep up with our latest articles and how-tos using RSS feeds