diff --git a/apigw-secretsmanager-apikey-cdk/README.md b/apigw-secretsmanager-apikey-cdk/README.md
new file mode 100644
index 000000000..421956949
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/README.md
@@ -0,0 +1,99 @@
+# Amazon API Gateway with AWS Lambda Authorizer and AWS Secrets Manager for API Key Authentication
+
+This pattern demonstrates how to implement a secure API key-based authorization system using Amazon API Gateway, AWS Lambda Authorizer, and AWS Secrets Manager. Each user/tenant has their own unique API key stored in Secrets Manager, which is validated by a Lambda authorizer when requests are made to protected API endpoints.
+
+Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/apigw-secretsmanager-apikey-cdk](https://serverlessland.com/patterns/apigw-secretsmanager-apikey-cdk)
+
+Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
+
+## Requirements
+
+* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
+* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
+* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed
+* [Node.js and npm](https://nodejs.org/) installed
+* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) installed
+
+## Deployment Instructions
+
+1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
+ ```
+ git clone https://github.com/aws-samples/serverless-patterns
+ ```
+1. Change directory to the pattern directory:
+ ```
+ cd apigw-secretsmanager-apikey-cdk
+ ```
+1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
+ ```
+ npm install
+ ```
+1. Deploy the stack:
+ ```
+ cdk deploy
+ ```
+
+Note the outputs from the CDK deployment process. The output will include the API Gateway URL you'll need for testing.
+
+## How it works
+
+
+
+1. Client makes a request to the API with an API key in the `x-api-key` header
+2. API Gateway forwards the authorization request to the Lambda Authorizer
+ - The Lambda Authorizer checks if the API key exists in Secrets Manager
+ - If the key is valid, the associated tenant information is retrieved and included in the authorization context
+3. The API Gateway then allows or denies access to the protected endpoint based on the policy returned by the authorizer
+
+Each API key in Secrets Manager should follow the naming convention `api-key-{keyvalue}` and contain a JSON document with at least a tenantId field.
+
+## Testing
+
+1. Create a new api key, you will need the api key later on
+ ```
+ ❯ ./create_api_key.sh sample-tenant
+ API key for tenant sample-tenant created: b4037c9368990982ac5d1c670053bf76
+ ```
+1. Get the API Gateway URL from the deployment output:
+ ```bash
+ # The output will be similar to
+ ApigwSecretsmanagerApikeyCdkStack.ApiUrl = https://383rm6ue91.execute-api.us-east-1.amazonaws.com/prod/
+ ```
+1. Make a request to the protected endpoint with a valid API key:
+ ```
+ curl -H "x-api-key: CREATED_API_KEY" https://REPLACE_WITH_URL_FROM_CDK_OUTPUT/protected
+ ```
+ If successful, you should receive a response like:
+ ```
+ { "message": "Access granted" }
+ ```
+1. Try with an invalid API key:
+ ```
+ curl -H "x-api-key: invalid-key" https://REPLACE_WITH_URL_FROM_CDK_OUTPUT/protected
+ ```
+ You should receive an unauthorized error.
+1. Try without an API key:
+ ```
+ curl https://REPLACE_WITH_URL_FROM_CDK_OUTPUT/protected
+ ```
+ You should also receive an unauthorized error.
+
+
+## Cleanup
+
+1. Delete the stack
+ ```bash
+ cdk destroy
+ ```
+1. Delete created SecretManager keys using
+ ```bash
+ ./remove_secrets.sh
+
+ # to check which keys will be removed, use the dry-run option
+ ./remove_secrets.sh --dry-run
+ ```
+
+----
+Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+SPDX-License-Identifier: MIT-0
diff --git a/apigw-secretsmanager-apikey-cdk/apigw-secretsmanager-apikey-cdk.png b/apigw-secretsmanager-apikey-cdk/apigw-secretsmanager-apikey-cdk.png
new file mode 100644
index 000000000..6d167277c
Binary files /dev/null and b/apigw-secretsmanager-apikey-cdk/apigw-secretsmanager-apikey-cdk.png differ
diff --git a/apigw-secretsmanager-apikey-cdk/apigws-secretsmanager-apikey-cdk.json b/apigw-secretsmanager-apikey-cdk/apigws-secretsmanager-apikey-cdk.json
new file mode 100644
index 000000000..d81e104ef
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/apigws-secretsmanager-apikey-cdk.json
@@ -0,0 +1,97 @@
+{
+ "title": "API Gateway, Lambda Authorizer & Secrets Manager for API Key Authentication",
+ "description": "Implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
+ "language": "TypeScript",
+ "level": "200",
+ "framework": "CDK",
+ "introBox": {
+ "headline": "How it works",
+ "text": [
+ "This pattern demonstrates how to implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
+ "Each user/tenant has their own unique API key stored in Secrets Manager, which is validated by a Lambda authorizer when requests are made to protected API endpoints.",
+ "The Lambda authorizer checks if the API key exists in Secrets Manager. If the key is valid, the associated tenant information is retrieved and included in the authorization context.",
+ "The API Gateway then allows or denies access to the protected endpoint based on the policy returned by the authorizer."
+ ]
+ },
+ "gitHub": {
+ "template": {
+ "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-secretsmanager-apikey-cdk",
+ "templateURL": "serverless-patterns/apigw-secretsmanager-apikey-cdk",
+ "projectFolder": "apigw-secretsmanager-apikey-cdk",
+ "templateFile": "lib/apigw-secretsmanager-apikey-stack.ts"
+ }
+ },
+ "resources": {
+ "bullets": [
+ {
+ "text": "Lambda Authorizers for Amazon API Gateway",
+ "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"
+ },
+ {
+ "text": "AWS Secrets Manager User Guide",
+ "link": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html"
+ },
+ {
+ "text": "Amazon API Gateway - REST APIs",
+ "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html"
+ }
+ ]
+ },
+ "deploy": {
+ "text": [
+ "npm install",
+ "cdk deploy"
+ ]
+ },
+ "testing": {
+ "text": [
+ "Create an API key using the provided script: ./create_api_key.sh sample-tenant",
+ "Make a request to the protected endpoint using the valid API key: curl -H \"x-api-key: CREATED_API_KEY\" https://REPLACE_WITH_CREATED_API_URL.amazonaws.com/prod/protected",
+ "If successful, you should receive a response: { \"message\": \"Access granted\" }"
+ ]
+ },
+ "cleanup": {
+ "text": [
+ "Delete the CDK stack: cdk destroy",
+ "Delete created SecretManager keys using the provided script: ./remove_secrets.sh"
+ ]
+ },
+ "authors": [
+ {
+ "name": "Marco Jahn",
+ "image": "https://sessionize.com/image/e99b-400o400o2-pqR4BacUSzHrq4fgZ4wwEQ.png",
+ "bio": "Senior Solutions Architect - ISV, Amazon Web Services",
+ "linkedin": "marcojahn"
+ }
+ ],
+ "patternArch": {
+ "icon1": {
+ "x": 20,
+ "y": 50,
+ "service": "apigw",
+ "label": "API Gateway REST API"
+ },
+ "icon2": {
+ "x": 50,
+ "y": 50,
+ "service": "lambda",
+ "label": "AWS Lambda Authorizer"
+ },
+ "icon3": {
+ "x": 80,
+ "y": 50,
+ "service": "secretsmanager",
+ "label": "AWS Secrets Manager"
+ },
+ "line1": {
+ "from": "icon1",
+ "to": "icon2",
+ "label": "Authorizer"
+ },
+ "line2": {
+ "from": "icon2",
+ "to": "icon3",
+ "label": "Request secret"
+ }
+ }
+}
diff --git a/apigw-secretsmanager-apikey-cdk/bin/apigw-secrectsmanager-apikey-cdk.ts b/apigw-secretsmanager-apikey-cdk/bin/apigw-secrectsmanager-apikey-cdk.ts
new file mode 100644
index 000000000..4d1d19f9d
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/bin/apigw-secrectsmanager-apikey-cdk.ts
@@ -0,0 +1,12 @@
+#!/usr/bin/env node
+import * as cdk from "aws-cdk-lib";
+import { ApigwSecretsmanagerApikeyStack } from "../lib/apigw-secretsmanager-apikey-stack";
+
+const app = new cdk.App();
+// amazonq-ignore-next-line
+new ApigwSecretsmanagerApikeyStack(app, "ApigwSecretsmanagerApikeyCdkStack", {
+ env: {
+ account: process.env.CDK_DEFAULT_ACCOUNT,
+ region: process.env.CDK_DEFAULT_REGION,
+ },
+});
diff --git a/apigw-secretsmanager-apikey-cdk/cdk.json b/apigw-secretsmanager-apikey-cdk/cdk.json
new file mode 100644
index 000000000..30908c28d
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/cdk.json
@@ -0,0 +1,88 @@
+{
+ "app": "npx ts-node --prefer-ts-exts bin/apigw-secrectsmanager-apikey-cdk.ts",
+ "watch": {
+ "include": [
+ "**"
+ ],
+ "exclude": [
+ "README.md",
+ "cdk*.json",
+ "**/*.d.ts",
+ "**/*.js",
+ "tsconfig.json",
+ "package*.json",
+ "yarn.lock",
+ "node_modules",
+ "test"
+ ]
+ },
+ "context": {
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
+ "@aws-cdk/core:checkSecretUsage": true,
+ "@aws-cdk/core:target-partitions": [
+ "aws",
+ "aws-cn"
+ ],
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
+ "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
+ "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
+ "@aws-cdk/aws-iam:minimizePolicies": true,
+ "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
+ "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
+ "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
+ "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
+ "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
+ "@aws-cdk/core:enablePartitionLiterals": true,
+ "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
+ "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
+ "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
+ "@aws-cdk/aws-route53-patters:useCertificate": true,
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
+ "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
+ "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
+ "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
+ "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
+ "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
+ "@aws-cdk/aws-redshift:columnId": true,
+ "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
+ "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
+ "@aws-cdk/aws-kms:aliasNameRef": true,
+ "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
+ "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
+ "@aws-cdk/aws-efs:denyAnonymousAccess": true,
+ "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
+ "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
+ "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
+ "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
+ "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
+ "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
+ "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
+ "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
+ "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
+ "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
+ "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
+ "@aws-cdk/aws-eks:nodegroupNameAttribute": true,
+ "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
+ "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
+ "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
+ "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
+ "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
+ "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
+ "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
+ "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
+ "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
+ "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
+ "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
+ "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
+ "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
+ "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
+ "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
+ "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
+ "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
+ "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
+ "@aws-cdk/core:enableAdditionalMetadataCollection": true,
+ "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": true
+ }
+}
diff --git a/apigw-secretsmanager-apikey-cdk/create_api_key.sh b/apigw-secretsmanager-apikey-cdk/create_api_key.sh
new file mode 100755
index 000000000..7247dcd3f
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/create_api_key.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# Create/update API key for a tenant
+# Usage: ./create-api-key tenant-1
+
+tenant_id=$1
+if [ -z "$tenant_id" ]; then
+ echo "Error: Tenant ID is required"
+ exit 1
+fi
+
+# Generate random 32-character API key
+api_key=$(openssl rand -hex 16)
+
+# Store the secret with the API key as the identifier
+aws secretsmanager create-secret \
+ --name "api-key-${api_key}" \
+ --secret-string "{\"tenantId\":\"${tenant_id}\"}" \
+ --no-cli-pager
+
+echo "API key for tenant ${tenant_id} created: ${api_key}"
diff --git a/apigw-secretsmanager-apikey-cdk/example-pattern.json b/apigw-secretsmanager-apikey-cdk/example-pattern.json
new file mode 100644
index 000000000..2a5b08f40
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/example-pattern.json
@@ -0,0 +1,65 @@
+{
+ "title": "API Gateway, Lambda Authorizer & Secrets Manager for API Key Authentication",
+ "description": "Implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
+
+ "language": "TypeScript",
+ "level": "200",
+ "framework": "CDK",
+ "introBox": {
+ "headline": "How it works",
+ "text": [
+ "This pattern demonstrates how to implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
+ "Each user/tenant has their own unique API key stored in Secrets Manager, which is validated by a Lambda authorizer when requests are made to protected API endpoints.",
+ "The Lambda authorizer checks if the API key exists in Secrets Manager. If the key is valid, the associated tenant information is retrieved and included in the authorization context.",
+ "The API Gateway then allows or denies access to the protected endpoint based on the policy returned by the authorizer."
+ ]
+ },
+ "gitHub": {
+ "template": {
+ "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-secretsmanager-apikey-cdk",
+ "templateURL": "serverless-patterns/apigw-secretsmanager-apikey-cdk",
+ "projectFolder": "apigw-secretsmanager-apikey-cdk",
+ "templateFile": "lib/apigw-secretsmanager-apikey-stack.ts"
+ }
+ },
+ "resources": {
+ "bullets": [
+ {
+ "text": "Lambda Authorizers for Amazon API Gateway",
+ "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"
+ },
+ {
+ "text": "AWS Secrets Manager User Guide",
+ "link": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html"
+ },
+ {
+ "text": "Amazon API Gateway - REST APIs",
+ "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html"
+ }
+ ]
+ },
+ "deploy": {
+ "text": ["npm install", "cdk deploy"]
+ },
+ "testing": {
+ "text": [
+ "Create an API key using the provided script: ./create_api_key.sh sample-tenant",
+ "Make a request to the protected endpoint using the valid API key: curl -H \"x-api-key: CREATED_API_KEY\" https://REPLACE_WITH_CREATED_API_URL.amazonaws.com/prod/protected",
+ "If successful, you should receive a response: { \"message\": \"Access granted\" }"
+ ]
+ },
+ "cleanup": {
+ "text": [
+ "Delete the CDK stack: cdk destroy",
+ "Delete created SecretManager keys using the provided script: ./remove_secrets.sh"
+ ]
+ },
+ "authors": [
+ {
+ "name": "Marco Jahn",
+ "image": "https://sessionize.com/image/e99b-400o400o2-pqR4BacUSzHrq4fgZ4wwEQ.png",
+ "bio": "Senior Solutions Architect - ISV, Amazon Web Services",
+ "linkedin": "marcojahn"
+ }
+ ]
+}
diff --git a/apigw-secretsmanager-apikey-cdk/lib/apigw-secretsmanager-apikey-stack.ts b/apigw-secretsmanager-apikey-cdk/lib/apigw-secretsmanager-apikey-stack.ts
new file mode 100644
index 000000000..e59b0547b
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/lib/apigw-secretsmanager-apikey-stack.ts
@@ -0,0 +1,93 @@
+import * as cdk from "aws-cdk-lib";
+import * as apigateway from "aws-cdk-lib/aws-apigateway";
+import * as lambda from "aws-cdk-lib/aws-lambda-nodejs";
+import { Runtime } from "aws-cdk-lib/aws-lambda";
+import * as iam from "aws-cdk-lib/aws-iam";
+import * as path from "path";
+import { Construct } from "constructs";
+
+export class ApigwSecretsmanagerApikeyStack extends cdk.Stack {
+ constructor(scope: Construct, id: string, props?: cdk.StackProps) {
+ super(scope, id, props);
+
+ // Create IAM role for the authorizer Lambda
+ const authorizerRole = new iam.Role(this, "AuthorizerRole", {
+ assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
+ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")],
+ });
+
+ // Add more specific permission to access only relevant Secrets Manager secrets
+ authorizerRole.addToPolicy(
+ new iam.PolicyStatement({
+ actions: ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
+ resources: [`arn:aws:secretsmanager:${this.region}:${this.account}:secret:api-key-*`],
+ }),
+ );
+
+ // Create a dedicated logGroup for ApiKeyAuthorizer
+ const authorizerLogGroup = new cdk.aws_logs.LogGroup(this, "AuthorizerLogGroup", {
+ retention: cdk.aws_logs.RetentionDays.ONE_WEEK,
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
+ });
+
+ // Create Lambda authorizer
+ const authorizer = new lambda.NodejsFunction(this, "ApiKeyAuthorizer", {
+ runtime: Runtime.NODEJS_22_X,
+ entry: path.join(__dirname, "lambda/authorizer.js"),
+ role: authorizerRole,
+ timeout: cdk.Duration.seconds(10),
+ environment: {
+ SECRET_PREFIX: "api-key-",
+ },
+ bundling: {
+ externalModules: [
+ "@aws-sdk/*", // AWS SDK v3 modules
+ ],
+ },
+ logGroup: authorizerLogGroup,
+ });
+
+ // Create API Gateway
+ const api = new apigateway.RestApi(this, "ApiGateway", {
+ restApiName: "API Key Protected Service",
+ description: "API protected with API key authorization",
+ });
+
+ // Create Lambda authorizer
+ const lambdaAuthorizer = new apigateway.TokenAuthorizer(this, "TokenAuthorizer", {
+ handler: authorizer,
+ identitySource: "method.request.header.x-api-key",
+ });
+
+ // Sample protected endpoint
+ const protectedResource = api.root.addResource("protected");
+
+ // Mock integration for demo purposes
+ const integration = new apigateway.MockIntegration({
+ integrationResponses: [
+ {
+ statusCode: "200",
+ responseTemplates: {
+ "application/json": '{ "message": "Access granted" }',
+ },
+ },
+ ],
+ passthroughBehavior: apigateway.PassthroughBehavior.NEVER,
+ requestTemplates: {
+ "application/json": '{ "statusCode": 200 }',
+ },
+ });
+
+ // Add method with authorizer
+ protectedResource.addMethod("GET", integration, {
+ authorizer: lambdaAuthorizer,
+ methodResponses: [{ statusCode: "200" }],
+ });
+
+ // Output the API URL
+ new cdk.CfnOutput(this, "ApiUrl", {
+ value: api.url,
+ description: "URL of the API Gateway",
+ });
+ }
+}
diff --git a/apigw-secretsmanager-apikey-cdk/lib/lambda/authorizer.js b/apigw-secretsmanager-apikey-cdk/lib/lambda/authorizer.js
new file mode 100644
index 000000000..3a40d0725
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/lib/lambda/authorizer.js
@@ -0,0 +1,87 @@
+import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
+const client = new SecretsManagerClient();
+
+const SECRET_PREFIX = process.env.SECRET_PREFIX || "api-key-";
+
+exports.handler = async (event) => {
+ // Get API key from request headers
+ const apiKey = event.authorizationToken;
+
+ if (!apiKey) {
+ throw new Error("Unauthorized: No API key provided");
+ }
+
+ try {
+ // Validate API key by directly accessing the specific secret
+ const tenantData = await validateApiKey(apiKey, event);
+
+ if (tenantData) {
+ return generatePolicy(tenantData.tenantId, "Allow", event.methodArn, tenantData.tenantId);
+ } else {
+ throw new Error("Unauthorized: Invalid API key");
+ }
+ } catch (error) {
+ console.error("Authorization error:", error.message);
+ throw new Error("Unauthorized");
+ }
+};
+
+async function validateApiKey(apiKey, event) {
+ try {
+ // Get the secret directly using the API key's secret name
+ const secretId = `${SECRET_PREFIX}${apiKey}`;
+
+ const secretData = await client.send(
+ new GetSecretValueCommand({
+ SecretId: secretId,
+ }),
+ );
+
+ if (secretData.SecretString) {
+ const secret = JSON.parse(secretData.SecretString);
+ return {
+ tenantId: secret.tenantId,
+ valid: true,
+ };
+ }
+ } catch (error) {
+ // Secret not found or other error - API key is invalid
+ if (error.code === "ResourceNotFoundException") {
+ console.log("API key not found");
+ } else {
+ console.error("Error fetching API key secret:", error);
+ }
+ return null;
+ }
+
+ return null;
+}
+
+// Helper function to generate IAM policy
+function generatePolicy(principalId, effect, resource, tenantId) {
+ const authResponse = {
+ principalId: principalId,
+ };
+
+ if (effect && resource) {
+ const policyDocument = {
+ Version: "2012-10-17",
+ Statement: [
+ {
+ Effect: effect,
+ Resource: resource,
+ Action: "execute-api:Invoke",
+ },
+ ],
+ };
+
+ authResponse.policyDocument = policyDocument;
+
+ // Add context with tenant ID for downstream Lambda functions
+ authResponse.context = {
+ tenantId: tenantId,
+ };
+ }
+
+ return authResponse;
+}
diff --git a/apigw-secretsmanager-apikey-cdk/package.json b/apigw-secretsmanager-apikey-cdk/package.json
new file mode 100644
index 000000000..97b35e2da
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "apigw-secrectsmanager-apikey-cdk",
+ "version": "0.1.0",
+ "bin": {
+ "apigw-secrectsmanager-apikey-cdk": "bin/apigw-secrectsmanager-apikey-cdk.js"
+ },
+ "scripts": {
+ "build": "tsc",
+ "watch": "tsc -w",
+ "cdk": "cdk"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.14",
+ "@types/node": "22.7.9",
+ "aws-cdk": "2.1003.0",
+ "esbuild": "^0.25.1",
+ "jest": "^29.7.0",
+ "ts-jest": "^29.2.5",
+ "ts-node": "^10.9.2",
+ "typescript": "~5.6.3"
+ },
+ "dependencies": {
+ "aws-cdk-lib": "2.181.1",
+ "constructs": "^10.0.0"
+ }
+}
diff --git a/apigw-secretsmanager-apikey-cdk/remove_secrets.sh b/apigw-secretsmanager-apikey-cdk/remove_secrets.sh
new file mode 100755
index 000000000..6f3c8ea3d
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/remove_secrets.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Define the prefix
+PREFIX="api-key-"
+
+# Add a dry run flag
+DRY_RUN=false
+
+# Parse command line arguments
+while [[ "$#" -gt 0 ]]; do
+ case $1 in
+ --dry-run) DRY_RUN=true ;;
+ *) echo "Unknown parameter passed: $1"; exit 1 ;;
+ esac
+ shift
+done
+
+# List secrets with the prefix and store them in an array
+secrets=($(aws secretsmanager list-secrets --query 'SecretList[?starts_with(Name, `'"$PREFIX"'`)].Name' --output text))
+
+# Loop through each secret
+for secret in "${secrets[@]}"; do
+ if $DRY_RUN; then
+ echo "Would delete secret: $secret"
+ else
+ aws secretsmanager delete-secret --secret-id "$secret" --recovery-window-in-days 7 --no-cli-pager
+ echo "Deleted secret: $secret"
+ fi
+done
+
+if $DRY_RUN; then
+ echo "Dry run completed. No secrets were actually deleted."
+fi
diff --git a/apigw-secretsmanager-apikey-cdk/tsconfig.json b/apigw-secretsmanager-apikey-cdk/tsconfig.json
new file mode 100644
index 000000000..aaa7dc510
--- /dev/null
+++ b/apigw-secretsmanager-apikey-cdk/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "commonjs",
+ "lib": [
+ "es2020",
+ "dom"
+ ],
+ "declaration": true,
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": false,
+ "inlineSourceMap": true,
+ "inlineSources": true,
+ "experimentalDecorators": true,
+ "strictPropertyInitialization": false,
+ "typeRoots": [
+ "./node_modules/@types"
+ ]
+ },
+ "exclude": [
+ "node_modules",
+ "cdk.out"
+ ]
+}