Skip to content

Commit

Permalink
feat(apigatewayv2): http api - lambda authorizer (aws#13181)
Browse files Browse the repository at this point in the history
Second part of aws#10534

Had to make small changes to `authorizerType` that route expects, since Route and Authorizer enums are not the same. See aws#10534 (comment). 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
iRoachie authored and hollanddd committed Aug 26, 2021
1 parent 3a42614 commit 1a52c9e
Show file tree
Hide file tree
Showing 16 changed files with 971 additions and 15 deletions.
30 changes: 30 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- [Route Authorization](#route-authorization)
- [JWT Authorizers](#jwt-authorizers)
- [User Pool Authorizer](#user-pool-authorizer)
- [Lambda Authorizers](#lambda-authorizers)

## Introduction

Expand Down Expand Up @@ -162,3 +163,32 @@ api.addRoutes({
authorizer,
});
```

## Lambda Authorizers

Lambda authorizers use a Lambda function to control access to your HTTP API. When a client calls your API, API Gateway invokes your Lambda function and uses the response to determine whether the client can access your API.

Lambda authorizers depending on their response, fall into either two types - Simple or IAM. You can learn about differences [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response).


```ts
// This function handles your auth logic
const authHandler = new Function(this, 'auth-function', {
//...
});

const authorizer = new HttpLambdaAuthorizer({
responseTypes: [HttpLambdaAuthorizerType.SIMPLE] // Define if returns simple and/or iam response
handler: authHandler,
});

const api = new HttpApi(stack, 'HttpApi');

api.addRoutes({
integration: new HttpProxyIntegration({
url: 'https://get-books-proxy.myproxy.internal',
}),
path: '/books',
authorizer,
});
```
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './user-pool';
export * from './jwt';
export * from './jwt';
export * from './lambda';
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class HttpJwtAuthorizer implements IHttpRouteAuthorizer {

return {
authorizerId: this.authorizer.authorizerId,
authorizationType: HttpAuthorizerType.JWT,
authorizationType: 'JWT',
};
}
}
130 changes: 130 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/lambda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
HttpAuthorizer,
HttpAuthorizerType,
HttpRouteAuthorizerBindOptions,
HttpRouteAuthorizerConfig,
IHttpRouteAuthorizer,
AuthorizerPayloadVersion,
IHttpApi,
} from '@aws-cdk/aws-apigatewayv2';
import { ServicePrincipal } from '@aws-cdk/aws-iam';
import { IFunction } from '@aws-cdk/aws-lambda';
import { Stack, Duration, Names } from '@aws-cdk/core';

// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct as CoreConstruct } from '@aws-cdk/core';

/**
* Specifies the type responses the lambda returns
*/
export enum HttpLambdaResponseType {
/** Returns simple boolean response */
SIMPLE,

/** Returns an IAM Policy */
IAM,
}

/**
* Properties to initialize HttpTokenAuthorizer.
*/
export interface HttpLambdaAuthorizerProps {

/**
* The name of the authorizer
*/
readonly authorizerName: string;

/**
* The identity source for which authorization is requested.
*
* @default ['$request.header.Authorization']
*/
readonly identitySource?: string[];

/**
* The lambda function used for authorization
*/
readonly handler: IFunction;

/**
* How long APIGateway should cache the results. Max 1 hour.
* Disable caching by setting this to `Duration.seconds(0)`.
*
* @default Duration.minutes(5)
*/
readonly resultsCacheTtl?: Duration;

/**
* The types of responses the lambda can return
*
* If HttpLambdaResponseType.SIMPLE is included then
* response format 2.0 will be used.
*
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response
*
* @default [HttpLambdaResponseType.IAM]
*/
readonly responseTypes?: HttpLambdaResponseType[];
}

/**
* Authorize Http Api routes via a lambda function
*/
export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;
private httpApi?: IHttpApi;

constructor(private readonly props: HttpLambdaAuthorizerProps) {
}

public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
if (this.httpApi && (this.httpApi.apiId !== options.route.httpApi.apiId)) {
throw new Error('Cannot attach the same authorizer to multiple Apis');
}

if (!this.authorizer) {
const id = this.props.authorizerName;

const responseTypes = this.props.responseTypes ?? [HttpLambdaResponseType.IAM];
const enableSimpleResponses = responseTypes.includes(HttpLambdaResponseType.SIMPLE) || undefined;

this.httpApi = options.route.httpApi;
this.authorizer = new HttpAuthorizer(options.scope, id, {
httpApi: options.route.httpApi,
identitySource: this.props.identitySource ?? [
'$request.header.Authorization',
],
type: HttpAuthorizerType.LAMBDA,
authorizerName: this.props.authorizerName,
enableSimpleResponses,
payloadFormatVersion: enableSimpleResponses ? AuthorizerPayloadVersion.VERSION_2_0 : AuthorizerPayloadVersion.VERSION_1_0,
authorizerUri: lambdaAuthorizerArn(this.props.handler),
resultsCacheTtl: this.props.resultsCacheTtl ?? Duration.minutes(5),
});

this.props.handler.addPermission(`${Names.nodeUniqueId(this.authorizer.node)}-Permission`, {
scope: options.scope as CoreConstruct,
principal: new ServicePrincipal('apigateway.amazonaws.com'),
sourceArn: Stack.of(options.route).formatArn({
service: 'execute-api',
resource: options.route.httpApi.apiId,
resourceName: `authorizers/${this.authorizer.authorizerId}`,
}),
});
}

return {
authorizerId: this.authorizer.authorizerId,
authorizationType: 'CUSTOM',
};
}
}

/**
* constructs the authorizerURIArn.
*/
function lambdaAuthorizerArn(handler: IFunction) {
return `arn:${Stack.of(handler).partition}:apigateway:${Stack.of(handler).region}:lambda:path/2015-03-31/functions/${handler.functionArn}/invocations`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer {

return {
authorizerId: this.authorizer.authorizerId,
authorizationType: HttpAuthorizerType.JWT,
authorizationType: 'JWT',
};
}
}
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,16 @@
"dependencies": {
"@aws-cdk/aws-apigatewayv2": "0.0.0",
"@aws-cdk/aws-cognito": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
"peerDependencies": {
"@aws-cdk/aws-apigatewayv2": "0.0.0",
"@aws-cdk/aws-cognito": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/core": "0.0.0",
"constructs": "^3.3.69"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable no-console */

export const handler = async (event: AWSLambda.APIGatewayProxyEventV2) => {
const key = event.headers['x-api-key'];

return {
isAuthorized: key === '123',
};
};
Loading

0 comments on commit 1a52c9e

Please sign in to comment.