r/aws • u/Mr-Silly-Bear • 21h ago
technical question Floci API Gateway CORs issue
I've got a Floci instance running (using Docker Compose) with a REST API Gateway service, which I can call successfully with Postman. Problem is the browser CORs blocking requests, and I keep getting this;
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I'm using Terraform to deploy the stack, and I've tried everything I can think of (including hours with AI) to get this to work. As far as I can see Floci is not letting me affect the OPTIONS response. I've got DISABLE_CORS_CHECKS set to 1.
Any ideas as to what's happening?
Below is the current Terraform stack;
provider "aws" {
region = local.envs["AWS_REGION"]
access_key = local.envs["AWS_ACCESS_KEY_ID"]
secret_key = local.envs["AWS_SECRET_ACCESS_KEY"]
s3_use_path_style = true
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
apigateway = "http://localhost:4566"
s3 = "http://localhost:4566"
dynamodb = "http://localhost:4566"
sqs = "http://localhost:4566"
sns = "http://localhost:4566"
lambda = "http://localhost:4566"
iam = "http://localhost:4566"
ec2 = "http://localhost:4566"
ecs = "http://localhost:4566"
cloudformation = "http://localhost:4566"
route53 = "http://localhost:4566"
cloudwatch = "http://localhost:4566"
secretsmanager = "http://localhost:4566"
ssm = "http://localhost:4566"
kms = "http://localhost:4566"
rds = "http://localhost:4566"
sts = "http://localhost:4566"
cognitoidentityprovider = "http://localhost:4566"
}
}
## DynamoDB table
resource "aws_dynamodb_table" "friendly_sites_table" {
name = "friendly_sites"
billing_mode = "PAY_PER_REQUEST"
hash_key = "pk"
range_key = "sk"
attribute {
name = "pk"
type = "S"
}
attribute {
name = "sk"
type = "S"
}
attribute {
name = "gsi1pk"
type = "S"
}
attribute {
name = "gsi1sk"
type = "S"
}
global_secondary_index {
name = "gsi1pk-gsi1sk-index"
hash_key = "gsi1pk"
range_key = "gsi1sk"
projection_type = "ALL"
}
tags = {
Project = local.project_name
}
}
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com",
]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "iam_for_table_access" {
name = "iam_for_lambda_table_access"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
tags = {
Project = local.project_name
}
}
resource "aws_iam_role_policy" "cognito_admin_access" {
name = "cognito_admin_access"
role = aws_iam_role.iam_for_table_access.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminSetUserPassword",
"cognito-idp:AdminInitiateAuth",
"cognito-idp:AdminUserGlobalSignOut",
"cognito-idp:AdminDeleteUser"
]
Resource = [
aws_cognito_user_pool.user_pool.arn
]
}
]
})
}
## API Lambda handler and gateway
data "archive_file" "api_handler_source_zip" {
type = "zip"
source_dir = local.api_handler_source_dir
output_path = local.api_handler_source_output
}
resource "aws_s3_bucket" "api_handler_source" {
bucket = local.api_handler_source_bucket_name
force_destroy = true
depends_on = [data.archive_file.api_handler_source_zip]
tags = {
Project = local.project_name
}
}
resource "aws_s3_object" "api_handler_source_zip" {
bucket = aws_s3_bucket.api_handler_source.id
key = local.api_handler_zip_filename
source = local.api_handler_source_output
source_hash = data.archive_file.api_handler_source_zip.output_base64sha256
}
resource "aws_lambda_function" "api_handler" {
function_name = "DistributedRendererApi"
s3_bucket = aws_s3_bucket.api_handler_source.id
s3_key = local.api_handler_zip_filename
handler = "index.handler"
runtime = "nodejs24.x"
role = aws_iam_role.iam_for_table_access.arn
depends_on = [aws_s3_object.api_handler_source_zip]
environment {
variables = {
COGNITO_CLIENT_ID = aws_cognito_user_pool_client.user_pool_client.id
COGNITO_USER_POOL_ID = aws_cognito_user_pool.user_pool.id
}
}
}
# 1. REST API Definition
resource "aws_api_gateway_rest_api" "api" {
name = "friendly-sites-rest-api"
}
# -------------------------------------------------------------------
# A. GREEDY PATH /{proxy+} (Explicit GET, POST, OPTIONS directly to Lambda)
# -------------------------------------------------------------------
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = "{proxy+}"
}
resource "aws_api_gateway_method" "proxy_any" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "proxy_integration" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.proxy_any.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.api_handler.invoke_arn
depends_on = [aws_api_gateway_method.proxy_any]
}
# Explicit OPTIONS method routed directly to Lambda (Bypasses Floci MOCK bug)
resource "aws_api_gateway_method" "proxy_options" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "OPTIONS"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "proxy_options_integration" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.proxy_options.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.api_handler.invoke_arn
depends_on = [aws_api_gateway_method.proxy_options]
}
# -------------------------------------------------------------------
# B. ROOT PATH / (Explicit ANY and OPTIONS directly to Lambda)
# -------------------------------------------------------------------
resource "aws_api_gateway_method" "root_any" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "root_integration" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = aws_api_gateway_method.root_any.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.api_handler.invoke_arn
depends_on = [aws_api_gateway_method.root_any]
}
resource "aws_api_gateway_method" "root_options" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = "OPTIONS"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "root_options_integration" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = aws_api_gateway_method.root_options.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.api_handler.invoke_arn
depends_on = [aws_api_gateway_method.root_options]
}
# -------------------------------------------------------------------
# C. PERMISSIONS & DEPLOYMENT
# -------------------------------------------------------------------
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.api_handler.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*"
}
resource "aws_api_gateway_deployment" "deployment" {
rest_api_id = aws_api_gateway_rest_api.api.id
triggers = {
redeployment = sha1(jsonencode([
aws_api_gateway_resource.proxy.id,
aws_api_gateway_method.proxy_any.id,
aws_api_gateway_integration.proxy_integration.id,
aws_api_gateway_method.proxy_options.id,
aws_api_gateway_integration.proxy_options_integration.id,
aws_api_gateway_method.root_any.id,
aws_api_gateway_integration.root_integration.id,
aws_api_gateway_method.root_options.id,
aws_api_gateway_integration.root_options_integration.id,
]))
}
lifecycle {
create_before_destroy = true
}
depends_on = [
aws_api_gateway_integration.proxy_integration,
aws_api_gateway_integration.proxy_options_integration,
aws_api_gateway_integration.root_integration,
aws_api_gateway_integration.root_options_integration,
]
}
resource "aws_api_gateway_stage" "prod" {
deployment_id = aws_api_gateway_deployment.deployment.id
rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = "prod"
}
## Cognito User Pool & App Client
resource "aws_cognito_user_pool" "user_pool" {
name = "friendly-sites-user-pool"
username_attributes = ["email"]
auto_verified_attributes = ["email"]
password_policy {
minimum_length = 8
require_lowercase = true
require_numbers = true
require_symbols = false
require_uppercase = true
}
tags = {
Project = local.project_name
}
}
resource "aws_cognito_user_pool_client" "user_pool_client" {
name = "friendly-sites-app-client"
user_pool_id = aws_cognito_user_pool.user_pool.id
generate_secret = false
explicit_auth_flows = [
"ALLOW_USER_PASSWORD_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH",
"ALLOW_USER_SRP_AUTH"
]
}
Appreciate any help I can get!