Skip to content

Commit

Permalink
chore(apigatewayv2-authorizers): re-organize authorizer APIs
Browse files Browse the repository at this point in the history
This is a follow on to a previous commit 29039e8.

Update the ergonomics of the authorizer construct APIs to be aligned
with the rest of the module, specifically the integration construct
APIs.

The API now takes the construct id and the integration target as part of
the constructor, instead of in the props class.

In most cases, except in the case of jwt, all properties in the props
struct become optional, which improves API ergonomics.
It also removes the need for `authorizerName` property to be required.
  • Loading branch information
Niranjan Jayakar committed Nov 30, 2021
1 parent 5fc0141 commit 1e87b64
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 93 deletions.
28 changes: 14 additions & 14 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
HttpRouteAuthorizerConfig,
IHttpRouteAuthorizer,
} from '@aws-cdk/aws-apigatewayv2';
import { Token } from '@aws-cdk/core';

/**
* Properties to initialize HttpJwtAuthorizer.
Expand All @@ -14,7 +13,7 @@ export interface HttpJwtAuthorizerProps {

/**
* The name of the authorizer
* @default 'JwtAuthorizer'
* @default - same value as `id` passed in the constructor
*/
readonly authorizerName?: string;

Expand All @@ -30,11 +29,6 @@ export interface HttpJwtAuthorizerProps {
* A valid JWT must provide an aud that matches at least one entry in this list.
*/
readonly jwtAudience: string[]

/**
* The base domain of the identity provider that issues JWT.
*/
readonly jwtIssuer: string;
}

/**
Expand All @@ -44,21 +38,27 @@ export interface HttpJwtAuthorizerProps {
export class HttpJwtAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;

constructor(private readonly props: HttpJwtAuthorizerProps) {
/**
* Initialize a JWT authorizer to be bound with HTTP route.
* @param id The id of the underlying construct
* @param jwtIssuer The base domain of the identity provider that issues JWT
* @param props Properties to configure the authorizer
*/
constructor(
private readonly id: string,
private readonly jwtIssuer: string,
private readonly props: HttpJwtAuthorizerProps) {
}

