Skip to main content

Gate on AWS as Lambda Authorizer with API Gateway

In this quickstart guide, we will show you how to deploy Gate on AWS as a Lambda Authorizer with API Gateway. If you aren't already familiar with Gate, we recommend reading our [getting started page](/docs/gate/getting-started.

In this guide, we will give you ready-to-use Terraform code and a script which uses AWS CLI.

If you are looking for detailed step-by-step instructions, please read the full Lambda Authorizer Guide.

UserAWSAPI Gatewayyour-lambdaGateLambda authorizerHTTP requestRequestheadersAuthorizerresponseHTTP request(if authorized)

Limitations

Due to the way Lambda Authorizer is integrated with API Gateway, it doesn't have access to the request body, nor can it edit the response. When running as a Lambda Authorizer, Gate cannot access the request body nor modify the response, due to the way Lambda Authorizer is integrated with API Gateway. As such, plugins that change request body or response are not supported. Currently this includes the Authorization Proxy and PII detection plugins.

To alter request or response headers, you need to configure them manually.

Currently, Gate supports AWS Gateway v2 only. Support for v1 will be added in the future.

Deploying

In this guide, we will use Gate as a Lambda Authorizer to protect a Lambda function by validating a JSON Web Token (JWT). This is a basic example to make the tutorial practical. A more extensive list of usecases supported by Gate can be found in the Use Cases page.

AWS API Gateway can be configured to use Gate as an Authorizer using the AWS Console or with Terraform.

A misconfiguration may prevent Gate from functioning correctly, and so we recommend following each step in this guide, which includes information on how to verify each step. If you encounter issues, please check the Troubleshooting page.

Throughout this guide, remember to replace

  • <YOUR AWS REGION> with your AWS region (e.g., us-east-1)
  • <YOUR ACCOUNT ID> with your AWS account ID (12 digits)
  • <YOUR ORGANIZATION ID> with your SlashID organization ID (36 characters)
tip

This guides uses JWTs issued by SlashID. If you don't have a SlashID organization yet, you can create one for free in 30 seconds by visiting SlashID Console.

You can learn how to authorize Terraform in AWS in the Terraform documentation.

After that, you can run:

terraform init
terraform apply

With the following Terraform code:

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

provider "aws" {
region = "<YOUR AWS REGION>"
}

variable "organization_id" {
default = "<YOUR ORGANIZATION ID>"
}

data "aws_region" "current" {}

####

resource "aws_iam_role" "lambda_exec" {
name = "serverless_lambda"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}

data "aws_iam_policy_document" "lambda_logging" {
statement {
effect = "Allow"

actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]

resources = ["arn:aws:logs:*:*:*"]
}
}

resource "aws_iam_policy" "lambda_logging" {
name = "lambda_logging"
path = "/"
description = "IAM policy for logging from a lambda"
policy = data.aws_iam_policy_document.lambda_logging.json
}

resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.lambda_exec.name
policy_arn = aws_iam_policy.lambda_logging.arn
}

####

data "http" "echo_lambda" {
url = "https://storage.googleapis.com/slashid-sandbox-ad6b-cdn/static/echo-lambda.zip"
}

resource "local_sensitive_file" "echo_lambda" {
content_base64 = data.http.echo_lambda.response_body_base64
filename = "${path.module}/echo-function.zip"
}

####

resource "aws_lambda_function" "echo" {
function_name = "echo"
handler = "echo.handler"
role = aws_iam_role.lambda_exec.arn


runtime = "provided.al2"
architectures = ["arm64"]

filename = "echo-function.zip"
source_code_hash = sha256(data.http.echo_lambda.response_body_base64)

depends_on = [
aws_iam_role_policy_attachment.lambda_logs,
local_sensitive_file.echo_lambda,
]
}


output "echo_lambda_logs" {
description = "Echo lambda logs"
value = format(
"https://%s.console.aws.amazon.com/lambda/home?region=%s#/functions/%s?tab=monitoring",
data.aws_region.current.name,
data.aws_region.current.name,
aws_lambda_function.echo.function_name,
)
}

