Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(apigatewayv2-authorizers): re-organize authorizer api #17772

Merged
merged 3 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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