API Gateway REST API Public Endpoint Authorization
Overview
This check verifies that your public Amazon API Gateway REST APIs have an authorizer configured. Public APIs are directly accessible from the internet, making proper authorization essential. The check ensures that methods on public endpoints require authentication through either a Lambda authorizer or Amazon Cognito user pool, rather than allowing anonymous access.
Risk
Without an authorizer on a public API, anonymous callers can:
- Read or alter data: Unauthenticated users may access or modify sensitive information
- Trigger backend actions: Attackers can invoke your backend systems without permission
- Abuse traffic: Malicious actors can spike usage, degrading availability and inflating costs
- Enable reconnaissance: Open endpoints allow attackers to enumerate your API and discover further attack vectors
Remediation Steps
Prerequisites
- AWS account access with permissions to modify API Gateway configurations
- A decision on authentication method: Cognito user pool or Lambda authorizer
Which authorizer type should I choose?
| Authorizer Type | Best For |
|---|---|
| Cognito User Pools | User-facing apps with sign-up/sign-in flows; simple JWT token validation |
| Lambda (TOKEN) | Custom authentication logic; validating third-party tokens (Auth0, Okta, etc.) |
| Lambda (REQUEST) | Authentication based on multiple request parameters (headers, query strings, etc.) |
AWS Console Method
- Sign in to the AWS Management Console
- Navigate to API Gateway
- Select your public REST API from the list
- In the left navigation, click Authorizers
- Click Create authorizer
For Cognito User Pool Authorizer:
- Name: Enter a descriptive name (e.g.,
my-cognito-authorizer) - Type: Select Cognito
- Cognito user pool: Select your existing user pool
- Token source: Enter
Authorization - Click Create authorizer
For Lambda Authorizer:
- Name: Enter a descriptive name (e.g.,
my-lambda-authorizer) - Type: Select Lambda
- Lambda function: Select your authorizer function
- Lambda invoke role: Leave blank (API Gateway will use resource-based policy)
- Lambda event payload: Choose Token or Request based on your needs
- Token source: Enter
Authorization - Click Create authorizer
- After creating the authorizer, go to Resources in the left navigation
- For each method (GET, POST, etc.) on your public endpoints:
- Click on the method name
- Click Method request
- Under Authorization, select your newly created authorizer
- Click the checkmark to save
- Click Deploy API and select your deployment stage
AWS CLI (optional)
List your REST APIs to find the public one:
aws apigateway get-rest-apis --region us-east-1
Check existing authorizers:
aws apigateway get-authorizers \
--rest-api-id <your-api-id> \
--region us-east-1
Create a Cognito User Pool authorizer:
aws apigateway create-authorizer \
--rest-api-id <your-api-id> \
--name my-cognito-authorizer \
--type COGNITO_USER_POOLS \
--provider-arns arn:aws:cognito-idp:us-east-1:<account-id>:userpool/<pool-id> \
--identity-source 'method.request.header.Authorization' \
--region us-east-1
Create a Lambda TOKEN authorizer:
aws apigateway create-authorizer \
--rest-api-id <your-api-id> \
--name my-lambda-authorizer \
--type TOKEN \
--authorizer-uri 'arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:<account-id>:function:<function-name>/invocations' \
--identity-source 'method.request.header.Authorization' \
--authorizer-result-ttl-in-seconds 300 \
--region us-east-1
Create a Lambda REQUEST authorizer (for multiple identity sources):
aws apigateway create-authorizer \
--rest-api-id <your-api-id> \
--name my-request-authorizer \
--type REQUEST \
--authorizer-uri 'arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:<account-id>:function:<function-name>/invocations' \
--identity-source 'method.request.header.Authorization,context.accountId' \
--authorizer-result-ttl-in-seconds 300 \
--region us-east-1
Apply the authorizer to a method:
For a Lambda authorizer (CUSTOM type):
aws apigateway update-method \
--rest-api-id <your-api-id> \
--resource-id <resource-id> \
--http-method GET \
--patch-operations op=replace,path=/authorizationType,value=CUSTOM op=replace,path=/authorizerId,value=<authorizer-id> \
--region us-east-1
For a Cognito authorizer:
aws apigateway update-method \
--rest-api-id <your-api-id> \
--resource-id <resource-id> \
--http-method GET \
--patch-operations op=replace,path=/authorizationType,value=COGNITO_USER_POOLS op=replace,path=/authorizerId,value=<authorizer-id> \
--region us-east-1
Deploy the API to apply changes:
aws apigateway create-deployment \
--rest-api-id <your-api-id> \
--stage-name prod \
--region us-east-1
CloudFormation (optional)
AWSTemplateFormatVersion: '2010-09-09'
Description: Public API Gateway REST API with Lambda Authorizer
Parameters:
AuthorizerLambdaArn:
Type: String
Description: ARN of the Lambda function to use as authorizer
Resources:
MyRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: my-public-api
Description: Public REST API with authorization enabled
EndpointConfiguration:
Types:
- REGIONAL
LambdaAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: lambda-token-authorizer
Type: TOKEN
RestApiId: !Ref MyRestApi
AuthorizerUri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizerLambdaArn}/invocations'
IdentitySource: method.request.header.Authorization
AuthorizerResultTtlInSeconds: 300
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref AuthorizerLambdaArn
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyRestApi}/authorizers/${LambdaAuthorizer}'
MyResource:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref MyRestApi
ParentId: !GetAtt MyRestApi.RootResourceId
PathPart: secure-endpoint
MyMethod:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref MyRestApi
ResourceId: !Ref MyResource
HttpMethod: GET
AuthorizationType: CUSTOM
AuthorizerId: !Ref LambdaAuthorizer
Integration:
Type: MOCK
RequestTemplates:
application/json: '{"statusCode": 200}'
IntegrationResponses:
- StatusCode: 200
MethodResponses:
- StatusCode: 200
ApiDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn: MyMethod
Properties:
RestApiId: !Ref MyRestApi
ApiStage:
Type: AWS::ApiGateway::Stage
Properties:
RestApiId: !Ref MyRestApi
DeploymentId: !Ref ApiDeployment
StageName: prod
Outputs:
ApiEndpoint:
Description: Public API Gateway endpoint URL
Value: !Sub 'https://${MyRestApi}.execute-api.${AWS::Region}.amazonaws.com/prod'
Deploy the template:
aws cloudformation deploy \
--template-file api-gateway-public-authorizer.yaml \
--stack-name api-gateway-public-authorizer-stack \
--parameter-overrides AuthorizerLambdaArn=arn:aws:lambda:us-east-1:<account-id>:function:<function-name> \
--region us-east-1
Terraform (optional)
resource "aws_api_gateway_rest_api" "public_api" {
name = "my-public-api"
description = "Public REST API with authorization enabled"
endpoint_configuration {
types = ["REGIONAL"]
}
}
resource "aws_api_gateway_authorizer" "lambda" {
name = "lambda-token-authorizer"
rest_api_id = aws_api_gateway_rest_api.public_api.id
type = "TOKEN"
authorizer_uri = var.authorizer_lambda_invoke_arn
identity_source = "method.request.header.Authorization"
authorizer_result_ttl_in_seconds = 300
}
resource "aws_api_gateway_resource" "secure_endpoint" {
rest_api_id = aws_api_gateway_rest_api.public_api.id
parent_id = aws_api_gateway_rest_api.public_api.root_resource_id
path_part = "secure-endpoint"
}
resource "aws_api_gateway_method" "secure_method" {
rest_api_id = aws_api_gateway_rest_api.public_api.id
resource_id = aws_api_gateway_resource.secure_endpoint.id
http_method = "GET"
authorization = "CUSTOM"
authorizer_id = aws_api_gateway_authorizer.lambda.id
}
resource "aws_api_gateway_deployment" "deployment" {
rest_api_id = aws_api_gateway_rest_api.public_api.id
depends_on = [aws_api_gateway_method.secure_method]
lifecycle {
create_before_destroy = true
}
}
resource "aws_api_gateway_stage" "prod" {
rest_api_id = aws_api_gateway_rest_api.public_api.id
deployment_id = aws_api_gateway_deployment.deployment.id
stage_name = "prod"
}
resource "aws_lambda_permission" "api_gateway" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = var.authorizer_lambda_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.public_api.execution_arn}/authorizers/${aws_api_gateway_authorizer.lambda.id}"
}
variable "authorizer_lambda_invoke_arn" {
description = "Invoke ARN of the Lambda authorizer function"
type = string
}
variable "authorizer_lambda_name" {
description = "Name of the Lambda authorizer function"
type = string
}
output "api_endpoint" {
value = aws_api_gateway_stage.prod.invoke_url
}
For a Cognito authorizer instead:
resource "aws_api_gateway_authorizer" "cognito" {
name = "cognito-authorizer"
rest_api_id = aws_api_gateway_rest_api.public_api.id
type = "COGNITO_USER_POOLS"
provider_arns = [var.cognito_user_pool_arn]
identity_source = "method.request.header.Authorization"
}
resource "aws_api_gateway_method" "secure_method" {
rest_api_id = aws_api_gateway_rest_api.public_api.id
resource_id = aws_api_gateway_resource.secure_endpoint.id
http_method = "GET"
authorization = "COGNITO_USER_POOLS"
authorizer_id = aws_api_gateway_authorizer.cognito.id
}
Verification
After configuring authorization, verify it is working:
- Go to API Gateway in the AWS Console
- Select your REST API
- Click Authorizers - your authorizer should be listed
- Click Resources - each method should show your authorizer name (not
Auth: NONE) - Test by calling an endpoint without a valid token - you should receive a 401 Unauthorized response
CLI verification commands
List authorizers for your API:
aws apigateway get-authorizers \
--rest-api-id <your-api-id> \
--region us-east-1
Expected output:
{
"items": [
{
"id": "abc123",
"name": "my-lambda-authorizer",
"type": "TOKEN",
"authorizerUri": "arn:aws:apigateway:us-east-1:lambda:path/...",
"identitySource": "method.request.header.Authorization",
"authorizerResultTtlInSeconds": 300
}
]
}
Check a specific method's authorization:
aws apigateway get-method \
--rest-api-id <your-api-id> \
--resource-id <resource-id> \
--http-method GET \
--region us-east-1
Verify authorizationType is not NONE:
{
"httpMethod": "GET",
"authorizationType": "CUSTOM",
"authorizerId": "abc123"
}
Test the endpoint without authorization:
curl -X GET https://<your-api-id>.execute-api.us-east-1.amazonaws.com/prod/secure-endpoint
Expected response (401 Unauthorized):
{
"message": "Unauthorized"
}
Additional Resources
- Use API Gateway Lambda Authorizers
- Use Amazon Cognito User Pools as Authorizer
- Control Access to REST APIs
- API Gateway Resource Policies
Notes
- Public vs Private APIs: This check specifically targets public APIs accessible from the internet. Private APIs (VPC endpoints only) have different security considerations.
- Defense in depth: Consider layering additional protections:
- AWS WAF for rate limiting and malicious request filtering
- Resource policies to restrict by IP or VPC
- Usage plans with API keys for throttling
- CloudWatch logging for monitoring and alerting
- Token caching: Lambda authorizers cache results by default (300 seconds). Reduce this value if tokens can be revoked mid-session.
- Deployment required: After adding or changing authorizers, you must deploy the API for changes to take effect.
- Testing: Always test both authorized and unauthorized requests after configuration to ensure legitimate traffic still works.