├── .gitignore ├── README.md ├── handler.ts ├── package.json ├── secrets.json ├── serverless.yml ├── src ├── authorizer.ts └── aws-policy-generator.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .webpack 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This is a an implementation of an AWS custom authorizer for the serverless framework. 4 | 5 | This repository is for my reference. If you have any questions, open an issue and I would be happy to help. 6 | 7 | ## Remember 8 | 9 | 1. Any serverless function using this function as an authorizer must configure the authorizer in the events. 10 | ``` 11 | functions: 12 | hello: 13 | handler: handler.hello 14 | events: 15 | - http: 16 | method: get 17 | cors: true 18 | path: hello 19 | authorizer: arn-to-your-authorizer-lambda 20 | identitySource: method.request.header.Authorization 21 | ``` 22 | 23 | 2. Any serverless function using this function as an authorizer must configure a CORS response in resources. 24 | ``` 25 | resources: 26 | Resources: 27 | GatewayResponse: 28 | Type: 'AWS::ApiGateway::GatewayResponse' 29 | Properties: 30 | ResponseParameters: 31 | gatewayresponse.header.Access-Control-Allow-Origin: "'*'" 32 | gatewayresponse.header.Access-Control-Allow-Headers: "'*'" 33 | ResponseType: EXPIRED_TOKEN 34 | RestApiId: 35 | Ref: 'ApiGatewayRestApi' 36 | StatusCode: '401' 37 | AuthFailureGatewayResponse: 38 | Type: 'AWS::ApiGateway::GatewayResponse' 39 | Properties: 40 | ResponseParameters: 41 | gatewayresponse.header.Access-Control-Allow-Origin: "'*'" 42 | gatewayresponse.header.Access-Control-Allow-Headers: "'*'" 43 | ResponseType: UNAUTHORIZED 44 | RestApiId: 45 | Ref: 'ApiGatewayRestApi' 46 | StatusCode: '401' 47 | ``` 48 | -------------------------------------------------------------------------------- /handler.ts: -------------------------------------------------------------------------------- 1 | import { Authorizer } from './src/authorizer'; 2 | import { AWSPolicyGenerator } from './src/aws-policy-generator'; 3 | 4 | const AUDIENCE = process.env.AUDIENCE; 5 | const JWKS_URI = process.env.JWKS_URI; 6 | const TOKEN_ISSUER = process.env.TOKEN_ISSUER; 7 | 8 | export const authorize = (event, context, cb) => { 9 | try { 10 | const token = event.authorizationToken.substring(7); 11 | const client = new Authorizer(TOKEN_ISSUER, JWKS_URI, AUDIENCE); 12 | 13 | client.authorize(token) 14 | .then((result) => { 15 | cb(null, 16 | AWSPolicyGenerator.generate(result.sub, 'Allow', event.methodArn)); 17 | }) 18 | .catch(err => { 19 | console.log(err); 20 | cb('Unauthorized'); 21 | }); 22 | } catch (err) { 23 | console.log(err); 24 | cb('Unauthorized') 25 | } 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-typescript-auth0-custom-authorizer", 3 | "version": "1.0.0", 4 | "description": "Serverless AWS custom authorizer for Auth0 using typescript", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "devDependencies": { 10 | "@types/es6-promise": "0.0.33", 11 | "@types/node": "^8.0.53", 12 | "serverless-webpack": "^3.0.0", 13 | "ts-loader": "^2.3.7", 14 | "typescript": "^2.5.2", 15 | "webpack": "^3.6.0" 16 | }, 17 | "author": "Mason Meyer", 18 | "license": "MIT", 19 | "dependencies": { 20 | "jsonwebtoken": "^8.1.0", 21 | "jwks-rsa": "^1.2.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "TOKEN_ISSUER": "https://example.auth0.com/", 3 | "JWKS_URI": "https://example.auth0.com/.well-known/jwks.json", 4 | "AUDIENCE": "https://api-audience-in-auth0" 5 | } -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: 2 | name: aws-custom-authorizer-auth0-typescript 3 | plugins: 4 | - serverless-webpack 5 | 6 | provider: 7 | name: aws 8 | runtime: nodejs6.10 9 | environment: 10 | TOKEN_ISSUER: ${file(./secrets.json):TOKEN_ISSUER} 11 | JWKS_URI: ${file(./secrets.json):JWKS_URI} 12 | AUDIENCE: ${file(./secrets.json):AUDIENCE} 13 | functions: 14 | auth: 15 | handler: handler.authorize 16 | cors: true 17 | integration: lambda 18 | -------------------------------------------------------------------------------- /src/authorizer.ts: -------------------------------------------------------------------------------- 1 | import * as jwt from 'jsonwebtoken'; 2 | import * as jwks from 'jwks-rsa'; 3 | 4 | export class Authorizer { 5 | constructor( 6 | private issuer: string, 7 | private jwksUri: string, 8 | private audience: string) { 9 | } 10 | 11 | public authorize(token: string): Promise { 12 | let decoded: any = jwt.decode(token, { complete: true }); 13 | return this.getKey(decoded.header.kid) 14 | .then(x => { 15 | return this.verify(token, x); 16 | }); 17 | } 18 | 19 | private getKey(kid:any): Promise { 20 | const client = jwks({ 21 | strictSsl: true, 22 | jwksUri: this.jwksUri 23 | }); 24 | 25 | return new Promise((resolve, reject) => { 26 | client.getSigningKey(kid, (err, key) => { 27 | if (err) { 28 | reject(err); 29 | } 30 | 31 | resolve(key.publicKey || key.rsaPublicKey); 32 | }); 33 | }); 34 | } 35 | 36 | private verify(token: string, cert: string): Promise<{}> { 37 | const options = { 38 | audience: this.audience 39 | }; 40 | 41 | return new Promise((resolve, reject) => { 42 | jwt.verify(token, cert, options, (err, decoded) => { 43 | if (err) { 44 | reject(err); 45 | } 46 | 47 | resolve(decoded); 48 | }); 49 | }); 50 | } 51 | } -------------------------------------------------------------------------------- /src/aws-policy-generator.ts: -------------------------------------------------------------------------------- 1 | /// AWS Policy Generator creates the proper access to the function. 2 | /// http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html 3 | export class AWSPolicyGenerator { 4 | static generate(principalId: string, effect: string, resource: string, context?: any) : any { 5 | var authResponse: any = {}; 6 | 7 | authResponse.principalId = principalId; 8 | if (effect && resource) { 9 | var policyDocument: any = {}; 10 | policyDocument.Version = '2012-10-17'; 11 | policyDocument.Statement = []; 12 | var statementOne: any = {}; 13 | statementOne.Action = 'execute-api:Invoke'; 14 | statementOne.Effect = effect; 15 | statementOne.Resource = resource; 16 | policyDocument.Statement[0] = statementOne; 17 | authResponse.policyDocument = policyDocument; 18 | } 19 | 20 | if (context) { 21 | authResponse.context = context; 22 | } 23 | 24 | return authResponse; 25 | } 26 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const slsw = require('serverless-webpack'); 3 | 4 | module.exports = { 5 | entry: slsw.lib.entries, 6 | resolve: { 7 | extensions: [ 8 | '.js', 9 | '.jsx', 10 | '.json', 11 | '.ts', 12 | '.tsx' 13 | ] 14 | }, 15 | output: { 16 | libraryTarget: 'commonjs', 17 | path: path.join(__dirname, '.webpack'), 18 | filename: '[name].js', 19 | }, 20 | target: 'node', 21 | module: { 22 | loaders: [ 23 | { test: /\.ts(x?)$/, loader: 'ts-loader' }, 24 | ], 25 | }, 26 | }; 27 | --------------------------------------------------------------------------------