├── dynamo.json.sample ├── event.json.sample ├── .env.sample ├── index.js ├── policyDocument.json.sample ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── package.json ├── lib.js ├── LICENSE └── README.md /dynamo.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "TableName" : "Users", 3 | "Item" : { 4 | "userId" : "" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /event.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "type" : "TOKEN", 3 | "authorizationToken" : "Bearer ", 4 | "methodArn":"arn:aws:execute-api:us-east-1:1234567890:apiId/stage/method/resourcePath" 5 | } 6 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # copy this file to .env and fill in the 2 | AUTH0_DOMAIN=.auth0.com 3 | AUTH0_DOMAIN_PARAMETER=ssm-parameter-name 4 | AUTH0_CLIENTID= 5 | AUTH0_CLIENTID_PARAMETER=ssm-parameter-name 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var lib = require('./lib'); 4 | 5 | // Lambda function index.handler - thin wrapper around lib.authenticate 6 | module.exports.handler = function( event, context ) { 7 | lib.authenticate( event ) 8 | .then( context.succeed ) 9 | .catch( err => { 10 | if ( ! err ) context.fail( "Unhandled error case" ); 11 | context.fail( err ); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /policyDocument.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1459758003000", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "execute-api:Invoke" 9 | ], 10 | "Resource": [ 11 | "arn:aws:execute-api:::///" 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [8.x, 10.x, 12.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm install, build, and test 21 | run: | 22 | npm ci 23 | npm run build --if-present 24 | npm test 25 | env: 26 | CI: true 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Visual Studio Code 7 | .vscode 8 | 9 | tmp 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directory 32 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 33 | node_modules 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | 41 | # project files that contain sensitive data 42 | .env 43 | event.json 44 | policyDocument.json 45 | *.zip 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-auth0-authenticator", 3 | "version": "0.0.2", 4 | "description": "An AWS Lambda function to provide an Auth0 Custom Authenticator for AWS API Gateway", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/lambda-local --timeout 300 --lambdapath index.js --eventpath event.json", 8 | "zip": "rm -f lambda-auth0-authorizer.zip ; zip lambda-auth0-authorizer.zip -r *.js *.json .env node_modules/" 9 | }, 10 | "author": "Jason Haines", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "auth0": "^4.1.0", 14 | "bluebird": "^3.4.6", 15 | "dotenv": "^2.0.0" 16 | }, 17 | "devDependencies": { 18 | "aws-sdk": "^2.1354.0", 19 | "lambda-local": "^1.6.3" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/jghaines/lambda-auth0-authorizer.git" 24 | }, 25 | "keywords": [ 26 | "aws", 27 | "api-gateway", 28 | "auth0", 29 | "custom-authorizer", 30 | "authentication", 31 | "lambda" 32 | ], 33 | "bugs": { 34 | "url": "https://github.com/jghaines/lambda-auth0-authorizer/issues" 35 | }, 36 | "homepage": "https://github.com/jghaines/lambda-auth0-authorizer#readme" 37 | } 38 | -------------------------------------------------------------------------------- /lib.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // static setup that can be done at load-time 4 | 5 | var ACCESS_TOKEN_LENGTH = 16; // (apparent) length of an Autho0 access_token 6 | 7 | // Lambda now supports environment variables - http://docs.aws.amazon.com/lambda/latest/dg/tutorial-env_cli.html 8 | // a .env file can be used as a development convenience. Real environment variables can be used in deployment and 9 | // will override anything loaded by dotenv. 10 | require('dotenv').config(); 11 | 12 | var fs = require('fs'); 13 | var Promise = require('bluebird'); 14 | Promise.longStackTraces(); 15 | 16 | var AWS = require('aws-sdk'); 17 | AWS.config.apiVersions = { dynamodb: '2012-08-10' }; 18 | if ( process.env.AWS_REGION ) { 19 | AWS.config.update( { region: process.env.AWS_REGION } ); 20 | } 21 | var dynamo = new AWS.DynamoDB.DocumentClient(); 22 | var ssm = new AWS.SSM(); 23 | Promise.promisifyAll( Object.getPrototypeOf( dynamo )); 24 | Promise.promisifyAll( Object.getPrototypeOf( ssm )); 25 | 26 | 27 | ///// TODO : use promises to load these asynchronously 28 | ///// return Promise.resolve to return cached values 29 | ///// see : http://bluebirdjs.com/docs/api/promise.method.html 30 | 31 | 32 | 33 | var policyDocumentFilename = 'policyDocument.json'; 34 | var policyDocument; 35 | try { 36 | policyDocument = JSON.parse(fs.readFileSync( __dirname + '/' + policyDocumentFilename, 'utf8')); 37 | } catch (e) { 38 | if (e.code === 'ENOENT') { 39 | console.error('Expected ' + policyDocumentFilename + ' to be included in Lambda deployment package'); 40 | // fallthrough 41 | } 42 | throw e; 43 | } 44 | 45 | var dynamoParametersFilename = 'dynamo.json'; 46 | var dynamoParameters = null; 47 | try { 48 | dynamoParameters = JSON.parse(fs.readFileSync( __dirname + '/' + dynamoParametersFilename, 'utf8')); 49 | } catch (e) { 50 | if (e.code !== 'ENOENT') { 51 | throw e; 52 | } 53 | // otherwise fallthrough 54 | } 55 | 56 | var AuthenticationClient = require('auth0').AuthenticationClient; 57 | 58 | var testClientOptions = function( domain, clientId ) { 59 | if ( typeof domain === "undefined" || ! domain.match( /\.auth0\.com$/ ) ) { 60 | throw new Error( "Expected AUTHO_DOMAIN or AUTH0_DOMAIN_PARAMETER environment variable to be set in .env file. See https://manage.auth0.com/#/applications" ) 61 | } 62 | 63 | if ( typeof clientId === "undefined" || clientId.length === 0 ) { 64 | throw new Error( "Expected AUTH0_CLIENTID or AUTH0_CLIENTID_PARAMETER environment variable to be set in .env file. See https://manage.auth0.com/#/applications" ) 65 | } 66 | } 67 | 68 | var getClient = function() { 69 | if ( process.env.AUTH0_DOMAIN && process.env.AUTH0_DOMAIN_PARAMETER ) { 70 | throw new Error( "Expected only one of AUTH0_DOMAIN and AUTH0_DOMAIN_PARAMETER environment variable to be set in .env file." ) 71 | } 72 | if ( process.env.AUTH0_CLIENTID && process.env.AUTH0_CLIENTID_PARAMETER ) { 73 | throw new Error( "Expected only one of AUTH0_CLIENTID and AUTH0_CLIENTID_PARAMETER environment variable to be set in .env file." ) 74 | } 75 | 76 | var domain, clientId 77 | var promise = Promise.resolve() 78 | 79 | if ( process.env.AUTH0_DOMAIN_PARAMETER ) { 80 | promise = Promise.all( [ 81 | promise, 82 | ssm.getParameterAsync( { Name: process.env.AUTH0_DOMAIN_PARAMETER, WithDecryption: true } ) 83 | .then( (data) => { 84 | domain = data.Parameter.Value 85 | } ) 86 | ] ) 87 | } else { 88 | domain = process.env.AUTH0_DOMAIN 89 | } 90 | 91 | if ( process.env.AUTH0_CLIENTID_PARAMETER ) { 92 | promise = Promise.all( [ 93 | promise, 94 | ssm.getParameterAsync( { Name: process.env.AUTH0_CLIENTID_PARAMETER, WithDecryption: true } ) 95 | .then( (data) => { 96 | clientId = data.Parameter.Value 97 | } ) 98 | ] ) 99 | } else { 100 | clientId = process.env.AUTH0_CLIENTID 101 | } 102 | 103 | return promise.then( () => { 104 | testClientOptions( domain, clientId ) 105 | return new AuthenticationClient( { 106 | domain : domain, 107 | clientId : clientId 108 | } ) 109 | } ) 110 | } 111 | 112 | 113 | // extract and return the Bearer Token from the Lambda event parameters 114 | var getToken = function( params ) { 115 | if ( ! params.type || params.type !== 'TOKEN' ) { 116 | throw new Error( "Expected 'event.type' parameter to have value TOKEN" ); 117 | } 118 | 119 | var tokenString = params.authorizationToken; 120 | if ( !tokenString ) { 121 | throw new Error( "Expected 'event.authorizationToken' parameter to be set" ); 122 | } 123 | 124 | var match = tokenString.match( /^Bearer (.*)$/ ); 125 | if ( ! match || match.length < 2 ) { 126 | throw new Error( "Invalid Authorization token - '" + tokenString + "' does not match 'Bearer .*'" ); 127 | } 128 | return match[1]; 129 | } 130 | 131 | var returnAuth0UserInfo = function( auth0return ) { 132 | if ( ! auth0return ) throw new Error( 'Auth0 empty return' ); 133 | if ( auth0return === 'Unauthorized') { 134 | throw new Error( 'Auth0 reports Unauthorized' ) 135 | } 136 | 137 | return auth0return 138 | } 139 | 140 | // if dynamo.json is included in the package, save the userInfo to DynamoDB 141 | var saveUserInfo = function( userInfo ) { 142 | if ( ! userInfo ) throw new Error( 'saveUserInfo - expected userInfo parameter' ); 143 | if ( ! userInfo.user_id ) throw new Error( 'saveUserInfo - expected userInfo.user_id parameter' ); 144 | 145 | if ( dynamoParameters ) { 146 | var putParams = Object.assign({}, dynamoParameters); 147 | var hashkeyName = Object.keys( putParams.Item )[0]; 148 | putParams.Item = userInfo; 149 | putParams.Item[ hashkeyName ] = userInfo.user_id; 150 | return dynamo.putAsync( putParams ) 151 | .then( () => userInfo ); 152 | } else { 153 | return userInfo; 154 | } 155 | 156 | } 157 | 158 | // extract user_id from the autho0 userInfo and return it for AWS principalId 159 | var getPrincipalId = function( userInfo ) { 160 | if ( ! userInfo || ! userInfo.user_id ) { 161 | throw new Error( "No user_id returned from Auth0" ); 162 | } 163 | console.log( 'Auth0 authentication successful for user_id ' + userInfo.user_id ); 164 | 165 | return userInfo.user_id; 166 | } 167 | 168 | // return the expected Custom Authorizaer JSON object 169 | var getAuthentication = function( principalId ) { 170 | return { 171 | principalId : principalId, 172 | policyDocument : policyDocument 173 | } 174 | } 175 | 176 | module.exports.authenticate = function (params) { 177 | return getClient() 178 | .then( ( auth0 ) => { 179 | var token = getToken(params); 180 | 181 | if ( token.length === ACCESS_TOKEN_LENGTH ) { // Auth0 v1 access_token (deprecated) 182 | return auth0.users.getInfo( token ); 183 | } else if ( token.length > ACCESS_TOKEN_LENGTH ) { // (probably) Auth0 id_token 184 | return auth0.tokens.getInfo( token ); 185 | } else { 186 | throw new TypeError( "Bearer token too short - expected >= 16 charaters" ); 187 | } 188 | } ) 189 | .then( returnAuth0UserInfo ) 190 | .then( saveUserInfo ) 191 | .then( getPrincipalId ) 192 | .then( getAuthentication ); 193 | } 194 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda-auth0-authorizer 2 | 3 | An AWS Custom Authorizer for AWS API Gateway that support Auth0 JWT Bearer tokens. 4 | 5 | > **⚠⚠ WARNING ⚠⚠** 6 | > This project is no longer supported. 7 | > 8 | > For many use cases, you are better off using the AWS HTTP API Gateway with native [JWT authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html) instead. See the Auth0 blog post [Securing AWS HTTP APIs with JWT Authorizers](https://auth0.com/blog/securing-aws-http-apis-with-jwt-authorizers/#Create-an-HTTP-API-on-AWS) (Oct 2021) 9 | > 10 | > If you insist on using the AWS REST API Gateway, consider one of the alternatives below 11 | 12 | ## Alternatives to lambda-auth0-authorizer 13 | 14 | The fine folks at Auth0 (somewhat sneakily) [forked this repo](https://github.com/auth0-samples/jwt-rsa-aws-custom-authorizer) to: 15 | 16 | * 17 | 18 | You can find it referenced in their documentation [Secure AWS API Gateway Endpoints Using Custom Authorizers](https://auth0.com/docs/customize/integrations/aws/aws-api-gateway-custom-authorizers). 19 | 20 | Auth0 uses JWTs. There are several Custom Authorizers for JWTs: 21 | 22 | * 23 | * 24 | 25 | 26 | ## About 27 | 28 | ### What is AWS API Gateway? 29 | API Gateway is an AWS service that allows for the definition, configuration and deployment of REST API interfaces. 30 | These interfaces can connect to a number of backend systems. 31 | One popular use case is to provide an interface to AWS Lambda functions to deliver a so-called 'serverless' architecture. 32 | 33 | ### What are Custom Authorizers? 34 | In February 2016 Amazon 35 | [announced](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) 36 | a new feature for API Gateway - 37 | [Custom Authorizers](http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html). 38 | 39 | This allows a Lambda function to be invoked prior to an API Gateway execution to perform authentication and authorization of the request and caching of the result. 40 | This code can then be isolated to a single function rather than replicated across every backend Lambda function. 41 | 42 | ### What is Auth0? 43 | Auth0 is a 3rd party single-sign on service that provides single sign-on services, abstracting various login and identity services. 44 | Auth0 offers a number of SDKs as well as integrations with AWS. 45 | 46 | ### What is lambda-auth0-authorizer? 47 | 48 | This package gives you the code for a Custom Authorizer that will, with a little configuration, perform Auth0 authentication on API Gateway requests. 49 | 50 | 51 | 52 | ## Configuration 53 | 54 | ### node modules 55 | 56 | Run `npm install` to download all the dependent modules. This is a prerequisite for deployment as AWS Lambda requires these files to be included in the bundle. 57 | 58 | ### .env 59 | 60 | Copy .env.sample to .env 61 | 62 | Values specified in this file will set the corresponding environment variables. 63 | 64 | You will need to set: 65 | 66 | AUTH0_DOMAIN=mydomain.auth0.com 67 | # or: 68 | AUTH0_DOMAIN_PARAMETER=ssm-parameter-name 69 | 70 | AUTH0_CLIENTID=MyClientId 71 | # or: 72 | AUTH0_CLIENTID_PARAMETER=ssm-parameter-name 73 | 74 | You can obtain these values from your Auth0 [Application settings](https://manage.auth0.com/#/applications). 75 | 76 | ### policyDocument.json 77 | 78 | Copy policyDocument.json.sample to policyDocument.json 79 | 80 | This AWS Policy document is returned by the authorizer and is the permission granted to the invoke of the API Gateway. 81 | 82 | You will need to edit it to give sufficient access for all the API Gateway functions it will use 83 | 84 | The general form an API Gateway ARN is: 85 | 86 | "arn:aws:execute-api:::///" 87 | 88 | To grant access to ALL your API Gateways you can use: 89 | 90 | "arn:aws:execute-api:*" 91 | 92 | ### dynamo.json 93 | 94 | lambda-auth0-autorizer can optionally store the auth0 user info into an AWS DynamoDB table. 95 | 96 | To do this copy dynamo.json.sample to dynamo.json. If present in the deployment bundle, it will be used as the template for the DynamoDoc Put operation. 97 | 98 | You should only change: 99 | * The value for "TableName", e.g. "Users" 100 | * The key of the first "Items" value, which is the HashKey of that table, e.g. "userId" 101 | 102 | ## Local testing 103 | 104 | ### Bearer token 105 | 106 | You will need to obtain a test Bearer Token. 107 | This is the [id_token](https://auth0.com/docs/tokens/id_token) that is provided by a successful Auth0 authentication. 108 | The id_token is valid for 10 hours (36000 seconds) by default. 109 | 110 | You can check your id_token by passing it to Auth0's API test interface: 111 | https://auth0.com/docs/auth-api#post--tokeninfo 112 | 113 | lamda-auth0-authorizer also will also auto-detect and support Auth0's depcreated 16-character v1 [access_token](https://auth0.com/docs/tokens/access_token). 114 | 115 | ### event.json 116 | 117 | Copy event.json.sample to event.json. Provide the id_token from the previous step. 118 | 119 | "authorizationToken" : "Bearer ", 120 | 121 | ### Local DynamoDB connection testing 122 | 123 | If you are using the DynamoDB configuration described above, check that your local environment is configured for DynamoDB 124 | 125 | Using the [AWS CLI](https://aws.amazon.com/cli/), run the following command with the you configured in dynamo.json. 126 | 127 | $ aws dynamodb describe-table --table-name 128 | 129 | The result should run without error and show the same value for TableName.KeySchema.AttributeName which the table key configured in dynamo.json. 130 | 131 | ### lambda-local 132 | 133 | Run `npm test` to use lambda-local test harness 134 | 135 | A successful run will look something like: 136 | 137 | $ npm test 138 | 139 | > lambda-auth0-authenticator@0.0.1 test ~/lambda-auth0-authorizer 140 | > lambda-local --timeout 300 --lambdapath index.js --eventpath event.json 141 | 142 | Logs 143 | ---- 144 | START RequestId: bcb21d17-c3f8-2299-58d9-0400adcfe921 145 | Auth0 authentication successful for user_id oauth|1234567890 146 | END 147 | 148 | 149 | Message 150 | ------ 151 | { 152 | "principalId": "oauth|1234567890", 153 | "policyDocument": { 154 | "Version": "2012-10-17", 155 | "Statement": [ 156 | { 157 | "Sid": "Stmt1459758003000", 158 | "Effect": "Allow", 159 | "Action": [ 160 | "execute-api:Invoke" 161 | ], 162 | "Resource": [ 163 | "arn:aws:execute-api:*" 164 | ] 165 | } 166 | ] 167 | } 168 | } 169 | 170 | The Message is the authorization data that the Lambda function returns to API Gateway. 171 | 172 | ## Deployment 173 | 174 | ### Create bundle 175 | 176 | You can create the bundle using `npm run zip`. This creates a lambda-auth0-authorizer.zip deployment package with all the source, configuration and node modules AWS Lambda needs. 177 | 178 | ### Create Lambda function 179 | 180 | From the AWS console https://console.aws.amazon.com/lambda/home#/create?step=2 181 | 182 | * Name : auth0_authorizer 183 | * Description: Auth0 authorizer for API Gateway 184 | * Runtime: Node.js 4.3 185 | * Code entry type: Upload a .ZIP file 186 | * Upload : < select lambda-auth0-authorizer.zip we created in the previous step > 187 | * Handler : index.handler 188 | * Role : Basic with DynamoDB 189 | * If you aren't using DynamoDB (see above), you can also pick Basic execution role 190 | * Memory (MB) : 128 191 | * Timeout : 30 seconds 192 | * VPC : No VPC 193 | 194 | Click Next and Create 195 | 196 | #### Testing Lambda 197 | 198 | In the Lambda console, select Actions -> Configure Test event. 199 | 200 | Use the following JSON as the test event data. The id_token is the same format we used in event.json above. Click Save and Test to run the Lambda. 201 | 202 | { 203 | "type": "TOKEN", 204 | "authorizationToken": "Bearer ", 205 | "methodArn":"arn:aws:execute-api:us-east-1:1234567890:apiId/stage/method/resourcePath" 206 | } 207 | 208 | ### Create IAM Role 209 | 210 | You will need to create an IAM Role that has permissions to invoke the Lambda function we created above. 211 | 212 | That Role will need to have a Policy similar to the following: 213 | 214 | { 215 | "Version": "2012-10-17", 216 | "Statement": [ 217 | { 218 | "Effect": "Allow", 219 | "Resource": [ 220 | "*" 221 | ], 222 | "Action": [ 223 | "lambda:InvokeFunction" 224 | ] 225 | } 226 | ] 227 | } 228 | 229 | You will also need to set a "Trust Relationship for the role". This will allow the API Gateway permission to assume the role and run the lambda function. The trust relationship can be set in a separate tab in the AWS console. Click the "Edit Trust Relationship button". It should look similar to the following: 230 | 231 | { 232 | "Version": "2012-10-17", 233 | "Statement": [ 234 | { 235 | "Effect": "Allow", 236 | "Principal": { 237 | "Service": [ 238 | "apigateway.amazonaws.com", 239 | "lambda.amazonaws.com" 240 | ] 241 | }, 242 | "Action": "sts:AssumeRole" 243 | } 244 | ] 245 | } 246 | 247 | 248 | ### Configure API Gateway 249 | 250 | From the AWS console https://console.aws.amazon.com/apigateway/home 251 | 252 | Open your API, or Create a new one. 253 | 254 | In the left panel, under your API name, click on **Custom Authorizers**. Click on **Create** 255 | 256 | * Name : auth0_authorizer 257 | * Lambda region : < from previous step > 258 | * Execution role : < the ARN of the Role we created in the previous step > 259 | * Identity token source : method.request.header.Authorization 260 | * Token validation expression : ```^Bearer [-0-9a-zA-z\.]*$``` 261 | ** Cut-and-paste this regular expression from ^ to $ inclusive 262 | * Result TTL in seconds : 3600 263 | 264 | Click **Create** 265 | 266 | ### Testing 267 | 268 | You can test the authorizer by supplying an Identity token and clicking **Test** 269 | 270 | The id_token is the same format we used in event.json above. 271 | 272 | Bearer 273 | 274 | A successful test will look something like: 275 | 276 | Latency: 2270 ms 277 | Principal Id: oauth|1234567890 278 | Policy 279 | { 280 | "Version": "2012-10-17", 281 | "Statement": [ 282 | { 283 | "Sid": "Stmt1459758003000", 284 | "Effect": "Allow", 285 | "Action": [ 286 | "execute-api:Invoke" 287 | ], 288 | "Resource": [ 289 | "arn:aws:execute-api:*" 290 | ] 291 | } 292 | ] 293 | } 294 | 295 | ### Configure API Gateway Methods to use the Authorizer 296 | 297 | In the left panel, under your API name, click on **Resources**. 298 | Under the Resource tree, select one of your Methods (POST, GET etc.) 299 | 300 | Select **Method Request**. Under **Authorization Settings** change: 301 | 302 | * Authorizer : auth0_authorizer 303 | 304 | Make sure that: 305 | 306 | * API Key Required : false 307 | 308 | Click the tick to save the changes. 309 | 310 | ### Deploy the API 311 | 312 | You need to Deploy the API to make the changes public. 313 | 314 | Select **Action** and **Deploy API**. Select your **Stage**. 315 | 316 | ### Test your endpoint remotely 317 | 318 | #### With Postman 319 | 320 | You can use Postman to test the REST API 321 | 322 | * Method: < matching the Method in API Gateway > 323 | * URL `https://.execute-api..amazonaws.com//` 324 | * The base URL you can see in the Stages section of the API 325 | * Append the Resource name to get the full URL 326 | * Header - add an Authorization key 327 | * Authorization : Bearer 328 | 329 | #### With curl from the command line 330 | 331 | $ curl -X POST -H 'Authorization: Bearer ' 332 | 333 | #### In (modern) browsers console with fetch 334 | 335 | fetch( '', { method: 'POST', headers: { Authorization : 'Bearer ' }}).then(response => { console.log( response );}); 336 | 337 | --------------------------------------------------------------------------------