-
Notifications
You must be signed in to change notification settings - Fork 1k
New serverless pattern APIGW-APIKey-tenantid-mapping #3124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
004c876
9397873
0c7a68f
5e69d50
1739bc0
c6e8226
e78d895
6bf4bc5
b048c4e
431e3ee
c17ba2c
455247a
717db4c
5d50c5b
caff571
fc92614
9d027a4
3030a71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| # Amazon API Gateway with Lambda authorizer, Amazon Cognito and Amazon DynamoDB for Tenant API Key Authentication | ||
|
|
||
| API Gateway's usage plans and API keys are fundamentally disconnected from authorization tokens. | ||
| Usage plans enforce rate limits via API keys, but auth tokens (JWTs from Cognito, Auth0, etc.) carry identity and permissions — these are two separate systems with no native link. This means customers cannot simply issue an auth token that inherently comes with rate-limiting attached. At scale (millions of auth tokens across thousands of tenants), managing this disconnect manually becomes untenable. | ||
|
|
||
| This pattern demonstrates how to implement a secure tenant-based API key authorization system using AWS Lambda authorizer, Amazon API Gateway, Amazon Cognito, and Amazon DynamoDB. Cognito authenticates users and issues JWTs containing a custom `tenantId` claim. The Lambda authorizer extracts the tenant ID from the JWT, looks up the corresponding API key in DynamoDB, and returns a policy document enabling API Gateway access. | ||
|
|
||
| What this pattern solves: | ||
| - Bridges the auth–throttling gap — The Lambda authorizer acts as the glue between identity (JWT tenantId) and rate-limiting (API Gateway API key). By looking up the tenant's API key in DynamoDB and returning it via [usageIdentifierKey](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html), a single auth token automatically activates the correct usage plan. Auth and throttling become one unified flow rather than two disconnected systems. | ||
| - Scales to millions of tokens per tenant — Any number of JWTs can map to the same tenant's API key. You don't need a 1:1 relationship between auth tokens and API keys. A tenant can have millions of active tokens, but they all resolve to one API key and one rate-limit policy — making management tractable at scale. | ||
| - Eliminates per-application auth logic — Backend services no longer independently validate tenants or enforce limits. The gateway handles both centrally, preventing inconsistency and reducing overhead. | ||
| - Prevents noisy neighbors transparently — Tenants only interact with their auth credentials. The API key mapping and usage plan enforcement happen internally, so rate-limiting is invisible to consumers but enforced consistently. | ||
| - Makes auth and usage a single operational concern — Onboarding a new tenant means: create identity (Cognito/Auth0), create API key with a usage plan, store the mapping in DynamoDB. One workflow governs both auth and throttling, rather than managing them as separate systems that drift apart over time. | ||
|
|
||
|
|
||
| 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. | ||
|
|
||
| ## Prerequisites: Usage Plan and API Key | ||
|
|
||
| Before using this pattern, you must create API Gateway [Usage Plans](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html) and an [API Key](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-setup-api-key-with-console.html) associated with it. The usage plan must be associated with the API and stage created by this CDK stack. | ||
| The usage plan must be associated with the API and stage created by this CDK stack. The API key value stored in the DynamoDB table must match a valid API key linked to a usage plan in API Gateway — otherwise, requests will be rejected even if the Lambda authorizer returns a successful policy. | ||
|
|
||
| For guidance on creating and configuring usage plans and API keys, see: | ||
| - [Create and use usage plans with API keys](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html) | ||
| - [Setting up API keys using the API Gateway console](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-setup-api-key-with-console.html) | ||
|
|
||
| ## 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-apikey-tenantid-cdk | ||
| ``` | ||
| 1. Install dependencies: | ||
| ``` | ||
| npm install | ||
| ``` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added cdk bootstrap |
||
| 1. Bootstrap the CDK environment (if you haven't already): | ||
| ``` | ||
| cdk bootstrap | ||
| ``` | ||
| 1. Deploy the stack: | ||
| ``` | ||
| cdk deploy | ||
| ``` | ||
|
|
||
| Note the outputs from the CDK deployment process. The output will include the API Gateway URL, DynamoDB table name, Cognito User Pool ID, and User Pool Client ID. | ||
|
|
||
| ## How it works | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The section says the API key is "returned in the authorization context via usageIdentifierKey," but the deployed stack never creates a usage plan or API key, and the test steps put a "apiKey": "my-api-key-123" item into DynamoDB that is shorter than the 20-character minimum for a real API key and is never associated with any plan. So even after following the README, throttling is not clearly demonstrated.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added instructions for usage plan and APIkey in the testing section |
||
|
|
||
|  | ||
|
|
||
| 1. Client authenticates with Amazon Cognito and receives a JWT (ID token) containing the custom `tenantId` claim | ||
| 2. Client makes a request to the API with the JWT in the `Authorization` header | ||
| 3. API Gateway forwards the token to the Lambda Authorizer | ||
| 4. The Lambda Authorizer decodes the JWT, extracts the `custom:tenantId` claim, and looks up the tenant in the DynamoDB table | ||
| - If the tenant exists, the associated API key is retrieved and returned in the authorization context via `usageIdentifierKey` | ||
| - If the tenant does not exist or the token is invalid, the request is denied | ||
| 5. The API Gateway allows or denies access to the protected endpoint based on the policy returned by the authorizer | ||
|
|
||
| The DynamoDB table uses `tenantId` as the partition key and stores the corresponding `apiKey` for each tenant. | ||
|
|
||
|
|
||
|
|
||
| ## Testing | ||
|
|
||
| > **Note:** This sample uses `USER_PASSWORD_AUTH` for simplicity. In production, use `USER_SRP_AUTH` (Secure Remote Password) so that the password is never transmitted over the network. The plain password flow here is for demonstration purposes only. | ||
|
|
||
| 1. Get the outputs from the deployment: | ||
| ```bash | ||
| # The outputs will be similar to | ||
| ApigwDynamodbApikeyCdkStack.ApiUrl = https://abc123def.execute-api.us-east-1.amazonaws.com/prod/ | ||
| ApigwDynamodbApikeyCdkStack.TableName = ApigwDynamodbApikeyCdkStack-TenantApiKeyTableXXXXXX-YYYYYY | ||
| ApigwDynamodbApikeyCdkStack.UserPoolId = us-east-1_XXXXXXXXX | ||
| ApigwDynamodbApikeyCdkStack.UserPoolClientId = XXXXXXXXXXXXXXXXXXXXXXXXXX | ||
| ``` | ||
|
|
||
| 1. Create a usage plan: | ||
| ```bash | ||
| aws apigateway create-usage-plan \ | ||
| --name "TenantUsagePlan" \ | ||
| --throttle burstLimit=50,rateLimit=100 \ | ||
| --api-stages apiId=API_ID,stage=prod | ||
| ``` | ||
| Note the `id` from the output — this is your USAGE_PLAN_ID. | ||
|
|
||
| 1. Create an API key: | ||
| ```bash | ||
| aws apigateway create-api-key \ | ||
| --name "SampleTenantKey" \ | ||
| --enabled \ | ||
| --value "tenant-usage-api-key-123" | ||
| ``` | ||
| Note the `id` from the output — this is your API_KEY_ID. | ||
|
|
||
| 1. Associate the API key with the usage plan: | ||
| ```bash | ||
| aws apigateway create-usage-plan-key \ | ||
| --usage-plan-id USAGE_PLAN_ID \ | ||
| --key-id API_KEY_ID \ | ||
| --key-type "API_KEY" | ||
| ``` | ||
|
|
||
| 1. Create a Cognito user with a tenantId: | ||
| ```bash | ||
| aws cognito-idp admin-create-user \ | ||
| --user-pool-id USER_POOL_ID \ | ||
| --username user@example.com \ | ||
| --user-attributes Name=email,Value=user@example.com Name=custom:tenantId,Value=sample-tenant \ | ||
| --temporary-password "TempPass1!" | ||
| ``` | ||
|
|
||
| 1. Set a permanent password for the user: | ||
| ```bash | ||
| aws cognito-idp admin-set-user-password \ | ||
| --user-pool-id USER_POOL_ID \ | ||
| --username user@example.com \ | ||
| --password "MySecurePass1!" \ | ||
| --permanent | ||
| ``` | ||
|
|
||
| 1. Insert a tenant mapping into the DynamoDB table: | ||
| ```bash | ||
| aws dynamodb put-item \ | ||
| --table-name TABLE_NAME \ | ||
| --item '{"tenantId": {"S": "sample-tenant"}, "apiKey": {"S": "tenant-usage-api-key-123"}}' | ||
| ``` | ||
|
|
||
| 1. Get a token and call the API using the helper script: | ||
| ```bash | ||
| node get-token.js --user-pool-id USER_POOL_ID --client-id CLIENT_ID \ | ||
| --username user@example.com --password "MySecurePass1!" \ | ||
| --api-url https://REPLACE_WITH_API_URL/protected | ||
| ``` | ||
| If successful, you should receive a response like: | ||
| ```json | ||
| { "message": "Access granted" } | ||
| ``` | ||
|
|
||
| 1. Try with an invalid or missing token: | ||
| ```bash | ||
| curl https://REPLACE_WITH_API_URL/protected | ||
| ``` | ||
| You should receive an unauthorized error. | ||
|
|
||
| ## Cleanup | ||
|
|
||
| 1. Delete the stack: | ||
| ```bash | ||
| cdk destroy | ||
| ``` | ||
|
|
||
| ---- | ||
| Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: MIT-0 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| { | ||
| "app": "npx ts-node --prefer-ts-exts src/bin/apigw-dynamodb-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 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The repo's schema-validation workflow expects the metadata file to be named exactly example-pattern.json at the pattern root.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. renamed the file |
||
| "title": "Amazon API Gateway with Lambda authorizer, Amazon Cognito, & Amazon DynamoDB for Tenant API Key Authentication", | ||
| "description": "Implement a secure tenant-based API key authorization system using Amazon Cognito, Amazon API Gateway, AWS Lambda Authorizer, and Amazon DynamoDB. Cognito issues JWTs with a custom tenantId claim, and the Lambda authorizer maps tenants to API keys via DynamoDB.", | ||
|
|
||
| "language": "TypeScript", | ||
| "level": "200", | ||
| "framework": "AWS CDK", | ||
| "introBox": { | ||
| "headline": "How it works", | ||
| "text": [ | ||
| "This pattern demonstrates how to implement a secure tenant-based API key authorization system using Amazon Cognito, Amazon API Gateway, Lambda Authorizer, and Amazon DynamoDB.", | ||
| "Amazon Cognito authenticates users and issues JWTs (ID tokens) containing a custom tenantId claim.", | ||
| "The client sends the JWT in the Authorization header. API Gateway forwards the token to the Lambda authorizer, which decodes the JWT, extracts the custom:tenantId claim, and queries DynamoDB to retrieve the corresponding API key.", | ||
| "The authorizer returns a policy document with the usageIdentifierKey set to the API key, enabling API Gateway usage plan integration.", | ||
| "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-apikey-tenantid-cdk", | ||
| "templateURL": "serverless-patterns/apigw-apikey-tenantid-cdk", | ||
| "projectFolder": "apigw-apikey-tenantid-cdk", | ||
| "templateFile": "src/lib/apigw-dynamodb-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": "Amazon API Gateway - REST APIs", | ||
| "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html" | ||
| }, | ||
| { | ||
| "text": "Amazon Cognito Developer Guide", | ||
| "link": "https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html" | ||
| }, | ||
| { | ||
| "text": "Amazon DynamoDB Developer Guide", | ||
| "link": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html" | ||
| }, | ||
| { | ||
| "text": "API Gateway Usage Plans", | ||
| "link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html" | ||
| } | ||
| ] | ||
| }, | ||
| "deploy": { | ||
| "text": ["npm install", "cdk deploy"] | ||
| }, | ||
| "testing": { | ||
| "text": [ | ||
| "Create a Cognito user: <code>aws cognito-idp admin-create-user --user-pool-id USER_POOL_ID --username user@example.com --user-attributes Name=email,Value=user@example.com Name=custom:tenantId,Value=sample-tenant --temporary-password \"TempPass1!\"</code>", | ||
| "Set a permanent password: <code>aws cognito-idp admin-set-user-password --user-pool-id USER_POOL_ID --username user@example.com --password \"MySecurePass1!\" --permanent</code>", | ||
| "Insert a tenant mapping into the DynamoDB table: <code>aws dynamodb put-item --table-name TABLE_NAME --item '{\"tenantId\": {\"S\": \"sample-tenant\"}, \"apiKey\": {\"S\": \"my-api-key-123\"}}'</code>", | ||
| "Get a token and call the API: <code>node get-token.js --user-pool-id USER_POOL_ID --client-id CLIENT_ID --username user@example.com --password \"MySecurePass1!\" --api-url https://REPLACE_WITH_API_URL/protected</code>", | ||
| "If successful, you should receive a response: <code>{ \"message\": \"Access granted\" }</code>" | ||
| ] | ||
| }, | ||
| "cleanup": { | ||
| "text": [ | ||
| "Delete the CDK stack: <code>cdk destroy</code>" | ||
| ] | ||
| }, | ||
| "authors": [ | ||
| { | ||
| "name": "Lavanya Tangutur", | ||
| "bio": "Lavanya Tangutur serves as a Senior Technical Account Manager at Amazon Web Services (AWS) focused on helping customers build, deploy, and run secure, resilient, and cost-effective workloads on AWS.", | ||
| "linkedin": "lavanyatangutur" | ||
| } | ||
| ] | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor naming items: the feature is "Lambda authorizer" (lowercase "authorizer") per AWS docs; first references should be "AWS Lambda", "Amazon API Gateway", "Amazon Cognito", "Amazon DynamoDB" (the README mostly does this well). The pattern folder is apigw-APIKey-tenantid-cdk with mixed-case APIKey repo convention is all-lowercase hyphenated slugs (e.g., apigw-apikey-tenantid-cdk).
Heads up that the example-pattern.json repoURL/projectFolder already use the mixed-case folder, so renaming the folder requires updating those too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed