Serverless authorizers - custom REST authorizer
Tags: serverless, aws, authorizer, node
Published: 2017-11-05 23:02:12

---
title: "Serverless authorizers - custom REST authorizer"
published: true
description: "Basics of authorizers. Writing custom authorizers for REST API with Serverless Framework"
excerpt: "Basics of authorizers. Writing custom authorizers for REST API with Serverless Framework"
tags: [serverless, aws, authorizer, node.js]
---

In the series of articles I will explain basics of Servlerless authorizers in Serverless Framework: where they can be used and how to write custom authorizers for Amazon API Gateway.
I am saying 'authorizers' but it is first of all about authentication mechanism. Authorization comes as second part.

Before we dive into details let's think for a moment what kind of authentication techniques are available.

* Basic

The most simple and very common is basic authentication where each request contains encoded username and password in request headers, e.g.:

```
GET /spec.html HTTP/1.1
Host: www.example.org
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
```

* Token in HTTP headers

An example of this kind of authentication is [OAuth 2.](https://oauth.net/2/) and [JWT](https://jwt.io). The API client needs to first call sign-in endpoint (unsecured) with username and password in the payload to obtain a token. This token is later passed in headers of subsequent secured API calls.
A good practice is to expire the token after some time and let the API client refresh it or sign in again to receive a new token.

```
GET /resource/1 HTTP/1.1
Host: example.com
Authorization: Bearer mF_9.B5f-4.1JqM
```

* Query Authentication with additional signature parameters.

In this kind of authentication a signature string is generated from plain API call and added to the URL parameters.
E.g. of such authentication is used by Amazon in [AWS Signature Version 4](http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html)

There are probably more variations of the above-mentioned techniques available, but you can get a general idea.

When to use which authentication mechanism? 

The answer is as usual - it depends!

It depends if our application is a public REST API or maybe on-premises service which does not get exposed behind company virtual private network.
Sometimes it's also a balance between security and ease of use. 

Let's take e.g. Amazon Signature 4 signed requests.
They are hard to create manually without using helpers API to sign requests (forget about Curl, which you could use easily with Basic and Token headers).
On the other hand, Amazon explains that these requests are secured against replay attacks (see more [here](http://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html#why-requests-are-signed)).

If you are building an API for banking then it must be very secure, but for most of the non-mission-critical cases, Token headers should be fine.

So we have chosen authentication and authorization mechanism. Now, how do we implement it with AWS?

We can do our own user identity storage or use an existing one, which is Amazon IAM ( [Identity and Access Management](https://aws.amazon.com/iam/?sc_channel=PS&sc_campaign=acquisition_PL&sc_publisher=google&sc_medium=iam_b&sc_content=amazon_iam_e&sc_detail=amazon%20iam&sc_category=iam&sc_segment=164224034079&sc_matchtype=e&sc_country=PL&s_kwcid=AL!4422!3!164224034079!e!!g!!amazon%20iam&ef_id=VgfxNgAAAPg@lfIu:20170910060310:s) ).

The last one has this advantage, that we don't need to worry about secure storing of username and password in the database but rely on Amazon.


# Custom REST Authorizer
Let's first look at a simple example of REST API authorized with a custom authorizer

### Create a new SLS project

```
serverless create --template aws-nodejs --path serverless-authorizers
```

### Add simple endpoint /hello/rest
The code is [here](https://github.com/piczmar/serverless-authorizers/tree/a48832c7869256226bae5e15f79822e87c738d2d) (Note the commit ID). 
The endpoint is completely insecure.

### Deploy application

```
sls deploy -v function -f helloRest

```

When it deploys it will print endpoint URL, e.g.:

```
endpoints:
  GET - https://28p4ur5tx8.execute-api.us-east-1.amazonaws.com/dev/hello/rest

```

### Call endpoint from client
Using curl we can call it like that:

```
curl https://28p4ur5tx8.execute-api.us-east-1.amazonaws.com/dev/hello/rest

```

### Secure endpoint with custom authorizer.
For the sake of simplicity, we will only compare the token with a hardcoded value in authorizer function.
In real case this value should be searched in the database. There should be another unsecured endpoint allowing to get the token value for username and password sent in the request.

Our authorizer will be defined in serverless.yml like this:

```javascript
functions:
  authorizerUser:
    handler: authorizer.user
  helloRest:
    handler: helloRest.handler
    events:
      - http:
          path: hello/rest
          method: get
          authorizer: ${self:custom.authorizer.users}

custom:
  stage: ${opt:stage, self:provider.stage}
  authorizer:
    users:
      name: authorizerUser
      type: TOKEN
      identitySource: method.request.header.Authorization
      identityValidationExpression: Bearer (.*)
```

In http events section we defined authorizer as:

```javascript
authorizer: ${self:custom.authorizer.users}
```

This will link to custom section where we defined authorizer with name `authorizerUser`. This is actually the name of a function which we defined in `functions` section as:

```javascript
functions:
  authorizerUser:
    handler: authorizer.user
```
The `handler` points to a file where authorizer handler function is defined by naming convention: `authorizer.user` means file `authoriser.js` with exported `user` function.

The implementation will look as follows:

```javascript
'use strict';

const generatePolicy = function(principalId, effect, resource) {
  const authResponse = {};
  authResponse.principalId = principalId;
  if (effect && resource) {
    const policyDocument = {};
    policyDocument.Version = '2012-10-17';
    policyDocument.Statement = [];
    const statementOne = {};
    statementOne.Action = 'execute-api:Invoke';
    statementOne.Effect = effect;
    statementOne.Resource = resource;
    policyDocument.Statement[0] = statementOne;
    authResponse.policyDocument = policyDocument;
  }
  return authResponse;
};

module.exports.user = (event, context, callback) => {

  // Get Token
  if (typeof event.authorizationToken === 'undefined') {
    if (process.env.DEBUG === 'true') {
      console.log('AUTH: No token');
    }
    callback('Unauthorized');
  }

  const split = event.authorizationToken.split('Bearer');
  if (split.length !== 2) {
    if (process.env.DEBUG === 'true') {
      console.log('AUTH: no token in Bearer');
    }
    callback('Unauthorized');
  }
  const token = split[1].trim();
  /*
   * extra custom authorization logic here: OAUTH, JWT ... etc
   * search token in database and check if valid
   * here for demo purpose we will just compare with hardcoded value
   */
   switch (token.toLowerCase()) {
    case "4674cc54-bd05-11e7-abc4-cec278b6b50a":
      callback(null, generatePolicy('user123', 'Allow', event.methodArn));
      break;
    case "4674cc54-bd05-11e7-abc4-cec278b6b50b":
      callback(null, generatePolicy('user123', 'Deny', event.methodArn));
      break;
    default:
      callback('Unauthorized');
   }

};
```

Authorizer function returns an Allow IAM policy on a specified method if the token value is `674cc54-bd05-11e7-abc4-cec278b6b50a`. 
This permits a caller to invoke the specified method. The caller receives a 200 OK response. 
The authorizer function returns a Deny policy against the specified method if the authorization token is `4674cc54-bd05-11e7-abc4-cec278b6b50b`.
If there is no token in the header or unrecognized token, it exits with HTTP code 401 'Unauthorized'.

[Here](https://github.com/piczmar/serverless-authorizers/tree/67fa5ef984184bd11b9de76f4bdc915545a67442) is the complete source code (note the commit ID).

We can now test the endpoint with Curl:

```
curl https://28p4ur5tx8.execute-api.us-east-1.amazonaws.com/dev/hello/rest

{"message":"Unauthorized"}

curl -H "Authorization:Bearer 4674cc54-bd05-11e7-abc4-cec278b6b50b" https://28p4ur5tx8.execute-api.us-east-1.amazonaws.com/dev/hello/rest

{"Message":"User is not authorized to access this resource with an explicit deny"}

curl -H "Authorization:Bearer 4674cc54-bd05-11e7-abc4-cec278b6b50a" https://28p4ur5tx8.execute-api.us-east-1.amazonaws.com/dev/hello/rest

{"message":"Hello REST, authenticated user: user123 !"}

```

More about custom authorizers in [AWS docs](http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html)

In the next series of Serverless Authorizers articles I will explain IAM Authorizer and how we can authorize GraphQL endpoints.

---

This article was initially posted at [https://cloudly.tech](https://cloudly.tech/blog/serverless-authorizers-1/) which is my blog about Serverless technologies and Serverless Framework in particular.