API Authorization with AWS at Emergency Response Africa
Tags: aws, node, javascript, webdev
Published: 2021-10-04 15:21:18

---
title: API Authorization with AWS at Emergency Response Africa
published: true
description: How we handle authorization and access to our Internal APIs at Emergency Response Africa.
tags: aws, node, javascript, webdev
cover_image: https://res.cloudinary.com/cheapflix/image/upload/v1633362910/lock.png
---


## Introduction

[Emergency Response Africa](emergencyresponseafrica.com) is a healthcare technology company that is changing how medical emergencies are managed in Africa.
As you can imagine, this means we manage a lot of web and mobile applications, used internally and externally.

![Emergency Response Africa](https://res.cloudinary.com/cheapflix/image/upload/v1633360788/Screenshot_2021-10-04_at_16.19.27.png)


The importance of securing access to resources from these client applications can't be overstated. The wrong user having access to the wrong resources can cause a lot of problem.

In this post, I'll discuss in detail how we handle Authorization to our internal APIs using Amazon Web Services (AWS) and how we determine the extent of the permissions to assign to the client making the request.

## What is Authorization

Authorization is the process of verifying the resources a client has access to. While often used interchangeably with authentication, authorization represents a fundamentally different function. To learn more, read this post on [Authentication and Authorization](https://auth0.com/docs/get-started/authentication-and-authorization) by Auth0.

## Our Workflow

Our workflow is pretty simple, and our API is deployed using the [Serverless Application Model](https://aws.amazon.com/serverless/sam/)

![ERA Authentication Architecture](https://res.cloudinary.com/cheapflix/image/upload/v1633353149/API_Authorization.png)

In this architecture, we make use of TOKEN Lambda authorizer. This means it expects the caller's identity in a bearer token, such as a JSON Web Token (JWT) or an OAuth token.

1. The client app calls a method on an [Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) API method, passing a bearer token in the header.

2. API Gateway checks whether a Lambda authorizer is configured for the method. If it is, API Gateway calls the Lambda function.

3. The Lambda function authenticates the client app by means generating an IAM policy based on the preconfigured settings in our API.

4. If the call succeeds, the Lambda function grants access by returning an output object containing at least an IAM policy and a principal identifier.

5. API Gateway evaluates the policy.
If access is denied, API Gateway returns a suitable HTTP status code, such as 403 ACCESS_DENIED.
If access is allowed, API Gateway executes the method.

## Implementation
The most technical aspect of this post. 
TLDR, You can jump straight to the [code](https://github.com/chidindu-ogbonna/AWS-Lambda-Authorizer) on GitHub.

1. First thing, define the resources in our SAM template.

This includes:
- The API
- Authorizer
- Environment variables
`template.yml`.

```yaml
Globals:
  Function:
    Runtime: nodejs12.x
    Timeout: 540
    MemorySize: 256
    Environment:
      Variables:
        # Environment variables for our application
        STAGE: test
        USER_POOL: eu-west-1_xxxxxxxxx
        REGION: eu-west-1

Resources:
  ApplicationAPI:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage
      Auth:
        DefaultAuthorizer: APIAuthorizer
        Authorizers:
          APIAuthorizer:
            FunctionPayloadType: REQUEST
            # Get the Amazon Resource Name (Arn) of our Authorizer function
            FunctionArn: !GetAtt Authorizer.Arn
            Identity:
              Headers:
              # Define the headers the API would look for. We make use of Bearer tokens so it's stored in Authorization header.
                - Authorization
               # Caching policy; here we define in seconds how long API Gateway should cache the policy for.
              ReauthorizeEvery: 300

  Authorizer:
    Type: AWS::Serverless::Function
    Properties:
      # Reference the relative path to our authorizer handler
      Handler: src/functions/middlewares/authorizer.handler
      Description: Custom authorizer for controlling access to API

```

2. We implement our authorization function
`authorizer.js`

```javascript
const { getUserClaim, AuthError, getPublicKeys, webTokenVerify } = require("./utils");


/**
 * Authorizer handler
 */
exports.handler = async (event, context, callback) => {
  const principalId = "client";

  try {
    const headers = event.headers;

    const response = await getUserClaim(headers);
    return callback(null, generatePolicy(principalId, "Allow", "*", response));
  } catch (error) {
    console.log("error", error);
    const denyErrors = ["auth/invalid_token", "auth/expired_token"];

    if (denyErrors.includes(error.code)) {
      // 401 Unauthorized
      return callback("Unauthorized");
    }

    // 403 Forbidden
    return callback(null, generatePolicy(principalId, "Deny"));
  }
};

/**
 * Generate IAM policy to access API
 */
const generatePolicy = function (principalId, effect, resource = "*", context = {}) {
  const policy = {
    principalId,
    policyDocument: {
      Version: "2012-10-17",
      Statement: [
        {
          Action: "execute-api:Invoke",
          Effect: effect,
          Resource: resource,
        },
      ],
    },
    context, // Optional output with custom properties of the String, Number or Boolean type.
  };

  return policy;
};

/**
 * Grant API access to request
 * @param {object} h Request headers
 */
exports.getUserClaim = async (h) => {
  try {
    const authorization = h["Authorization"] || h["authorization"];

    const token = authorization.split(" ")[1];
    const tokenSections = (token || "").split(".");
    if (tokenSections.length < 2) {
      throw AuthError("invalid_token", "Requested token is incomplete");
    }

    const headerJSON = Buffer.from(tokenSections[0], "base64").toString("utf8");
    const header = JSON.parse(headerJSON);
    const keys = await getPublicKeys();
    const key = keys[header.kid];
    if (key === undefined) {
      throw AuthError("invalid_token", "Claim made for unknown kid");
    }
    
    // claims is verified.
    const claims = await webTokenVerify(token, key.pem);
    return { claims: JSON.stringify(claims) };
  } catch (error) {
    const message = `${error.name} - ${error.message}`;
    if (error.name === "TokenExpiredError")
      throw AuthError("expired_token", message);

    if (error.name === "JsonWebTokenError")
      throw AuthError("invalid_token", message);

    throw error;
  }
};

```

3. We implement our utils file
`utils.js`
```javascript
const { promisify } = require("util");
const fetch = require("node-fetch");
const jwkToPem = require("jwk-to-pem");
const jsonwebtoken = require("jsonwebtoken");

/**
 * Get public keys from Amazon Cognito
 */
exports.getPublicKeys = async () => {
  const issuer = `https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL}`;
  const url = `${issuer}/.well-known/jwks.json`;
  const response = await fetch(url, { method: "get" });
  const publicKeys = await response.json();

  return publicKeys.keys.reduce((total, currentValue) => {
    const pem = jwkToPem(currentValue);
    total[currentValue.kid] = { instance: currentValue, pem };
    return total;
  }, {});
};

/**
 * Using JSON Web Token we verify our token
 */
exports.webTokenVerify = promisify(jsonwebtoken.verify.bind(jsonwebtoken));

/**
 * Generate Auth Error
 */
exports.AuthError = (code, message) => {
  const error = new Error(message);
  error.name = "AuthError";
  error.code = `auth/${code}`;
  return error;
};



```


4. We define helper functions to help us parse our event request.

Our claims is stored in `event.requestContext.authorizer`.
From our authorization function above we are only able to pass strings from our API Gateway authorizer, so it's stringified in the `claims` objects

`helpers.js`

```javascript
 * Parse claims from event request context
 * @param {import("aws-lambda").APIGatewayProxyEvent} event
 */
exports.parseClaims = (event) => {
  return JSON.parse(event.requestContext.authorizer.claims);
};

```

## Conclusion
This rounds up our implementation.
This post serves as a reference to how we implemented authorization in our API, any further updates to our workflow would be made on this post.

For more clarification, you can reach out to me on [Email](mailto:promise.bones@gmail.com) or [Twitter](https://twitter.com/6ones_)

## Resources
[Use API Gateway Lambda authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html)