public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
if (!this.authorizer) {
const id = this.props.authorizerName && !Token.isUnresolved(this.props.authorizerName) ?
this.props.authorizerName : 'JwtAuthorizer';

this.authorizer = new HttpAuthorizer(options.scope, id, {
this.authorizer = new HttpAuthorizer(options.scope, this.id, {
httpApi: options.route.httpApi,
identitySource: this.props.identitySource ?? ['$request.header.Authorization'],
type: HttpAuthorizerType.JWT,
authorizerName: this.props.authorizerName,
authorizerName: this.props.authorizerName ?? this.id,
jwtAudience: this.props.jwtAudience,
jwtIssuer: this.props.jwtIssuer,
jwtIssuer: this.jwtIssuer,
});
}

Expand Down
25 changes: 11 additions & 14 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/lib/http/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ export enum HttpLambdaResponseType {
export interface HttpLambdaAuthorizerProps {

/**
* The name of the authorizer
* Friendly authorizer name
* @default - same value as `id` passed in the constructor.
*/
readonly authorizerName: string;
readonly authorizerName?: string;

/**
* The identity source for which authorization is requested.
Expand All @@ -43,11 +44,6 @@ export interface HttpLambdaAuthorizerProps {
*/
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)`.
Expand Down Expand Up @@ -76,7 +72,10 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;
private httpApi?: IHttpApi;

constructor(private readonly props: HttpLambdaAuthorizerProps) {
constructor(
private readonly id: string,
private readonly handler: IFunction,
private readonly props: HttpLambdaAuthorizerProps = {}) {
}

public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
Expand All @@ -85,26 +84,24 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer {
}

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

this.props.handler.addPermission(`${Names.nodeUniqueId(this.authorizer.node)}-Permission`, {
this.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({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { HttpAuthorizer, HttpAuthorizerType, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, IHttpRouteAuthorizer } from '@aws-cdk/aws-apigatewayv2';
import { IUserPool, IUserPoolClient } from '@aws-cdk/aws-cognito';
import { Stack, Token } from '@aws-cdk/core';
import { Stack } from '@aws-cdk/core';

/**
* Properties to initialize HttpUserPoolAuthorizer.
*/
export interface HttpUserPoolAuthorizerProps {
/**
* The user pool clients that should be used to authorize requests with the user pool.
* @default - a new client will be created for the given user pool
*/
readonly userPoolClients: IUserPoolClient[];

/**
* The associated user pool
*/
readonly userPool: IUserPool;
readonly userPoolClients?: IUserPoolClient[];

/**
* The AWS region in which the user pool is present
Expand All @@ -23,8 +19,8 @@ export interface HttpUserPoolAuthorizerProps {
readonly userPoolRegion?: string;

/**
* The name of the authorizer
* @default 'UserPoolAuthorizer'
* Friendly name of the authorizer
* @default - same value as `id` passed in the constructor
*/
readonly authorizerName?: string;

Expand All @@ -43,21 +39,30 @@ export interface HttpUserPoolAuthorizerProps {
export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;

constructor(private readonly props: HttpUserPoolAuthorizerProps) {
/**
* Initialize a Cognito user pool authorizer to be bound with HTTP route.
* @param id The id of the underlying construct
* @param pool The user pool to use for authorization
* @param props Properties to configure the authorizer
*/
constructor(
private readonly id: string,
private readonly pool: IUserPool,
private readonly props: HttpUserPoolAuthorizerProps = {}) {
}

public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
if (!this.authorizer) {
const id = this.props.authorizerName && !Token.isUnresolved(this.props.authorizerName) ?
this.props.authorizerName : 'UserPoolAuthorizer';
const region = this.props.userPoolRegion ?? Stack.of(options.scope).region;
this.authorizer = new HttpAuthorizer(options.scope, id, {
const clients = this.props.userPoolClients ?? [this.pool.addClient('UserPoolAuthorizerClient')];

this.authorizer = new HttpAuthorizer(options.scope, this.id, {
httpApi: options.route.httpApi,
identitySource: this.props.identitySource ?? ['$request.header.Authorization'],
type: HttpAuthorizerType.JWT,
authorizerName: this.props.authorizerName,
jwtAudience: this.props.userPoolClients.map((c) => c.userPoolClientId),
jwtIssuer: `https://cognito-idp.${region}.amazonaws.com/${this.props.userPool.userPoolId}`,
authorizerName: this.props.authorizerName ?? this.id,
jwtAudience: clients.map((c) => c.userPoolClientId),
jwtIssuer: `https://cognito-idp.${region}.amazonaws.com/${this.pool.userPoolId}`,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"RouteKey": "GET /",
"AuthorizationType": "CUSTOM",
"AuthorizerId": {
"Ref": "MyHttpApimysimpleauthorizer98398C16"
"Ref": "MyHttpApiLambdaAuthorizerB8A0E2A4"
},
"Target": {
"Fn::Join": [
Expand All @@ -94,7 +94,7 @@
}
}
},
"MyHttpApimysimpleauthorizer98398C16": {
"MyHttpApiLambdaAuthorizerB8A0E2A4": {
"Type": "AWS::ApiGatewayV2::Authorizer",
"Properties": {
"ApiId": {
Expand Down Expand Up @@ -133,7 +133,7 @@
]
}
},
"MyHttpApiAuthorizerIntegMyHttpApimysimpleauthorizer0F14A472PermissionF37EF5C8": {
"MyHttpApiAuthorizerIntegMyHttpApiLambdaAuthorizerB89228D7Permission82260331": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
Expand Down Expand Up @@ -166,7 +166,7 @@
},
"/authorizers/",
{
"Ref": "MyHttpApimysimpleauthorizer98398C16"
"Ref": "MyHttpApiLambdaAuthorizerB8A0E2A4"
}
]
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ const authHandler = new lambda.Function(stack, 'auth-function', {
});


const authorizer = new HttpLambdaAuthorizer({
const authorizer = new HttpLambdaAuthorizer('LambdaAuthorizer', authHandler, {
authorizerName: 'my-simple-authorizer',
identitySource: ['$request.header.X-API-Key'],
handler: authHandler,
responseTypes: [HttpLambdaResponseType.SIMPLE],
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"JwtConfiguration": {
"Audience": [
{
"Ref": "userpoolmyclientFAD947AB"
"Ref": "userpoolUserPoolAuthorizerClient6A7486E8"
}
],
"Issuer": {
Expand Down Expand Up @@ -160,7 +160,7 @@
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
},
"userpoolmyclientFAD947AB": {
"userpoolUserPoolAuthorizerClient6A7486E8": {
"Type": "AWS::Cognito::UserPoolClient",
"Properties": {
"UserPoolId": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@ const httpApi = new HttpApi(stack, 'MyHttpApi');

const userPool = new cognito.UserPool(stack, 'userpool');

const userPoolClient = userPool.addClient('my-client');

const authorizer = new HttpUserPoolAuthorizer({
userPool,
userPoolClients: [userPoolClient],
});
const authorizer = new HttpUserPoolAuthorizer('UserPoolAuthorizer', userPool);

const handler = new lambda.Function(stack, 'lambda', {
runtime: lambda.Runtime.NODEJS_12_X,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ describe('HttpJwtAuthorizer', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'HttpApi');

const authorizer = new HttpJwtAuthorizer({
const authorizer = new HttpJwtAuthorizer('BooksAuthorizer', 'https://test.us.auth0.com', {
jwtAudience: ['3131231'],
jwtIssuer: 'https://test.us.auth0.com',
});

// WHEN
Expand All @@ -30,6 +29,7 @@ describe('HttpJwtAuthorizer', () => {
Audience: ['3131231'],
Issuer: 'https://test.us.auth0.com',
},
Name: 'BooksAuthorizer',
});
});

Expand All @@ -38,9 +38,8 @@ describe('HttpJwtAuthorizer', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'HttpApi');

const authorizer = new HttpJwtAuthorizer({
const authorizer = new HttpJwtAuthorizer('BooksAuthorizer', 'https://test.us.auth0.com', {
jwtAudience: ['3131231'],
jwtIssuer: 'https://test.us.auth0.com',
});

// WHEN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ describe('HttpLambdaAuthorizer', () => {
handler: 'index.handler',
});

const authorizer = new HttpLambdaAuthorizer({
authorizerName: 'default-authorizer',
handler,
});
const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler);

// WHEN
api.addRoutes({
Expand All @@ -32,7 +29,7 @@ describe('HttpLambdaAuthorizer', () => {

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Authorizer', {
Name: 'default-authorizer',
Name: 'BooksAuthorizer',
AuthorizerType: 'REQUEST',
AuthorizerResultTtlInSeconds: 300,
AuthorizerPayloadFormatVersion: '1.0',
Expand All @@ -57,10 +54,8 @@ describe('HttpLambdaAuthorizer', () => {
handler: 'index.handler',
});

const authorizer = new HttpLambdaAuthorizer({
authorizerName: 'my-simple-authorizer',
const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, {
responseTypes: [HttpLambdaResponseType.SIMPLE],
handler,
});

// WHEN
Expand Down Expand Up @@ -88,10 +83,8 @@ describe('HttpLambdaAuthorizer', () => {
handler: 'index.handler',
});

const authorizer = new HttpLambdaAuthorizer({
authorizerName: 'my-iam-authorizer',
const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, {
responseTypes: [HttpLambdaResponseType.IAM],
handler,
});

// WHEN
Expand Down Expand Up @@ -119,10 +112,8 @@ describe('HttpLambdaAuthorizer', () => {
handler: 'index.handler',
});

const authorizer = new HttpLambdaAuthorizer({
authorizerName: 'my-simple-iam-authorizer',
const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, {
responseTypes: [HttpLambdaResponseType.IAM, HttpLambdaResponseType.SIMPLE],
handler,
});

// WHEN
Expand Down Expand Up @@ -150,10 +141,7 @@ describe('HttpLambdaAuthorizer', () => {
handler: 'index.handler',
});

const authorizer = new HttpLambdaAuthorizer({
authorizerName: 'my-simple-authorizer',
responseTypes: [HttpLambdaResponseType.SIMPLE],
handler,
const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler, {
resultsCacheTtl: Duration.minutes(10),
});

Expand Down
Loading

0 comments on commit 1e87b64

Please sign in to comment.