Skip to content

Commit

Permalink
Initial code example for custom authorizer w Auth0 (pulumi#282)
Browse files Browse the repository at this point in the history
* code example for custom authorizer w Auth0
  • Loading branch information
ekrengel committed Apr 23, 2019
1 parent 3935367 commit 6785662
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 0 deletions.
3 changes: 3 additions & 0 deletions aws-ts-apigateway-auth0/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: lambda-authorizer
runtime: nodejs
description: A sample AWS API Gateway example that uses Custom Authorizers
113 changes: 113 additions & 0 deletions aws-ts-apigateway-auth0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Auth0 Protected Serverless REST API on AWS

A simple REST API that is protected by a custom AWS Lambda Authorizer. The Authorizer uses [Auth0](https://auth0.com/) to authorize requests.

This example is similar to Auth0's tutorial: [Secure AWS API Gateway Endpoints Using Custom Authorizers](https://auth0.com/docs/integrations/aws-api-gateway/custom-authorizers), but uses Pulumi to create the Serverless app and Custom Authorizer.

## Set Up Auth0

You can follow the steps below or alternatively you can follow [Auth0's Part 1: Create an Auth0 API](https://auth0.com/docs/integrations/aws-api-gateway/custom-authorizers/part-1).

1. [Sign up](https://auth0.com/signup) for an Auth0 account or login if you already have one.

1. Click on `APIs` in the left-hand menu.

1. Click `Create API`.

* Enter a name and Identifier for you New API.
* Select RS256 as the Signing Algorithm.
* Click `Create`.

1. Under the `Quick Start` tab, the Node.js example will show you the values for `jwksUri`, `audience` and `issuer` you will need in the next section.

## Deploying and Running the Program

1. Create a new stack:

```bash
pulumi stack init auth0-api-testing
```

1. Set the AWS region:

```bash
pulumi config set aws:region us-east-2
```

1. Set up the Auth0 configuration values as secrets in Pulumi:

Run the following commands after replacing `<jwksUri>`, `<audience>` and `<issuer>` with the appropriate values.

```bash
pulumi config set --secret jwksUri <jwksUri>
pulumi config set --secret audience <audience>
pulumi config set --secret issuer <issuer>
```

1. Restore NPM modules via `npm install` or `yarn install`.

1. Run `pulumi up` to preview and deploy changes:

```bash
$ pulumi up
Previewing update (dev):

...

Updating (dev):

Type Name Status Info
+ pulumi:pulumi:Stack lambda-authorizer-dev created 1 message
+ ├─ aws:apigateway:x:API myapi created
+ │ ├─ aws:iam:Role myapi70a45a97 created
+ │ ├─ aws:iam:RolePolicyAttachment myapi70a45a97-32be53a2 created
+ │ ├─ aws:lambda:Function myapi70a45a97 created
+ │ ├─ aws:apigateway:RestApi myapi created
+ │ ├─ aws:apigateway:Deployment myapi created
+ │ ├─ aws:lambda:Permission myapi-31a4e902 created
+ │ └─ aws:apigateway:Stage myapi created
+ ├─ aws:iam:Role jwt-rsa-custom-authorizer created
+ ├─ aws:iam:Role jwt-rsa-custom-authorizer-authorizer-role created
+ ├─ aws:iam:RolePolicyAttachment jwt-rsa-custom-authorizer-32be53a2 created
+ ├─ aws:lambda:Function jwt-rsa-custom-authorizer created
+ └─ aws:iam:RolePolicy jwt-rsa-custom-authorizer-invocation-policy created

Outputs:
url: "https://***.execute-api.us-east-2.amazonaws.com/stage/"

Resources:
+ 14 created

Duration: 18s
```

## Testing Our API

We can now use cURL to test out our new endpoint. If we cURL without a token, we should get a 401 Unauthorized response.

```bash
$ curl $(pulumi stack output url)hello
{"message":"Unauthorized"}
```

We can curl our endpoint with an invalid token and should once again get a 401 Unauthorized response.

```bash
$ curl $(pulumi stack output url)hello -H "Authorization: Bearer invalid"
{"message":"Unauthorized"}
```

Finally, we expect a 200 response when we obtain a token from Auth0 and use it to call our API. We can get a token by visiting the API Details page for our API and clicking the Test tab. Using the provided access token and the API a 200 response: Hello world!

```bash
$ curl $(pulumi stack output url)hello -H "Authorization: Bearer <VALID_TOKEN>"
<h1>Hello world!</h1>
```

## Clean up

1. Run `pulumi destroy` to tear down all resources.

1. To delete the stack itself, run `pulumi stack rm`. Note that this command deletes all deployment history from the Pulumi Console.
121 changes: 121 additions & 0 deletions aws-ts-apigateway-auth0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2016-2019, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http:https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as awsx from "@pulumi/awsx";
import * as pulumi from '@pulumi/pulumi';

import * as jwksClient from 'jwks-rsa';
import * as jwt from 'jsonwebtoken';
import * as util from 'util';

const config = new pulumi.Config();
const jwksUri = config.require("jwksUri");
const audience = config.require("audience");
const issuer = config.require("issuer");

const authorizerLambda = async (event: awsx.apigateway.AuthorizerEvent) => {
try {
return await authenticate(event);
}
catch (err) {
console.log(err);
// Tells API Gateway to return a 401 Unauthorized response
throw new Error("Unauthorized");
}
};

// Create our API and reference the Lambda authorizer
const api = new awsx.apigateway.API("myapi", {
routes: [{
path: "/hello",
method: "GET",
eventHandler: async () => {
return {
statusCode: 200,
body: "<h1>Hello world!</h1>",
};
},
authorizers: awsx.apigateway.getTokenLambdaAuthorizer({
authorizerName: "jwt-rsa-custom-authorizer",
header: "Authorization",
handler: authorizerLambda,
identityValidationExpression: "^Bearer [-0-9a-zA-Z\._]*$",
authorizerResultTtlInSeconds: 3600,
}),
}],
});

// Export the URL for our API
export const url = api.url;

/**
* Below is all code that gets added to the Authorizer Lambda. The code was copied and
* converted to TypeScript from [Auth0's GitHub
* Example](https://github.com/auth0-samples/jwt-rsa-aws-custom-authorizer)
*/

// Extract and return the Bearer Token from the Lambda event parameters
function getToken(event: awsx.apigateway.AuthorizerEvent): string {
if (!event.type || event.type !== 'TOKEN') {
throw new Error('Expected "event.type" parameter to have value "TOKEN"');
}

const tokenString = event.authorizationToken;
if (!tokenString) {
throw new Error('Expected "event.authorizationToken" parameter to be set');
}

const match = tokenString.match(/^Bearer (.*)$/);
if (!match) {
throw new Error(`Invalid Authorization token - ${tokenString} does not match "Bearer .*"`);
}
return match[1];
}

// Check the Token is valid with Auth0
async function authenticate(event: awsx.apigateway.AuthorizerEvent): Promise<awsx.apigateway.AuthorizerResponse> {
console.log(event);
const token = getToken(event);

const decoded = jwt.decode(token, { complete: true });
if (!decoded || typeof decoded === "string" || !decoded.header || !decoded.header.kid) {
throw new Error('invalid token');
}

const client = jwksClient({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 10, // Default value
jwksUri: jwksUri
});

const key = await util.promisify(client.getSigningKey)(decoded.header.kid);
const signingKey = key.publicKey || key.rsaPublicKey;
if (!signingKey) {
throw new Error('could not get signing key');
}

const verifiedJWT = await jwt.verify(token, signingKey, { audience, issuer });
if (!verifiedJWT || typeof verifiedJWT === "string" || !isVerifiedJWT(verifiedJWT)) {
throw new Error('could not verify JWT');
}
return awsx.apigateway.authorizerResponse(verifiedJWT.sub, 'Allow', event.methodArn);
}

interface VerifiedJWT {
sub: string,
}

function isVerifiedJWT(toBeDetermined: VerifiedJWT | Object): toBeDetermined is VerifiedJWT {
return (<VerifiedJWT>toBeDetermined).sub !== undefined;
}
15 changes: 15 additions & 0 deletions aws-ts-apigateway-auth0/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "aws-typescript",
"devDependencies": {
"@types/node": "^11.13.6"
},
"dependencies": {
"@pulumi/aws": "latest",
"@pulumi/awsx": "latest",
"@pulumi/pulumi": "latest",
"@types/auth0-js": "^9.10.1",
"@types/jsonwebtoken": "^8.3.2",
"jsonwebtoken": "^8.5.1",
"jwks-rsa": "^1.4.0"
}
}
22 changes: 22 additions & 0 deletions aws-ts-apigateway-auth0/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"outDir": "bin",
"target": "es6",
"lib": [
"es6"
],
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true
},
"files": [
"index.ts"
]
}

0 comments on commit 6785662

Please sign in to comment.