Auto-generate CloudFormation with no-code and no commitment 🚀
Try Now

Use case: API authentication

Learn how to secure all your AWS REST API requests using a single custom authorizer Lambda function.
Yev Krupetsky

July 14 2021 · 10 min read

SHARE ON

Use case: API authentication

In this article will walk through a common use case: adding authentication to APIs using an authorizer function. The authorizer function feature of API gateway enables you to protect some or all API handlers in a single location, reducing security risks, inconsistencies, and saves the handler Lambda functions from executing when requests don’t pass authentication and authorization.

We will build a simple CRUD service from a template and add authentication to it.

Architecture

Our service will be a serverless CRUD service, composed of an API Gateway with an authorizer Lambda function, a Lambda function to handle all API requests, and a DynamoDB table to store the service data.

I’ll use Auth0 as the users service, it will handle users sign up, login, and access token generation.

While I choose to use Auth0, you can use whichever service or method you prefer. The important part is the authorizer function that does the request validation. However, I recommend that you don’t implement an authentication solution yourself—it’s very time-consuming and easy to get wrong.

Here’s how our service will look like:

architecture

Setting up

What you’ll need

Create the project

We’ll start by creating a new Altostra project from the simple-crud-service-nodejs template.

  1. On the projects page, click Create Project.
  2. Enter a name.
  3. Select the simple-crud-service-nodejs template from the list.
  4. Click Create.

Altostra will create the new project and a Git repository for it.

new project

Open the project on your machine

Clone the project to the local machine and open it in Visual Studio Code:

  1. On the project page that you created, click Clone, select your preferred protocol and copy the link.
  2. Clone the repository to your local machine.
  3. Open the project directory in VS Code.
clone project

Login to Altostra CLI

The CLI requires authentication to work with your Altostra account.

Simply run:

$ alto login

Building the service

Let’s start by deploying the service without authentication to test that it works.

Deploy and test

The template we used to create the project comes with all the resources and code required for our service’s operations. All we need to do is deploy it using the Altostra CLI:

$ alto deploy blog --env Lab --push

The command above will deploy a stack named "blog" to the Lab environment. To learn more about environments in Altostra, read the documentation.

deploy project

Once the deployment finishes, we can get the URL for our API gateway:

$ alto endpoints blog
âś” Checking user credentials
âś” Working account: Lab
âś” Loading project
âś” Getting project domain names
api (Api01):
        StageProd: https://k9ifbfjszd.execute-api.us-east-1.amazonaws.com/Prod

Take the API URL from the output above and add /resource to the end to get the full URL. Refer to the architecture image above to see the routes and methods available on the API.

Add a user:

curl \
--request POST \
--url https://k9ifbfjszd.execute-api.us-east-1.amazonaws.com/Prod/resource \
--data '{"name": "Yev", "email": "yev@altostra.com"}' \
--header 'Content-Type: application/json'

Get all users:

$ curl \
--request GET
--url https://k9ifbfjszd.execute-api.us-east-1.amazonaws.com/Prod/resource
[{"id":"452ad845ac3a1bf80409538c9fefa408","email":"yev@altostra.com","name":"Yev"}]%

Great, our API works as expected. Let’s move on to authentication.

Adding authentication

I will use Auth0 for authentication. It will handle user sign up, login, tokens and all the nitty-gritty things I don’t want to deal with myself. It’s certainly up to you whether to use Auth0 or another solution.

Setting up an Auth0 account is out of the scope for this article. They have great documentation and guides to help you get started. If you’re interested, you can find Altostra on the Auth0 marketplace along with instructions on how to configure the integration.

On my Auth0 account, I have created an application and API that I will use to login the users and produce access tokens.

Our demo authorizer function will check the validity of the access token and allow or deny access based on the validation. In your projects, you can implement more complex validation and authorization.

Add the Authorizer Lambda function

Back to VS Code.

  1. Open the Altostra Designer view (from the commands menu using F1)
  2. From the resources panel, add a new Lambda function and name it API Authorizer.
  3. Connect the API (using the handle on the top) to the new Lambda function. This creates an Authorizer connection type.

