Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions apigw-secretsmanager-apikey-cdk/README.md
Original file line number Diff line number Diff line change
@@ -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

![Architecture Diagram](./apigw-secretsmanager-apikey-cdk.png)

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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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: <code>./create_api_key.sh sample-tenant</code>",
"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>",
"If successful, you should receive a response: <code>{ \"message\": \"Access granted\" }</code>"
]
},
"cleanup": {
"text": [
"Delete the CDK stack: <code>cdk destroy</code>",
"Delete created SecretManager keys using the provided script: <code>./remove_secrets.sh</code>"
]
},
"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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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,
},
});
88 changes: 88 additions & 0 deletions apigw-secretsmanager-apikey-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -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
}
}
21 changes: 21 additions & 0 deletions apigw-secretsmanager-apikey-cdk/create_api_key.sh
Original file line number Diff line number Diff line change
@@ -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}"
65 changes: 65 additions & 0 deletions apigw-secretsmanager-apikey-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -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: <code>./create_api_key.sh sample-tenant</code>",
"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>",
"If successful, you should receive a response: <code>{ \"message\": \"Access granted\" }</code>"
]
},
"cleanup": {
"text": [
"Delete the CDK stack: <code>cdk destroy</code>",
"Delete created SecretManager keys using the provided script: <code>./remove_secrets.sh</code>"
]
},
"authors": [
{
"name": "Marco Jahn",
"image": "https://sessionize.com/image/e99b-400o400o2-pqR4BacUSzHrq4fgZ4wwEQ.png",
"bio": "Senior Solutions Architect - ISV, Amazon Web Services",
"linkedin": "marcojahn"
}
]
}
Loading