Skip to content

Commit

Permalink
chore(apigatewayv2-authorizers): re-organize authorizer api (#17772)
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.

BREAKING CHANGE: The default value for the prop `authorizerName`
in `HttpJwtAuthorizerProps` has changed.
* **apigatewayv2-authorizers:** `HttpJwtAuthorizer` now takes the
  construct id and the target jwt issuer as part of its constructor.
* **apigatewayv2-authorizers:** `HttpLambdaAuthorizer` now takes
  the construct id and the target lambda function handler as part of
  its constructor.
* **apigatewayv2-authorizers:** The default value for the prop
  `authorizerName` in `HttpUserPoolAuthorizerProps` has changed.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Niranjan Jayakar committed Nov 30, 2021
1 parent 1799f7e commit 719f33e
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 107 deletions.
22 changes: 8 additions & 14 deletions packages/@aws-cdk/aws-apigatewayv2-authorizers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ In the example below, all routes will require the `manage:books` scope present i
```ts
import { HttpJwtAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers';

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

const api = new apigwv2.HttpApi(this, 'HttpApi', {
Expand All @@ -73,9 +73,9 @@ The example below showcases default authorization, along with route authorizatio
import { HttpJwtAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers';
import { HttpUrlIntegration } from '@aws-cdk/aws-apigatewayv2-integrations';

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

const api = new apigwv2.HttpApi(this, 'HttpApi', {
Expand Down Expand Up @@ -130,9 +130,9 @@ Clients that fail authorization are presented with either 2 responses:
import { HttpJwtAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers';
import { HttpUrlIntegration } from '@aws-cdk/aws-apigatewayv2-integrations';

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

const api = new apigwv2.HttpApi(this, 'HttpApi');
Expand All @@ -158,12 +158,8 @@ import { HttpUserPoolAuthorizer } from '@aws-cdk/aws-apigatewayv2-authorizers';
import { HttpUrlIntegration } from '@aws-cdk/aws-apigatewayv2-integrations';

const userPool = new cognito.UserPool(this, 'UserPool');
const userPoolClient = userPool.addClient('UserPoolClient');

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

const api = new apigwv2.HttpApi(this, 'HttpApi');

Expand All @@ -188,10 +184,8 @@ import { HttpUrlIntegration } from '@aws-cdk/aws-apigatewayv2-integrations';
// This function handles your auth logic
declare const authHandler: lambda.Function;

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

const api = new apigwv2.HttpApi(this, 'HttpApi');
Expand Down
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
31 changes: 17 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,16 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;
private httpApi?: IHttpApi;

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

public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
Expand All @@ -85,26 +90,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
Loading

0 comments on commit 719f33e

Please sign in to comment.