You can click the connection to see its properties where you can configure caching—I’ll keep the defaults for now.

add authorizer

The Auth0 resource you see in the image above specifies the Auth0 application we want to use. Altostra will inject the Auth0 application’s data into the Lambda function as environment variables that you can use in your code.

Add the API Authorizer code

API Gateway invokes the authorizer function on every request to the API, it’s job is to determine whether the request should be allowed access to the requested resource. The function must return an AWS policy document that tells API Gateway if the request should be allowed or denied.

Our demo code is based on Auth0’s example, which you can find here. It simply validates that the request contains a valid Bearer access token and grants access, or denies it if it is missing or invalid.

auth prams
const jwt = require('jsonwebtoken')

const {
  AUDIENCE: audience,
  AUTH0_DOMAIN_AUTH01: auth0Domain,
  AUTH0_SIGNING_CERT_AUTH01: signingCert
} = process.env

module.exports.handler = async (event, context) => {
  try {
    return authenticate(event)
  } catch (err) {
    console.error(`Authentication crashed.`, err)
    return context.fail(`Unauthorized`)
  }
}

function getPolicyDocument(resource) {
  return {
    Version: '2012-10-17',
    Statement: [{
      Effect: 'Allow',
      Action: 'execute-api:Invoke',
      Resource: resource,
    }]
  }
}

function getToken(params) {
  if (!params.type || params.type !== 'REQUEST') {
    throw new Error('Expected "event.type" parameter to have value "REQUEST"')
  }

  const tokenString = params.headers && params.headers.Authorization
  if (!tokenString) {
    throw new Error('Expected "event.headers.Authorization" parameter to be set')
  }

  const match = tokenString.match(/^Bearer (.*)$/)
  if (!match || match.length < 2) {
    throw new Error(`Invalid Authorization token - ${tokenString} does not match "Bearer .*"`)
  }

  return match[1]
}

function authenticate(params) {
  const token = getToken(params)

  const decoded = jwt.decode(token, { complete: true })
  if (!decoded || !decoded.header || !decoded.header.kid) {
    throw new Error('Invalid JWT token.')
  }

  const verified = jwt.verify(token, signingCert, {
    audience,
    issuer: `https://${auth0Domain}/`
  })

  return {
    principalId: verified.sub,
    policyDocument: getPolicyDocument(params.methodArn)
  }
}

Update & test

Let’s deploy an updated version of our stack:

$ alto deploy blog --push

Once the deployment finishes, we can test the authentication.

First, let’s see we’re denied access without a valid access token:

$ curl \
--request GET
--url https://k9ifbfjszd.execute-api.us-east-1.amazonaws.com/Prod/resource
{"message":"Missing Authentication Token"}%

Good! Now, let’s get the token:

$ curl --request POST \
  --url https://***.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{"client_id":"******","client_secret":"******","audience":"https://lab.altostra.com/blog/api","grant_type":"client_credentials"}'

And use it in our request to add a user:

curl \
--request POST \
--url https://k9ifbfjszd.execute-api.us-east-1.amazonaws.com/Prod/resource \
--data '{"name": "Gal", "email": "gal@altostra.com"}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer jwt_token'

And get the data back:

$ curl \
--request GET
--url https://k9ifbfjszd.execute-api.us-east-1.amazonaws.com/Prod/resource
[{"id":"452ad845ac3a1bf80409538c9fefa408","email":"yev@altostra.com","name":"Yev"},{"id":"64aaf72c0bc5e25740e7a12450f35f6a","email":"gal@altostra.com","name":"Gal"}]%

Next steps

Want to give it a try? We provide a free-forever plan for developers. Create your free account today at https://app.altostra.com.

We want to hear from you! Share your Altostra experience with us on Twitter @AltostraHQ and LinkedIn and any suggestions that can make your work with Altostra even better.

Happy coding!

By submitting this form, you are accepting our Terms of Service and our Privacy Policy

Thanks for subscribing!

Ready to Get Started?

Get Started for Free

Copyright © 2021 Altostra. All rights reserved.