├── .env.template ├── .gitignore ├── .npmignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── aws-cdk-amazon-api-gateway-jwt.ts ├── cdk.json ├── jest.config.js ├── lambda └── auth-check.js ├── lib └── aws-cdk-amazon-api-gateway-jwt-stack.ts ├── package-lock.json ├── package.json └── tsconfig.json /.env.template: -------------------------------------------------------------------------------- 1 | # 2 | # Set your issuer here. 3 | # In the case of Auth0, it's looks like 'https://[Your Domain]' 4 | # In the case of Cognito, it's looks like 'https://cognito-idp.[Region].amazonaws.com/[Pool ID]' 5 | # 6 | JWT_ISSUER=[REPLACE ME] 7 | 8 | # 9 | # Set your audiences here. 10 | # You can set multiple audiences with ',' delimiters. 11 | # In the case of Cognito, you should set an app client ID. 12 | # In the case of Auth0, you should set a client ID. 13 | # 14 | JWT_AUDIENCE=[REPLACE ME] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | 10 | # Parcel build directories 11 | .cache 12 | .build 13 | 14 | # dotenv file 15 | .env 16 | 17 | # lambda files 18 | !/lambda/*.js 19 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## aws-cdk-amazon-api-gateway-jwt 2 | 3 | In this repository, we show a way to implement JWT authorizer for API Gateway (HTTP API) with CDK. 4 | 5 | This repository would be good example for lower level (CloudFormation level) implementation in CDK as well. 6 | 7 | See [aws-cdk-amazon-api-gateway-jwt-stack.ts](/lib/aws-cdk-amazon-api-gateway-jwt-stack.ts) for details. 8 | 9 | ## Prerequirements 10 | - CLI of CDK ([Installtion guide](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html)) 11 | - JWT issuer and audiences 12 | - **Cognito** You need a user pool and an app client 13 | - **Auth0** You need an application 14 | 15 | ## Create `.env` file 16 | The stack reads an **issuer** and **audiences** from `.env` file. 17 | You can find a template of it at [`.env.template`](/.env.template). (Copy the file and replace the values!) 18 | 19 | ```bash 20 | mv .env.template .env 21 | ``` 22 | 23 | ## Deploy 24 | 25 | ```bash 26 | # Install dependencies 27 | npm install 28 | 29 | # Deployment 30 | cdk deploy 31 | ``` 32 | 33 | ## Check authentication 34 | 35 | Once you deploy the stack, it creates API Gateway with `/auth-check` route. 36 | 37 | First, let's confirm that the api is protected. 38 | 39 | ```bash 40 | curl https://[Your API Gateway ID].execute-api.[Your Region].amazonaws.com/auth-check 41 | ``` 42 | 43 | It returns `{"message":"Unauthorized"}`. 44 | 45 | After getting id token, execute the below and confirm that it returns `{"auth": "ok"}` 46 | 47 | ```bash 48 | curl -H "Authorization: Bearer [Your ID Token]" https://[Your API Gateway ID].execute-api.[Your Region].amazonaws.com/auth-check 49 | ``` 50 | 51 | ## Tips 52 | 53 | To get id token from Cognito, execute the below. 54 | ```bash 55 | aws cognito-idp admin-initiate-auth \ 56 | --user-pool-id [Your User Pool ID] \ 57 | --client-id [Your App Client ID] \ 58 | --auth-flow ADMIN_NO_SRP_AUTH \ 59 | --auth-parameters "USERNAME=[Username],PASSWORD=[Password]" \ 60 | --query "AuthenticationResult.IdToken" 61 | ``` 62 | 63 | ## Cleanup 64 | ```bash 65 | cdk destroy 66 | ``` 67 | 68 | ## Security 69 | 70 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 71 | 72 | ## License 73 | 74 | This library is licensed under the MIT-0 License. See the LICENSE file. 75 | -------------------------------------------------------------------------------- /bin/aws-cdk-amazon-api-gateway-jwt.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import { AwsCdkAmazonApiGatewayJwtStack } from '../lib/aws-cdk-amazon-api-gateway-jwt-stack'; 5 | 6 | const app = new cdk.App(); 7 | new AwsCdkAmazonApiGatewayJwtStack(app, 'AwsCdkAmazonApiGatewayJwtStack'); 8 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node bin/aws-cdk-amazon-api-gateway-jwt.ts", 3 | "context": { 4 | "@aws-cdk/core:enableStackNameDuplicates": "true", 5 | "aws-cdk:enableDiffNoFail": "true" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/test'], 3 | testMatch: ['**/*.test.ts'], 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest' 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /lambda/auth-check.js: -------------------------------------------------------------------------------- 1 | // 2 | // This is a quite simple lambda function. 3 | // Since we check the authentication in API Gateway layer, this function just returns the success message. 4 | // 5 | exports.handler = async (event) => { 6 | return { 7 | statusCode: 200, 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | 'Access-Control-Allow-Origin': '*', 11 | }, 12 | body: JSON.stringify({ auth: 'ok' }), 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/aws-cdk-amazon-api-gateway-jwt-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import * as apigateway from '@aws-cdk/aws-apigatewayv2'; 3 | import * as iam from '@aws-cdk/aws-iam'; 4 | import * as lambda from '@aws-cdk/aws-lambda'; 5 | import * as dotenv from 'dotenv'; 6 | 7 | export class AwsCdkAmazonApiGatewayJwtStack extends cdk.Stack { 8 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 9 | super(scope, id, props); 10 | 11 | // Load environment variables from dotenv files 12 | dotenv.config(); 13 | 14 | // Check environment variables 15 | this.validation(); 16 | const issuer = process.env.JWT_ISSUER!; 17 | const audience = process.env.JWT_AUDIENCE!.split(','); 18 | const identitySource = ['$request.header.Authorization']; 19 | 20 | // Create a http api 21 | const httpApi = this.createHttpApi(); 22 | const httpApiAuthCheck = this.createHttpRouteKey('/auth-check', apigateway.HttpMethod.GET); 23 | 24 | // Create a lambda function and it's integration 25 | const authCheckLambda = this.createAuthCheckLambda(httpApi, httpApiAuthCheck); 26 | const authCheckLambdaIntegration = this.createHttpIntegration(httpApi, authCheckLambda); 27 | 28 | // Create an authorizer and a route with the authorizer 29 | const authorizer = this.createAuthorizer(httpApi, issuer, audience, identitySource) 30 | const route = this.createRoute(httpApi, httpApiAuthCheck, authCheckLambdaIntegration, authorizer); 31 | } 32 | 33 | validation() { 34 | if (!process.env.JWT_ISSUER || !process.env.JWT_AUDIENCE) { 35 | console.error('------------------------------[ERROR]------------------------------'); 36 | console.error(' Environment variable JWT_ISSUER and/or JWT_AUDIENCE are not set.'); 37 | console.error(' Check you .env file or you can set it directly.'); 38 | console.error(' > export JWT_ISSUER=[REPLACE ME]'); 39 | console.error(' > export JWT_AUDIENCE=[REPLACE ME]'); 40 | console.error('-------------------------------------------------------------------'); 41 | process.exit(0); 42 | } 43 | } 44 | 45 | createHttpApi() : apigateway.HttpApi { 46 | const httpApi = new apigateway.HttpApi(this, 'aws-cdk-amazon-api-gateway-jwt-api', { 47 | corsPreflight: { 48 | allowHeaders: ['*'], 49 | allowOrigins: ['*'], 50 | allowMethods: [apigateway.HttpMethod.GET], 51 | }, 52 | }); 53 | 54 | return httpApi; 55 | } 56 | 57 | createHttpRouteKey(path: string, method: apigateway.HttpMethod): apigateway.HttpRouteKey { 58 | return apigateway.HttpRouteKey.with(path, method); 59 | } 60 | 61 | createAuthCheckLambda(httpApi: apigateway.HttpApi, httpRouteKey: apigateway.HttpRouteKey) : lambda.Function { 62 | const authCheckLambda = new lambda.Function(this, 'aws-cdk-amazon-api-gateway-jwt-lambda', { 63 | runtime: lambda.Runtime.NODEJS_12_X, 64 | code: lambda.Code.asset('lambda'), 65 | handler: 'auth-check.handler', 66 | }); 67 | 68 | authCheckLambda.addPermission('aws-cdk-amazon-api-gateway-jwt-lambda-permission', { 69 | principal: new iam.ServicePrincipal('apigateway.amazonaws.com'), 70 | sourceArn: cdk.Stack.of(this).formatArn({ 71 | service: 'execute-api', 72 | resource: httpApi.httpApiId, 73 | resourceName: `*/*${httpRouteKey.path ?? ''}`, 74 | }), 75 | }); 76 | 77 | return authCheckLambda; 78 | } 79 | 80 | createHttpIntegration(httpApi: apigateway.HttpApi, handler: lambda.Function) : apigateway.HttpIntegration { 81 | const httpIntegration = new apigateway.HttpIntegration(this, 'aws-cdk-amazon-api-gateway-jwt-integration', { 82 | httpApi, 83 | integrationType: apigateway.HttpIntegrationType.LAMBDA_PROXY, 84 | integrationUri: handler.functionArn, 85 | payloadFormatVersion: apigateway.PayloadFormatVersion.VERSION_2_0, 86 | }); 87 | 88 | return httpIntegration; 89 | } 90 | 91 | createAuthorizer( 92 | httpApi: apigateway.HttpApi, 93 | issuer: string, 94 | audience: string[], 95 | identitySource: string[] 96 | ) : apigateway.CfnAuthorizer { 97 | const authorizer = new apigateway.CfnAuthorizer(this, 'aws-cdk-amazon-api-gateway-jwt-authorizer', { 98 | apiId: httpApi.httpApiId, 99 | authorizerType: 'JWT', 100 | name: 'aws-cdk-amazon-api-gateway-jwt', 101 | identitySource, 102 | jwtConfiguration: { 103 | audience, 104 | issuer, 105 | }, 106 | }); 107 | 108 | return authorizer; 109 | } 110 | 111 | createRoute( 112 | httpApi: apigateway.HttpApi, 113 | httpRouteKey: apigateway.HttpRouteKey, 114 | httpIntegration: apigateway.HttpIntegration, 115 | authorizer: apigateway.CfnAuthorizer) : apigateway.CfnRoute { 116 | const route = new apigateway.CfnRoute(this, 'aws-cdk-amazon-api-gateway-jwt-route', { 117 | apiId: httpApi.httpApiId, 118 | target: `integrations/${httpIntegration.integrationId}`, 119 | routeKey: httpRouteKey.key, 120 | authorizationType: 'JWT', 121 | authorizerId: authorizer.ref, 122 | }); 123 | 124 | return route; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-cdk-amazon-api-gateway-jwt", 3 | "version": "0.1.0", 4 | "bin": { 5 | "aws-cdk-amazon-api-gateway-jwt": "bin/aws-cdk-amazon-api-gateway-jwt" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@aws-cdk/assert": "1.65.0", 15 | "@types/jest": "^25.2.1", 16 | "@types/node": "^10.17.5", 17 | "aws-cdk": "1.65.0", 18 | "jest": "^25.5.0", 19 | "ts-jest": "^25.3.1", 20 | "ts-node": "^8.1.0", 21 | "typescript": "~3.7.2" 22 | }, 23 | "dependencies": { 24 | "@aws-cdk/aws-apigatewayv2": "1.65.0", 25 | "@aws-cdk/aws-iam": "1.65.0", 26 | "@aws-cdk/aws-lambda": "1.65.0", 27 | "@aws-cdk/core": "1.65.0", 28 | "dotenv": "^8.2.0", 29 | "source-map-support": "^0.5.16" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2018"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["cdk.out"] 23 | } 24 | --------------------------------------------------------------------------------