How to authenticate your JavaScript SDK using AWS Lambda & Serverless published 11/12/2019 | 10 min read

Hello my friends, and welcome to a new story. Today we'll discuss the shortest method to use API key authentication for your JavaScript SDK using AWS Lambda.



The use case

So you have a JavaScript SDK (file) that you want to allow only clients with valid key and valid domain to actually be able to request and download the SDK file.

Let's say we want https://mydomain.com/sdk.min.js?key=XXXX to work when requested from client.com website, and of course when the key is valid, and should send 401 unauthorized if the key is not valid, or the key is valid, but from a different domain.



Checklist

Here's our checklist from start to finish:

  1. Install Serverless (if you haven't already).
  2. Initialize project.
  3. Create our Lambda.
  4. Try locally.
  5. Deploy.

1. Install Serverless

This method installs serverless globally, but you can also install it locally to your project only if you prefer.

  
npm install -g serverless
yarn global add serverless

2. Initialize project

First thing we need is to run the serverless CLI tool, it'll start an interactive process for creating a new project, it's pretty straightforward.

  
serverless

This will initialize the interactive CLI of serverless for creating new project

The above command will start the interactive process of creating a new serverless project, I've included the steps below:

  
$ serverless
    $ Serverless: No project detected. Do you want to create a new one? (Y/n) 
    $ Serverless: What do you want to make? (Use arrow keys)
    ❯ AWS Node.js 
      AWS Python 
    $ Serverless: What do you want to call this project? 
    $ Project successfully created in 'lambda-sdk-auth' folder.
    $ You can monitor, troubleshoot, and test your new service with a free Serverless account.
    $ Serverless: Would you like to enable this? (Y/n)
    $ Serverless: Would you like to setup a command line <tab> completion? (Y/n)

The last 2 steps are optional.

Now we have a directory called lambda-sdk-auth which include our new project.



Files tree

You should now have 3 files in your project:

  1. .gitignore Which is used to instruct git to exclude some paths like node_modules & .serverless.
  2. handler.js We'll write our lambda function here.
  3. serverless.yml This file include all of our serverless configurations.

3. Create our Lambda function

Let's start by updating the default serverless config, replace serverless.yml's contents with the following:

  
service: lambda-sdk-auth
    
provider:
  name: aws
  runtime: nodejs10.x

  stage: dev
  region: us-east-1

functions:
  sdk:
    handler: handler.default
    events:
      - http:
          path: sdk.min.js
          method: get

Let me explain what we've got here:

  1. Service: This will be our project name, it'll be used in the deployment's CloudFormation template.
  2. Provider: The cloud provider, serverless supports Azure, Google Compute Cloud and AWS, we're using AWS in this example, and NodeJS 10 as a runtime (choose earlier during initializing project).
  3. Stage: The API Gateway stage.
  4. Region: The region in AWS where you wish to deploy your Lambda to.
  5. Functions: List of our Lambda functions, we only have 1.
  6. Function.sdk: This is our Lambda function name.
  7. Function.sdk.handler: The path to our Lambda function in the project, the .default part of the value means it will look for module.exports.default in that file.
  8. Function.sdk.events: Events list for this function, we can have different types of events like HTTP or a scheduled/cron. We're using HTTP, which is the only suitable one for our use case.
  9. Function.sdk.events.http.path: The URL structure for your Lambda.
  10. Function.sdk.events.http.method: The HTTP request method, we're using GET.

Before writing our Lambda code, there's 1 last step we need to do, is to install serverless-offline node module, which will make local development easier by allowing us to invoke our Lambda from the browser, to best simulate AWS environment.

Install serverless-offline:

  
$ npm install serverless-offline -D

Install serverless offline as a dev dependency via npm

Or via Yarn:

  
yarn add serverless-offline -D

Install serverless offline as a dev dependency via yarn

Finally update serverless file to include plugins section, with this serverless plugin:

  
service: lambda-sdk-auth

plugins:
  - serverless-offline

provider:
  name: aws
  runtime: nodejs10.x

  stage: dev
  region: us-east-1

functions:
  sdk:
    handler: handler.default
    events:
      - http:
          path: sdk.min.js
          method: get

Now let's do 2 primary things:

  1. Create our SDK file.
  2. Create the lambda function.

Create a file called sdk.js in the root of your project, and add anything to it, example:

  
const test = () => 123;

Our SDK's contents

Now let's move on to writing our function's logic; Let's start by replacing our Lambda file with the below contents, then I'll explain it in details:

  
const fs = require('fs');
    
module.exports.default = async (event, context) => {
  // Get the "key" from request query params
  const queryParams = event.queryStringParameters;
  const key = queryParams && queryParams.key;
  
  // Return 401 if key is not passed in GET query params
  if (!key) {
    return {
      statusCode: 401,
      body: JSON.stringify(
        {
          error: 'Key is missing'
        }, null, 2),
    };
  };

  // Get requesting domain
  const clientDomain = event.headers.Origin;

  // This is a static mock for allowed clients.
  // You should normally get this from your database
  const allowedClients = [
    {
      domains: ['devspedia.com'],
      key: 'example-key'
    }
  ];
  
  // Check if client exists using it's unique key
  const client = allowedClients.find(client => client.key === key);

  // If client is not found, return 401 as the key is not authrorized
  if (!client) {
    return {
      statusCode: 401,
      body: JSON.stringify(
        {
          error: 'Key not registered'
        }, null, 2),
    };
  }

  // Check if the domain exists within the client's allowed
  // domains
  const validDomain = clientDomain && client.domains
    .find(domain => domain === clientDomain.split('://')[1]);

  // If the request if coming from a domain that was not 
  // allowed for this key, return 401
  if (!validDomain) {
    return {
      statusCode: 401,
      body: JSON.stringify(
        {
          error: 'Domain not registered for this key'
        }, null, 2),
    };
  }

  // Read the SDK javascript file's contents
  const sdk = fs.readFileSync(`${__dirname}/sdk.js`);

  // Return the file's contents with correct headers
  // and transform the sdk buffer array to a string
  return {
    statusCode: 200,
    headers: { 'Content-type': 'text/javascript' },
    body: sdk.toString(),
  };
};

hander.js

Here's what we did in brief points:

  1. Get the "key" from request query params.
  2. If key is not passed in query params, we'll return 401 unauthorized.
  3. If above step passed, we'll get the requesting domain.
  4. Then check if this domain is allowed for this requesting key.
  5. If not allowed, we'll return 401 unauthorized.
  6. If allowed, we read the sdk file's contents using the native node fs module.
  7. Finally return the file's content after setting the correct headers. Viola :)

4. Try locally

Now, to try this locally, we'll use cURL from the terminal. Make sure you're inside the project's directory, and then run:

  
serverless invoke local -f sdk -d "{"headers":{"Origin":"https://devspedia.com"},"queryStringParameters":{"key":"example-key"}}"

The response should be:

  
{
    "statusCode": 200,
    "headers": {
      "Content-type": "text/javascript"
    },
    "body": "const test = () => 123;"
}

Lambda response

Now, when this is requested from the browser, the browser will only render the body part of this response, and in this case, it's our SDK's content.

To actually simulate this in the browser, let's run serverless offline. Make sure you're inside the project directory, and then run:

  
serverless offline

Then in the browser, you can open devspedia.com and open up browser's developer tools/console, then make a request via fetch:

  
fetch(
  'http://localhost:3000/sdk.min.js?key=example-key'
).then(
  res => res.text()
).then(
  res => console.log(res)
);

fetch request the SDK

You should see const test = () => 123; in the logs, which is the file's contents.

5. Deploy

Deployment is easy, few steps you'll need to make:

  1. Install AWS CLI (https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html).
  2. Run aws configure in your terminal, and add your authentication info.
  3. Finally, from the root of your project, run serverless deploy.

That's it, thanks for reading devspedia, I love you, and I'll see you the next time :)



You may also like reading: