developer
interactivity
Generate Access Tokens Using AWS Services
Katie Gray

Building applications utilizing the Dolby.io Interactivity API requires a key and a secret for authentication. While you don’t want the consumer key and secret in client code, you may also want to get an application up and running quickly without having to set up an entire backend infrastructure.

Amazon Web Services (AWS) cloud platform allows you to build quickly without having to worry about infrastructure by building a serverless application. AWS services can act as an authentication proxy to request an access token from the Interactivity REST API for use in a client application.

This article will first demonstrate how to set up a local authentication server using Node.JS and Express and test locally with some sample client code.

Then the following sections will detail how to modify that server code to use in a Lambda function and configure with API Gateway to trigger that Lambda function. This way, an authentication server that returns an access token can be built on AWS Services:

  • Local Express Server
  • AWS Console 
  • AWS CLI
  • Automated Bash Script

You can find the source code from this article on GitHub: https://github.com/dolbyio-samples/blog-token-server-with-aws-services

NOTE: Ultimately, it is your responsibility as a developer to protect this authentication server from unauthorized access. We hope this article helps you take the next steps and avoid accidentally putting your credentials in client code. Currently, the API Gateway example is exposed to outside use via the invoke url. There are several suggestions at the end of the guide pointing to documentation on AWS how to secure the invoke url. 

It is also important to keep in mind that there is a cost to using AWS. Additionally, if the API Gateway and Lambda Function are used for testing only, it is recommended to clean up (delete) the gateway and function after testing is complete.

Section 0 – Set Up Local Authentication Server and Client

This section will demonstrate how to get a token and authenticate with the Interactivity API by setting up a local authentication server with Express, as well as some sample client code to invoke the endpoint. Testing locally will demonstrate the core functionality of the authentication server. This server code can be modified to use in a Lambda function, as will be demonstrated in the following sections. 

You will need:

Create a file directory structure:

local-server-sample-code
├── client
│   ├── app.js
│   └── index.html
└── server
    ├── index.js
    └── env

Within the server directory, initialize npm and install the dependencies. These dependencies will allow the building of a server framework, handling of http requests, providing middleware to enable cors, and loading environment variables from the .env file.

cd server
npm init -y
npm install axios express dotenv cors

Your file structure should look similar to the following:

local-server-sample-code
├── client
│   ├── app.js
│   └── index.html
└── server
    ├── index.js
    ├── node_modules/
    ├── package-lock.json
    ├── package.json
    └── env

Here is the server code for index.js. See https://github.com/dolbyio-samples/blog-token-server-with-aws-services/tree/master/0-set-up-local-authentication-server-and-client for the Github Repo:

// express server
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const dotenv = require('dotenv');
 
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
 
const CONSUMER_KEY = process.env.CONSUMER_KEY;
const CONSUMER_SECRET = process.env.CONSUMER_SECRET;
const PORT = 3001;
const credentials = new Buffer.from(
  CONSUMER_KEY + ':' + CONSUMER_SECRET
).toString('base64');
 
const url = 'https://session.voxeet.com/v1/oauth2/token';
const config = {
  headers: {
    Authorization: 'Basic ' + credentials,
  },
  body: { grant_type: 'client_credentials' },
};
 
async function fetchToken() {
  let result = await axios.post(url, {}, config);
  return result.data;
}
 
app.get('/token', async (req, res) => {
  let response = await fetchToken();
  return res.json(response);
});
 
app.listen(PORT, () => console.log(`Listening at http://localhost:${PORT}`));

Inside the .env file, insert the following, and replace with your own key and secret:

CONSUMER_KEY=your_key
CONSUMER_SECRET=your_secret

Dolby.io Interactivity key and secret can be found on Dolby.io on the dashboard.

To start the server, run node index.js from within the server directory. 

You should see “Listening at http://localhost:3001” in the console, indicating that the authentication server is ready for requests. 

At a high level, we are creating a server running on ‘http://localhost:3001′. When a request comes into the /token endpoint from the client, the server then makes a request to the Voxeet token REST endpoint on the behalf of the client using the key and secret, and returns an access token and refresh token in the response. This way, the key and secret are kept out of the client code, as the authentication server is acting as a proxy to make the request and return the access token to the client. 

Test with Client Code

The following sample client code can be used to test the authentication server that is running locally. This client code can also be used in later sections to test the authentication server on AWS services. 

Sample Client Code for index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Client Sample Code</title>
  <script
      src="https://unpkg.com/@voxeet/voxeet-web-sdk"
      type="text/javascript"
    ></script>
    <script src="app.js" type="module"></script>
</head>
<body>
  <input id="get-token" type="button" value="get token" />
  <div> Access Token: </div>
  <div id="access-token"></div>
  <input id="init-session" type="button" value="initialize session" />
</body>
</html>

and app.js:

const BASE_URL = 'http://localhost:3001';
let tokenUrl = `${BASE_URL}/token`;
 
let accessTokenDiv = document.getElementById('access-token');
 
async function refreshVoxeetToken() {
  return fetch(tokenUrl);
}
 
async function getVoxeetToken() {
  // initializeToken authorization flow
  return fetch(tokenUrl)
    .then((res) => {
      return res.json();
    })
    .then(async (result) => {
      await VoxeetSDK.initializeToken(result.access_token, async () => {
        await refreshVoxeetToken();
      });
      accessTokenDiv.innerHTML = result.access_token;
    })
    .then(() => {
      console.log('token received');
    })
    .catch((error) => {
      console.log(error);
    });
}
 
async function initializeVoxeetSession() {
  try {
    await VoxeetSDK.session.open({
      name: 'test-name',
    });
    console.log('session initialized!');
  } catch (e) {
    alert('Something went wrong: ' + e);
  }
}
 
document.getElementById('get-token').onclick = getVoxeetToken;
document.getElementById('init-session').onclick = initializeVoxeetSession;

Open the index.html file in your browser of choice, and click “Get Token”. You should see the access token rendered in the browser. The response from the local authentication server should look like:

{
  "token_type":"Bearer",
  "access_token":"access_token",
  "refresh_token":"refresh_token",
  "expires_in":3600
}

Section 1 – Using AWS Console to Set Up Lambda Function and API Gateway

In the previous section, an Express server was built to handle the authentication calls to the Interactivity API. Now, that same Express server code, when modified, can be used in an AWS Lambda function.

> NOTE A Lambda function is a service that allows you to run your code without managing servers. Lambda functions can be configured to run in response to events. In the case of this tutorial, the Lambda function will be run in response to incoming HTTP requests using Amazon API Gateway. To learn more, see the AWS Documentation.

In order to get the Lambda function running, the following steps need to be completed:

  1. Modify local authentication server code
  2. Zip it up
  3. Create a Lambda function in the AWS Console and upload the zip
  4. Set environment variables in the AWS Console
  5. Test

Modify local authentication server code

The code in the local server/index.js file can be reused in the Lambda function with some modifications. The code needs to be modified because Lambda functions use different syntax.

See Github repo for the code: https://github.com/dolbyio-samples/blog-token-server-with-aws-services/tree/master/1-lambda-function

Note the following modifications in server/index.js for the Lambda function:

  • only requires axios
  • does not use Express
  • within the fetchToken function, the response body is structured differently. This is due to the requirements of a Lambda function with a proxy integration. See AWS documentation for more information
  • notice the `exports.handler` at the bottom of the code. This is the AWS Lambda function handler in node.js. See the AWS documentation for more information. 

The sample client code can remain the same, with the exception of changing the url that the client is calling (ie the invoke url for AWS Gateway instead of localhost) in server/index.js:

const axios = require('axios');
const CONSUMER_KEY = process.env.CONSUMER_KEY;
const CONSUMER_SECRET = process.env.CONSUMER_SECRET;
const credentials = new Buffer.from(
  CONSUMER_KEY + ':' + CONSUMER_SECRET
).toString('base64');
const url = 'https://session.voxeet.com/v1/oauth2/token';
const config = {
  headers: {
    Authorization: 'Basic ' + credentials,
  },
  body: { grant_type: 'client_credentials' },
};
 
async function fetchToken() {
  const token = {};
  const res = {};
  await axios
    .post(url, {}, config)
    .then(function (response) {
      token.access_token = response.data.access_token;
      token.refresh_token = response.data.refresh_token;
      res.statusCode = 200;
      res.headers = {
        'Access-Control-Allow-Headers': 'Content-Type',
        'Access-Control-Allow-Origin': '*', // NOTE this is to allow for CORS when testing locally
        'Access-Control-Allow-Methods': 'OPTIONS,POST,GET',
      };
      res.body = JSON.stringify(token);
    })
    .catch(function (error) {
      // handle error
      console.log(error);
    });
  return res;
}
 
exports.handler = async (event) => {
  let response = await fetchToken();
  return response;
};

Zip it up

The Lambda function code needs to be zipped up as a deployment package with the necessary dependencies in order to deploy the code to Lambda. 

Although there is a code editor within the AWS Console when you create a Lambda function, simply copying and pasting the modified server/index.js code will not suffice. Since the dependency axios is being used, a deployment package must first be zipped up. That way, when the deployment package is uploaded the the Lambda function, the Lambda function will have the necessary dependencies needed to execute. 

/server
├── index.js
├── node_modules/
├── package-lock.json
└── package.json

The files index.js, the directory node_modules, and the file package.json will need to be zipped up. 

There are several options to create a zip file: 

  • use OSX Finder by right clicking on the contents of the server directory and selecting Compress “server”
  • use WinZip or similar applications
  • run zip from a command line terminal
cd server
zip -r function.zip .

See the AWS Documentation for more information. 

Create a lambda function in the AWS Console and upload zip

To get started with a basic Lambda function set up, see the AWS Documentation to create a lambda function.

> NOTE this example will be written in Node.js, so leave Node.js selected during setup. 

There is boilerplate setup in the console after the Lambda function is created. This code will be replaced with code that will fetch the access token from the Interactivity API. 

Back in the AWS console, select Actions > Upload a .zip file

The sample code should now be replaced with the index.js, node_modules, and package.json files.

Set environment variables

Before the Lambda function can be tested, the environment variables “CONSUMER_KEY” and ad “CONSUMER_SECRET” need to be set.

> NOTE Interactivity Key and Secret can be found on the Dolby.io Dashboard

  1. In the AWS console, choose Configuration > Environment variables
  2. Under “Environment variables”, click “Edit”
  3. Choose “Add environment variable” to add your Interactivity CONSUMER_KEY, and click again to add your Interactivity CONSUMER_SECRET.
  4. Choose Save

> NOTE In a production environment, it is best practice to use AWS Secret Manager to handle environment variables. Since this is a test, we will input them in the console

Test Lambda function in the AWS Console

To test, click on “Test” at the top of the AWS code editor.

Enter an event name, ie “test” and delete the provided keys and values.

Click Create.

You can then click “Test” back in the console to run the Lambda Function.

You should see a statusCode 200, with the response body as the access token.

In order for this Lambda function to be accessible to client code, it needs to be integrated with Amazon API Gateway. We will do that in the next section. 

Lambda function troubleshooting

  • Be sure to click the “Deploy” button after every change made to the Lambda function. Changes will not be persisted if the function is not deployed.

Amazon API Gateway with Lambda Proxy Integration Setup via AWS Console

AWS services offer several options to invoke a Lambda function. In this case, Amazon API Gateway will be used to generate an invoke URL to route HTTP requests to the Lambda function. This way, when the client makes a GET request to the invoke URL, the response body will contain the access token returned by the Lambda function.

See the AWS documentation for more details. 

Setting this up via the console takes a few clicks. 

  1. Go to the API Gateway section of the AWS Console
  2. Make sure the API Gateway is created within the same AWS region as the Lambda function
  3. Select to build a new REST API
  1. create a /token resource on the gateway, and configure the previously created Lambda function as the method
    • Create methods and resources by clicking the ‘Actions’ dropdown
    • when creating a method, select GET
    • check “Lambda Proxy Integration”
    • In the Lambda function dropdown, select the name of the Lambda function you created earlier
  2. Under Actions, click “Deploy API”
  3. On the next screen, the invoke URL is shown, which you can test after adding the resource to the url

