Securing Your APIs with Python Jose

Learn how to secure your serverless APIs with AWS Lambda Authorisers and Python Jose. We'll walk you
through the mechanics of OAuth and show you how to build a proof-of-concept with Terraform and Python.

When it comes to building serverless APIs, security is always a top concern. One way to ensure that only authorised users can access your APIs is by using an AWS Lambda Authoriser. An authoriser is a Lambda function that you can use to authenticate and authorise incoming requests to your API Gateway.

we will show you how to use AWS Lambda Authorisers in combination with Python Jose, a library for handling JSON Web Tokens (JWTs), to secure your serverless APIs. We'll start by discussing the basics of OAuth, the standard for authorisation on the web, and then move on to building a proof-of-concept with Terraform and Python.

First Steps!

First, let's take a look at the mechanics of OAuth. OAuth is an open standard for authorization that enables third-party applications to obtain limited access to an HTTP service. It works by allowing users to authenticate and authorize a third-party application to access their resources, without having to share their credentials.

There are three main actors in the OAuth flow: the resource owner (the user), the client (the third-party application), and the resource server (the API). The client sends a request to the resource server for access to the user's resources. The resource server then redirects the user to an authorization server, where the user grants permission for the client to access their resources. Once permission is granted, the resource server sends an access token to the client, which can then be used to access the user's resources.

Now that we understand the mechanics of OAuth, let's take a look at how we can use AWS Lambda Authorisers and Python Jose to secure our serverless APIs.

AWS Lambda Authorizers are Lambda functions that you can use to authenticate and authorize incoming requests to your API Gateway. They can be configured to use various authentication mechanisms, such as JWTs, OAuth tokens, or custom authentication. In this example, we'll use Python Jose to verify JWTs.

Going Deeper

First, you'll need to create a new Lambda function and add it as an authorizer to your API Gateway. Here's an example of a simple Lambda authorizer written in Python:

import json
import jose
def lambda_handler(event, context):
    # Get the JWT from the request
    jwt = event['authorizationToken']
    # Verify the JWT
    try:
        jwt_obj = jose.jwt.decode(jwt, 'secret_key', algorithms=['HS256'])
    except jose.exceptions.JWTError:
        raise Exception('Invalid JWT')
    # Return the user's email address
    return {
        'principalId': jwt_obj['email'],
        'policyDocument': {
            'Version': '2012-10-17',
            'Statement': [
                {
                    'Effect': 'Allow',
                    'Action': 'execute-api:Invoke',
                    'Resource': '*'
                }
            ]
        }
    }



This is a simple example of a Lambda function that can be used as an authorizer for your API Gateway. The function takes an incoming request and extracts the JWT from the authorizationToken field. It then uses the jose library to verify the JWT. If the JWT is valid, the function returns the user's email address as the principal ID. If the JWT is invalid, the function raises an exception.

You'll need to replace 'secret_key' with the secret key that you've used to sign the JWT. The 'email' field should be the field that holds the user's email address in your JWT.

Once you have created your Lambda function, you can add it as an authorizer to your API Gateway. To do this, you'll need to go to the API Gateway console and select your API. Then, select the Authorizers option from the sidebar, and click the Create button. You'll then be prompted to select the Lambda function that you want to use as an authorizer.

With your Lambda Authorizer set up, any incoming requests to your API Gateway will be passed through the Lambda function for authentication and authorization.

Refining the idea

To take this a step further, you can use Terraform to build out a proof-of-concept environment that includes an API Gateway, a Lambda function, and an IAM role for the Lambda function. Here is an example of a Terraform configuration that creates an API Gateway, a Lambda function, and an IAM role for the Lambda function:

resource "aws_api_gateway_rest_api" "example" {
  name = "example"
}
resource "aws_api_gateway_resource" "example" {
  rest_api_id = aws_api_gateway_rest_api.example.id
  parent_id = aws_api_gateway_rest_api.example.root_resource_id
  path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "example" {
  rest_api_id = aws_api_gateway_rest_api.example.id
  resource_id = aws_api_gateway_resource.example.id
  http_method = "ANY"
  authorization = "CUSTOM"
  authorizer_id = aws_api_gateway_authorizer.example.id
}
resource "aws_api_gateway_authorizer" "example" {
  name = "example"
  rest_api_id = aws_api_gateway_rest_api.example.id
  type = "TOKEN"
  identity_source = "method.request.header.Authorization"
  authorizer_uri = aws_lambda_function.example.invoke_arn
}
resource "aws_lambda_function" "example" {
  filename = "lambda_function.zip"
  function_name = "example"
  role = aws_iam_role.example.arn
  handler = "lambda_function.lambda_handler"
  runtime = "python3.8"
}
resource "aws_iam_role" "example" {
  name = "example"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}
resource "aws_iam_role_policy" "example" {
  name = "example"
  role = aws_iam_role.example.id
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "logs:*",
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}
EOF
}

This Terraform configuration creates an API Gateway, a Lambda function, and an IAM role for the Lambda function. The API Gateway is configured to use the Lambda function as an authorizer, and the Lambda function is configured to use the IAM role.


Wrap Up!

AWS Lambda Authorizers provide a powerful way to authenticate and authorize incoming requests to your serverless APIs. By using a Lambda function to authenticate and authorize requests, you can add an extra layer of security to your APIs without having to manage servers or infrastructure.

When building a Lambda Authorizer, it is a good idea to use a JWT library such as Python Jose, to handle the JWT verification. The library provides a simple and secure way to handle JWTs and it's available in multiple languages.