####

resource "aws_apigatewayv2_api" "lambda_gateway" {
name = "lambda_gateway"
protocol_type = "HTTP"
}

resource "aws_apigatewayv2_integration" "echo_lambda" {
api_id = aws_apigatewayv2_api.lambda_gateway.id

integration_uri = aws_lambda_function.echo.invoke_arn
integration_type = "AWS_PROXY"
integration_method = "POST"
}

resource "aws_lambda_permission" "lambda_permission" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.echo.function_name
principal = "apigateway.amazonaws.com"

source_arn = "${aws_apigatewayv2_api.lambda_gateway.execution_arn}/*/*"
}

resource "aws_apigatewayv2_route" "echo" {
api_id = aws_apigatewayv2_api.lambda_gateway.id

route_key = "GET /echo"
target = "integrations/${aws_apigatewayv2_integration.echo_lambda.id}"
authorization_type = "CUSTOM"
authorizer_id = "${aws_apigatewayv2_authorizer.gate.id}"

depends_on = [
aws_apigatewayv2_integration.echo_lambda,
aws_apigatewayv2_authorizer.gate,
]
}

####

resource "aws_apigatewayv2_stage" "lambda_test" {
api_id = aws_apigatewayv2_api.lambda_gateway.id

name = "test"
auto_deploy = true

default_route_settings {
data_trace_enabled = true
logging_level = "INFO"
detailed_metrics_enabled = true

throttling_rate_limit = 100
throttling_burst_limit = 100
}
access_log_settings {
destination_arn = aws_cloudwatch_log_group.lambda_gateway.arn
format = "$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] \"$context.httpMethod $context.resourcePath $context.protocol\" $context.status $context.responseLength $context.requestId"
}
}

resource "aws_cloudwatch_log_group" "lambda_gateway" {
name = "/aws/apigateway/lambda_gateway"

retention_in_days = 30
}


output "echo_url" {
description = "Base URL for API Gateway stage."
value = format("%s/%s", aws_apigatewayv2_stage.lambda_test.invoke_url, "echo")
}


####

resource "null_resource" "download_gate" {
provisioner "local-exec" {
command = "mkdir -p gate && cd gate && wget -O- https://cdn.slashid.com/releases/latest/gate-free_latest_linux_arm64.tar.gz | tar xz && mv gate-free bootstrap"
}
}

data "archive_file" "gate_zip" {
depends_on = [
null_resource.download_gate
]

source_dir = "./gate/"
output_path = "${path.module}/gate.zip"
type = "zip"
}

resource "aws_lambda_function" "gate" {
function_name = "gate"
role = aws_iam_role.lambda_exec.arn
handler = "gate.handler"

runtime = "provided.al2"
architectures = ["arm64"]

filename = "gate.zip"
source_code_hash = data.archive_file.gate_zip.output_base64sha256

environment {
variables = {
GATE_MODE = "aws_lambda_auth"
GATE_LOG_LEVEL = "debug"

GATE_PLUGINS_0_TYPE = "validate-jwt"
GATE_PLUGINS_0_ENABLED = "true"

GATE_PLUGINS_0_PARAMETERS_JWKS_URL = "https://api.slashid.com/.well-known/jwks.json"
GATE_PLUGINS_0_PARAMETERS_JWT_ALLOWED_ALGORITHMS = "RS256"
GATE_PLUGINS_0_PARAMETERS_JWT_EXPECTED_ISSUER = "https://api.slashid.com"
GATE_PLUGINS_0_PARAMETERS_JWT_EXPECTED_AUDIENCE = var.organization_id
}
}

depends_on = [
aws_iam_role_policy_attachment.lambda_logs,
]
}