We now have an authentication server set up on AWS services, that will return an access token for the Interactivity API when a request comes in to the invoke url. We can now test the endpoint for the authentication server from the sample client code. 

Testing API Gateway and Lambda Function from Sample Client Code

Test with cURL

If the invoke URL is tested via curl, the response should be a body containing the access token.

Test with Client Code

In your client sample code, replace the tokenUrl with the invoke url with the full resource path (https://[id].execute-api.[region].amazonaws.com/prod/token). 

You should see the following in the browser:

AWS API Gateway Troubleshooting

Section 2 – Using AWS CLI to Set Up Lambda Function and API Gateway

The AWS CLI tool can be used instead of the AWS console.

You will need:

The following steps need to be completed:

  1. Create a trust policy 
  2. Create an IAM role and attach basic Lambda execution role permissions
  3. Create the Lambda function that uses the zipped up code
  4. Update environment variables of created Lambda function with key and secret
  5. Test Lambda function

Create a trust policy

Create a directory for lambda function setup, and create a trust-policy.json file

mkdir aws_cli_test
touch trust-policy.json

Copy the following as an example trust-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Create an IAM role and attach basic Lambda execution role permissions

To give your Lambda function permissions to access AWS resource, use the `create-role` command. In this case, the role name is `lambda-ex`

aws iam create-role --role-name lambda-ex --assume-role-policy-document file://trust-policy.json

You should see something like the following output:

{
    "Role": {
        "Path": "/",
        "RoleName": "lambda-ex",
        "RoleId": "xxxxxxxxxxxxxx",
        "Arn": "arn:aws:iam::123456789012:role/lambda-ex",
        "CreateDate": "2020-01-17T23:19:12Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

Next, add permissions to the role using the `attach-policy-to-role` command

aws iam attach-role-policy --role-name lambda-ex --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

The `AWSLambdaBasicExecutionRole` gives permissions to write logs to the CloudWatch Logs, which are useful for debugging.

Create the Lambda function that uses the zipped up code

The same zipped up deployment package can be used from the previous section. 

Create a Lambda function with the zipped file with the create-function command, using the role that you created earlier. 

aws lambda create-function --function-name my-function \
--zip-file fileb://function.zip --handler index.handler --runtime nodejs12.x \
--role arn:aws:iam::123456789012:role/lambda-ex
                    ^^^^^^^^^^^^
                    replace with your IAM user account ID

You should see output similar to the following:

{
    "FunctionName": "my-function",
    "FunctionArn": "arn:aws:lambda:us-east-2:xxxxxxx:function:my-function",
    "Runtime": "nodejs12.x",
    "Role": "arn:aws:iam::xxxxxxx:role/lambda-ex",
    "Handler": "index.handler",
    "CodeSha256": "xxxxxxxx/xxxxxx",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "88ebe1e1-bfdf-4dc3-84de-3017268fa1ff",
    ...
}

If you need to change your lambda function, you can zip the files again and update the lambda function with: 

aws lambda update-function-code --function-name my-function --zip-file fileb://function.zip

Update environment variables of created Lambda function with key and secret

Replace the consumer key and secret with your Interactivity API key and secret respectively.

aws lambda update-function-configuration --function-name my-function \
    --environment "Variables={CONSUMER_KEY=your_consumer_key,CONSUMER_SECRET=your_consumer_secret}"

You should see the following output with your set environment variables:

{
    "FunctionName": "my-function",
    "FunctionArn": "arn:aws:lambda:us-west-1:XXXXXXXXX:function:my-function",
    "Runtime": "nodejs12.x",
    "Role": "arn:aws:iam::XXXXXXXXX:role/lambda-ex",
    "Handler": "index.handler",
    "CodeSize": 1169,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2021-02-19T20:30:14.783+0000",
    "CodeSha256": "T+/OhCY+yTpvS8mFFSUuS9q5nAaiJ4zKEFLh2xiCsSo=",
    "Version": "$LATEST",
    "Environment": {
        "Variables": {
            "CONSUMER_SECRET": "your_key",
            "CONSUMER_KEY": "your_secret"
        }
    },
    ...
}

Test Lambda function

In the terminal, run:

aws lambda invoke --function-name my-function response.json

You should then see the response.json:

{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

Amazon API Gateway with Lambda Proxy Integration Setup via AWS CLI

While setting up a Lambda proxy integration with the Amazon API gateway via the AWS Console takes a few clicks, doing so via the command line is more complex. 

On a high level, the steps needed to be completed are:

  1. Create the gateway
  2. Add the resource (endpoint) of the API
  3. Attach GET method to endpoint
  4. Configure the integration
  5. Deploy
  6. Grant the API gateway access to the lambda function

For more details on how to do so, see the AWS Documentation

Testing from sample client code

Once you have set up an Amazon API Gateway Via the command line, you can then test the generated invoke url the same way it was tested in the previous section using the sample client code. 

Section 3 – Bash Script to Automatically set up Lambda and API Gateway

This script programmatically creates the Lambda function and API Gateway for you. 

To run this script you will need: 

  • ARN for the Role you want to use to grant to your Lambda function
    • run `create_fetch_token_iam_role.sh` to create an ARN Role if you do not have one. This creates an execution role and attached the role policy to give the Lambda function permissions as a service role
  • Interactivity Consumer Key
  • Interactivity Consumer Secret
  • optional: name for function (default is fetchInteractivityToken)
  • optional: region (default is us-west-1)

Github Repo: https://github.com/dolbyio-samples/blog-token-server-with-aws-services/tree/master/3-bash-script

# these commands assume you are running them within /bin
# get ARN for Role (if you don't already have one)
./create_fetch_token_iam_role.sh
 
# returns:
Created a new role called FetchInteractivityTokenRole. The ARN for this is: ROLE_ARN
 
# create Lambda and API Gateway Setup to get Invoke URL for access token
# function name and region are optional
./aws_setup.sh <ARN_ROLE> <CONSUMER_KEY> <CONSUMER_SECRET> <-n FUNCTION_NAME> <-o REGION>
 
# returns:
URL to use to GET interactivity access token:
https://XXXXXXXXXX.execute-api.REGION.amazonaws.com/prod/token

This script is automating a number of steps:

  1. Creating an IAM role with basic Lambda execution role permissions
  2. Creating the Lambda function that uses the zipped up fetch_token.zip
  3. Getting ARN of Lambda function just created
  4. Updating environment variables of created Lambda function with key and secret
  5. Creating API Gateway
  6. Getting ID of API just created
  7. Getting parent resource id of API
  8. Adding proxy resource to API Gateway
  9. Adding the GET method on the resource
  10. Configuring integration with type: aws_proxy
  11. Deploying Gateway
  12. Creating permissions for gateway to invoke Lambda function
  13. Printing the URL to use to get access token

Section 4 – Securing Amazon API Gateway

Ultimately, it is your responsibility as a developer to protect this authentication server from unauthorized access. We hope this article helps you take the next steps and avoid accidentally putting your credentials in client code. Currently, the API example is exposed to outside world via the invoke url of the API Gateway. In addition, the Access-Control-Allow-Origin is set to ‘*’, meaning any origin can access the url. This can be adjusted in the index.js sample file.  

There are several ways to secure the endpoint from outside use.

Distributing Amazon API Gateway with CloudFront and implement Basic Authentication with Lambda@Edge Function

If you distribute your API Gateway with Cloudfront, you can then use a Lamda@Edge function to implement basic authentication. The Lambda@Edge function will trigger with every viewer request coming into Cloudfront and check for Authorization header. 

Lambda Authorizer

Tags: aws, security
RELATED POSTS
DEVELOPER
MEDIA
Recording Audio on Android with Examples

How-to get started recording audio on Android with the most common libraries compared.

Megan Ren
|
android
INTERACTIVITY
PRODUCT
To Build the Best Web Conferencing Tool, Use a Product Roadmap

Strategies and product methodologies to build a web conferencing application that users will love.

Dolby.io
|
INTERACTIVITY
PRODUCT
MediGuru Develops Telehealth Platform for 400+ Providers with Dolby.io

Dolby.io Interactivity APIs are included in the MediGuru tech stack to support HIPAA compliance, ease of use and scalability.

Stephane Giraudie
|
case-study
We're happy to chat about our APIs, SDKs...or magic.