Skip to content

Commit 7e746d8

Browse files
authored
Merge pull request #2690 from marcojahn/apigw-secretsmanager-apikey-cdk
New pattern submission - API Gateway with Lambda Authorizer and Secrets Manager for API Key Authentication
2 parents b584d57 + 943b7dd commit 7e746d8

12 files changed

+652
-0
lines changed
+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Amazon API Gateway with AWS Lambda Authorizer and AWS Secrets Manager for API Key Authentication
2+
3+
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.
4+
5+
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)
6+
7+
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.
8+
9+
## Requirements
10+
11+
* [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.
12+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
13+
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed
14+
* [Node.js and npm](https://nodejs.org/) installed
15+
* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) installed
16+
17+
## Deployment Instructions
18+
19+
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
20+
```
21+
git clone https://github.com/aws-samples/serverless-patterns
22+
```
23+
1. Change directory to the pattern directory:
24+
```
25+
cd apigw-secretsmanager-apikey-cdk
26+
```
27+
1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
28+
```
29+
npm install
30+
```
31+
1. Deploy the stack:
32+
```
33+
cdk deploy
34+
```
35+
36+
Note the outputs from the CDK deployment process. The output will include the API Gateway URL you'll need for testing.
37+
38+
## How it works
39+
40+
![Architecture Diagram](./apigw-secretsmanager-apikey-cdk.png)
41+
42+
1. Client makes a request to the API with an API key in the `x-api-key` header
43+
2. API Gateway forwards the authorization request to the Lambda Authorizer
44+
- The Lambda Authorizer checks if the API key exists in Secrets Manager
45+
- If the key is valid, the associated tenant information is retrieved and included in the authorization context
46+
3. The API Gateway then allows or denies access to the protected endpoint based on the policy returned by the authorizer
47+
48+
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.
49+
50+
## Testing
51+
52+
1. Create a new api key, you will need the api key later on
53+
```
54+
❯ ./create_api_key.sh sample-tenant
55+
API key for tenant sample-tenant created: b4037c9368990982ac5d1c670053bf76
56+
```
57+
1. Get the API Gateway URL from the deployment output:
58+
```bash
59+
# The output will be similar to
60+
ApigwSecretsmanagerApikeyCdkStack.ApiUrl = https://383rm6ue91.execute-api.us-east-1.amazonaws.com/prod/
61+
```
62+
1. Make a request to the protected endpoint with a valid API key:
63+
```
64+
curl -H "x-api-key: CREATED_API_KEY" https://REPLACE_WITH_URL_FROM_CDK_OUTPUT/protected
65+
```
66+
If successful, you should receive a response like:
67+
```
68+
{ "message": "Access granted" }
69+
```
70+
1. Try with an invalid API key:
71+
```
72+
curl -H "x-api-key: invalid-key" https://REPLACE_WITH_URL_FROM_CDK_OUTPUT/protected
73+
```
74+
You should receive an unauthorized error.
75+
1. Try without an API key:
76+
```
77+
curl https://REPLACE_WITH_URL_FROM_CDK_OUTPUT/protected
78+
```
79+
You should also receive an unauthorized error.
80+
81+
82+
## Cleanup
83+
84+
1. Delete the stack
85+
```bash
86+
cdk destroy
87+
```
88+
1. Delete created SecretManager keys using
89+
```bash
90+
./remove_secrets.sh
91+
92+
# to check which keys will be removed, use the dry-run option
93+
./remove_secrets.sh --dry-run
94+
```
95+
96+
----
97+
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
98+
99+
SPDX-License-Identifier: MIT-0
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{
2+
"title": "API Gateway, Lambda Authorizer & Secrets Manager for API Key Authentication",
3+
"description": "Implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
4+
"language": "TypeScript",
5+
"level": "200",
6+
"framework": "CDK",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"This pattern demonstrates how to implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
11+
"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.",
12+
"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.",
13+
"The API Gateway then allows or denies access to the protected endpoint based on the policy returned by the authorizer."
14+
]
15+
},
16+
"gitHub": {
17+
"template": {
18+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-secretsmanager-apikey-cdk",
19+
"templateURL": "serverless-patterns/apigw-secretsmanager-apikey-cdk",
20+
"projectFolder": "apigw-secretsmanager-apikey-cdk",
21+
"templateFile": "lib/apigw-secretsmanager-apikey-stack.ts"
22+
}
23+
},
24+
"resources": {
25+
"bullets": [
26+
{
27+
"text": "Lambda Authorizers for Amazon API Gateway",
28+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"
29+
},
30+
{
31+
"text": "AWS Secrets Manager User Guide",
32+
"link": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html"
33+
},
34+
{
35+
"text": "Amazon API Gateway - REST APIs",
36+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html"
37+
}
38+
]
39+
},
40+
"deploy": {
41+
"text": [
42+
"npm install",
43+
"cdk deploy"
44+
]
45+
},
46+
"testing": {
47+
"text": [
48+
"Create an API key using the provided script: <code>./create_api_key.sh sample-tenant</code>",
49+
"Make a request to the protected endpoint using the valid API key: <code>curl -H \"x-api-key: CREATED_API_KEY\" https://REPLACE_WITH_CREATED_API_URL.amazonaws.com/prod/protected</code>",
50+
"If successful, you should receive a response: <code>{ \"message\": \"Access granted\" }</code>"
51+
]
52+
},
53+
"cleanup": {
54+
"text": [
55+
"Delete the CDK stack: <code>cdk destroy</code>",
56+
"Delete created SecretManager keys using the provided script: <code>./remove_secrets.sh</code>"
57+
]
58+
},
59+
"authors": [
60+
{
61+
"name": "Marco Jahn",
62+
"image": "https://sessionize.com/image/e99b-400o400o2-pqR4BacUSzHrq4fgZ4wwEQ.png",
63+
"bio": "Senior Solutions Architect - ISV, Amazon Web Services",
64+
"linkedin": "marcojahn"
65+
}
66+
],
67+
"patternArch": {
68+
"icon1": {
69+
"x": 20,
70+
"y": 50,
71+
"service": "apigw",
72+
"label": "API Gateway REST API"
73+
},
74+
"icon2": {
75+
"x": 50,
76+
"y": 50,
77+
"service": "lambda",
78+
"label": "AWS Lambda Authorizer"
79+
},
80+
"icon3": {
81+
"x": 80,
82+
"y": 50,
83+
"service": "secretsmanager",
84+
"label": "AWS Secrets Manager"
85+
},
86+
"line1": {
87+
"from": "icon1",
88+
"to": "icon2",
89+
"label": "Authorizer"
90+
},
91+
"line2": {
92+
"from": "icon2",
93+
"to": "icon3",
94+
"label": "Request secret"
95+
}
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env node
2+
import * as cdk from "aws-cdk-lib";
3+
import { ApigwSecretsmanagerApikeyStack } from "../lib/apigw-secretsmanager-apikey-stack";
4+
5+
const app = new cdk.App();
6+
// amazonq-ignore-next-line
7+
new ApigwSecretsmanagerApikeyStack(app, "ApigwSecretsmanagerApikeyCdkStack", {
8+
env: {
9+
account: process.env.CDK_DEFAULT_ACCOUNT,
10+
region: process.env.CDK_DEFAULT_REGION,
11+
},
12+
});
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/apigw-secrectsmanager-apikey-cdk.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40+
"@aws-cdk/aws-route53-patters:useCertificate": true,
41+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47+
"@aws-cdk/aws-redshift:columnId": true,
48+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51+
"@aws-cdk/aws-kms:aliasNameRef": true,
52+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
55+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71+
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
72+
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
73+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
74+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
75+
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
76+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
77+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
78+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
79+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
80+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
81+
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
82+
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
83+
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
84+
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
85+
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
86+
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": true
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
3+
# Create/update API key for a tenant
4+
# Usage: ./create-api-key tenant-1
5+
6+
tenant_id=$1
7+
if [ -z "$tenant_id" ]; then
8+
echo "Error: Tenant ID is required"
9+
exit 1
10+
fi
11+
12+
# Generate random 32-character API key
13+
api_key=$(openssl rand -hex 16)
14+
15+
# Store the secret with the API key as the identifier
16+
aws secretsmanager create-secret \
17+
--name "api-key-${api_key}" \
18+
--secret-string "{\"tenantId\":\"${tenant_id}\"}" \
19+
--no-cli-pager
20+
21+
echo "API key for tenant ${tenant_id} created: ${api_key}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"title": "API Gateway, Lambda Authorizer & Secrets Manager for API Key Authentication",
3+
"description": "Implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
4+
5+
"language": "TypeScript",
6+
"level": "200",
7+
"framework": "CDK",
8+
"introBox": {
9+
"headline": "How it works",
10+
"text": [
11+
"This pattern demonstrates how to implement a secure API key-based authorization system using Amazon API Gateway, Lambda Authorizer, and AWS Secrets Manager.",
12+
"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.",
13+
"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.",
14+
"The API Gateway then allows or denies access to the protected endpoint based on the policy returned by the authorizer."
15+
]
16+
},
17+
"gitHub": {
18+
"template": {
19+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-secretsmanager-apikey-cdk",
20+
"templateURL": "serverless-patterns/apigw-secretsmanager-apikey-cdk",
21+
"projectFolder": "apigw-secretsmanager-apikey-cdk",
22+
"templateFile": "lib/apigw-secretsmanager-apikey-stack.ts"
23+
}
24+
},
25+
"resources": {
26+
"bullets": [
27+
{
28+
"text": "Lambda Authorizers for Amazon API Gateway",
29+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html"
30+
},
31+
{
32+
"text": "AWS Secrets Manager User Guide",
33+
"link": "https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html"
34+
},
35+
{
36+
"text": "Amazon API Gateway - REST APIs",
37+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html"
38+
}
39+
]
40+
},
41+
"deploy": {
42+
"text": ["npm install", "cdk deploy"]
43+
},
44+
"testing": {
45+
"text": [
46+
"Create an API key using the provided script: <code>./create_api_key.sh sample-tenant</code>",
47+
"Make a request to the protected endpoint using the valid API key: <code>curl -H \"x-api-key: CREATED_API_KEY\" https://REPLACE_WITH_CREATED_API_URL.amazonaws.com/prod/protected</code>",
48+
"If successful, you should receive a response: <code>{ \"message\": \"Access granted\" }</code>"
49+
]
50+
},
51+
"cleanup": {
52+
"text": [
53+
"Delete the CDK stack: <code>cdk destroy</code>",
54+
"Delete created SecretManager keys using the provided script: <code>./remove_secrets.sh</code>"
55+
]
56+
},
57+
"authors": [
58+
{
59+
"name": "Marco Jahn",
60+
"image": "https://sessionize.com/image/e99b-400o400o2-pqR4BacUSzHrq4fgZ4wwEQ.png",
61+
"bio": "Senior Solutions Architect - ISV, Amazon Web Services",
62+
"linkedin": "marcojahn"
63+
}
64+
]
65+
}

0 commit comments

Comments
 (0)