output "gate_lambda_logs" {
description = "Gate lambda logs"
value = format(
"https://%s.console.aws.amazon.com/lambda/home?region=%s#/functions/%s?tab=monitoring",
data.aws_region.current.name,
data.aws_region.current.name,
aws_lambda_function.gate.function_name,
)
}

####

resource "aws_apigatewayv2_authorizer" "gate" {
api_id = aws_apigatewayv2_api.lambda_gateway.id
authorizer_type = "REQUEST"
authorizer_uri = aws_lambda_function.gate.invoke_arn
identity_sources = ["$request.header.Authorization"]
name = "gate"
authorizer_payload_format_version = "2.0"
enable_simple_responses = true

authorizer_result_ttl_in_seconds = 0
}

resource "aws_lambda_permission" "authorizer_permission" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.gate.function_name
principal = "apigateway.amazonaws.com"

source_arn = "${aws_apigatewayv2_api.lambda_gateway.execution_arn}/authorizers/${aws_apigatewayv2_authorizer.gate.id}"
}

Verifying deployment

Let's ensure that requests without a token are rejected:

> curl https://[API ID].execute-api.us-east-1.amazonaws.com/test/echo --header 'Cache-Control:"max-age=0"'

{"message":"Unauthorized"}

Now we can verify that Gate allows requests with a valid token. First, we need to create a valid token.

We will use the SlashID mint token endpoint.

tip

You may have no persons in your organization yet. You can create a new person by using the create person endpoint or in the SlashID Console.

> curl --location --request POST 'https://api.slashid.com/persons/<PERSON ID>/mint-token' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'SlashID-API-Key: <YOUR API KEY>' \
--header 'SlashID-OrgID: <YOUR ORG ID>'

{
"result": "<TOKEN>"
}

Now, let's include the generated token in a request to the echo Lambda:

> curl https://[API ID].execute-api.us-east-1.amazonaws.com/test/echo \
--header 'Authorization:Bearer <TOKEN>' \
--header 'Cache-Control:max-age=0'

{
"event": {
"version": "1.0",
"routeKey": "",
"rawPath": "",
"rawQueryString": "",
"headers": {
"Content-Length": "0",
"Host": "<API ID>.execute-api.us-east-1.amazonaws.com",
"User-Agent": "curl/7.84.0",
"X-Amzn-Trace-Id": "Root=1-652015d6-10b9e01b65c0ef3a443d7fee",
"X-Forwarded-For": "[REQUEST IP]",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https",
"accept": "*/*",
"authorization": "Bearer <TOKEN>",
"cache-control": "max-age=0"
},
"requestContext": {
"routeKey": "",
"accountId": "<ACCOUNT ID>",
"stage": "test",
"requestId": "MYhZjgHNIAMES8g=",
"authorizer": {},
"apiId": "<API ID>",
"domainName": "<API ID>.execute-api.us-east-1.amazonaws.com",
"domainPrefix": "<API ID>",
"time": "",
"timeEpoch": 0,
"http": {
"method": "",
"path": "",
"protocol": "",
"sourceIp": "",
"userAgent": ""
},
"authentication": {
"clientCert": {
"clientCertPem": "",
"issuerDN": "",
"serialNumber": "",
"subjectDN": "",
"validity": {
"notAfter": "",
"notBefore": ""
}
}
}
},
"isBase64Encoded": false
},
"message": "Hello World!"
}

You should check the Gate logs (gate_lambda_logs Terraform output) if it didn't work. Remember that it takes AWS a couple of seconds to expose logs in their UI.

If you can't find a reason for failure, you can change the log level by changing the GATE_LOG_LEVEL env variable to trace.

Cleaning up

Instructions to remove created resources.

Run terraform destroy.

Troubleshooting

If you are stuck, please check the Troubleshooting page.

Further reading

In this tutorial, we covered just the basic configuration. For a more detailed walkthrough on deploying Gate as a Lambda Authorizer, you can read the full Lambda Authorizer Guide. For more advanced configuration options, you can check the Advanced Configuration page.