Skip to content
New issue

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

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

Already on GitHub? # to your account

feat(cdk): expose authorizer id and authorization type #31622

Merged
merged 13 commits into from
Oct 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,10 @@ new CfnOutput(stack, 'URL', {
});
new CfnOutput(stack, 'URLWithDefaultAuthorizer', {
value: httpApiWithDefaultAuthorizer.url!,
});
});
new CfnOutput(stack, 'AuthorizerId', {
value: authorizer.authorizerId,
});
new CfnOutput(stack, 'AuthorizationType', {
value: authorizer.authorizationType,
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey } from 'aws-cdk-lib/aws-ap
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { App, Stack } from 'aws-cdk-lib';
import { App, Stack, CfnOutput } from 'aws-cdk-lib';
import { HttpUserPoolAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';

/*
Expand Down Expand Up @@ -45,4 +45,11 @@ new HttpRoute(stack, 'Route', {
httpApi: httpApiWithDefaultAuthorizer,
routeKey: HttpRouteKey.with('/v1/mything/{proxy+}', HttpMethod.ANY),
integration: new HttpLambdaIntegration('RootIntegration', handler),
});
});

new CfnOutput(stack, 'AuthorizerId', {
value: authorizer.authorizerId,
});
new CfnOutput(stack, 'AuthorizationType', {
value: authorizer.authorizationType,
});
44 changes: 44 additions & 0 deletions packages/aws-cdk-lib/aws-apigatewayv2-authorizers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,47 @@ import software.amazon.awscdk.aws_apigatewayv2_authorizers.*;
// If you want to import a specific construct
import software.amazon.awscdk.aws_apigatewayv2_authorizers.WebSocketIamAuthorizer;
```

## Import an existing HTTP Authorizer
If you want to import av existing HTTP Authorizer you can retrieve the authorizer id once it has been bound to a route, store it where you prefer, and then pass the values to

`HttpAuthorizer.fromHttpAuthorizerAttributes`

```ts
import { HttpLambdaAuthorizer, HttpLambdaResponseType } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import { HttpUrlIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import { CfnOutput } from 'aws-cdk-lib';

// This function handles your auth logic
declare const authHandler: lambda.Function;

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

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

api.addRoutes({
integration: new HttpUrlIntegration('BooksIntegration', 'https://get-books-proxy.example.com'),
path: '/books',
authorizer,
});

// You can only access authorizerId after it's been bound to a route
// In this example we expect use CfnOutput
new CfnOutput(this, 'authorizerId', authorizer.authorizerId);
new CfnOutput(this, 'authorizerType', authorizer.authorizerType);
```
JonWallsten marked this conversation as resolved.
Show resolved Hide resolved

```ts
import { HttpAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2';
import { Fn } from 'aws-cdk-lib'

const authorizerId = Fn.importValue('authorizerId');
const authorizerType = Fn.importValue('authorizerType');

const authorizer = HttpAuthorizer.fromHttpAuthorizerAttributes(stack, '', {
authorizerId,
authorizerType
});
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import {
* Authorize HTTP API Routes with IAM
*/
export class HttpIamAuthorizer implements IHttpRouteAuthorizer {
/**
* The authorizationType used for IAM Authorizer
*/
public readonly authorizationType = HttpAuthorizerType.IAM;
public bind(_options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
return {
authorizationType: HttpAuthorizerType.IAM,
authorizationType: this.authorizationType,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export interface HttpJwtAuthorizerProps {
*/
export class HttpJwtAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;
/**
* The authorizationType used for JWT Authorizer
*/
public readonly authorizationType = 'JWT';

/**
* Initialize a JWT authorizer to be bound with HTTP route.
Expand All @@ -50,6 +54,18 @@ export class HttpJwtAuthorizer implements IHttpRouteAuthorizer {
private readonly props: HttpJwtAuthorizerProps) {
}

/**
* Return the id of the authorizer if it's been constructed
*/
public get authorizerId(): string {
if (!this.authorizer) {
throw new Error(
'Cannot access authorizerId until authorizer is attached to a HttpRoute',
);
}
return this.authorizer.authorizerId;
}

public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
if (!this.authorizer) {
this.authorizer = new HttpAuthorizer(options.scope, this.id, {
Expand All @@ -64,7 +80,7 @@ export class HttpJwtAuthorizer implements IHttpRouteAuthorizer {

return {
authorizerId: this.authorizer.authorizerId,
authorizationType: 'JWT',
authorizationType: this.authorizationType,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;
private httpApi?: IHttpApi;

/**
* The authorizationType used for Lambda Authorizer
*/
public readonly authorizationType = 'CUSTOM';

/**
* Initialize a lambda authorizer to be bound with HTTP route.
* @param id The id of the underlying construct
Expand All @@ -80,6 +85,18 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer {
private readonly props: HttpLambdaAuthorizerProps = {}) {
}

/**
* Return the id of the authorizer if it's been constructed
*/
public get authorizerId(): string {
if (!this.authorizer) {
throw new Error(
'Cannot access authorizerId until authorizer is attached to a HttpRoute',
);
}
return this.authorizer.authorizerId;
}

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');
Expand Down Expand Up @@ -116,7 +133,7 @@ export class HttpLambdaAuthorizer implements IHttpRouteAuthorizer {

return {
authorizerId: this.authorizer.authorizerId,
authorizationType: 'CUSTOM',
authorizationType: this.authorizationType,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export interface HttpUserPoolAuthorizerProps {
*/
export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer {
private authorizer?: HttpAuthorizer;

/**
* The authorizationType used for UserPool Authorizer
*/
public readonly authorizationType = 'JWT';
/**
* Initialize a Cognito user pool authorizer to be bound with HTTP route.
* @param id The id of the underlying construct
Expand All @@ -51,6 +54,18 @@ export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer {
private readonly props: HttpUserPoolAuthorizerProps = {}) {
}

/**
* Return the id of the authorizer if it's been constructed
*/
public get authorizerId(): string {
if (!this.authorizer) {
throw new Error(
'Cannot access authorizerId until authorizer is attached to a HttpRoute',
);
}
return this.authorizer.authorizerId;
}

public bind(options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
if (!this.authorizer) {
const region = this.props.userPoolRegion ?? Stack.of(options.scope).region;
Expand All @@ -68,7 +83,7 @@ export class HttpUserPoolAuthorizer implements IHttpRouteAuthorizer {

return {
authorizerId: this.authorizer.authorizerId,
authorizationType: 'JWT',
authorizationType: this.authorizationType,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,37 @@ describe('HttpJwtAuthorizer', () => {
// THEN
Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::Authorizer', 1);
});

test('should expose authorizer id after authorizer has been bound to route', () => {
// GIVEN
const stack = new Stack();
const api = new HttpApi(stack, 'HttpApi');
const authorizer = new HttpJwtAuthorizer('BooksAuthorizer', 'https://test.us.auth0.com', {
jwtAudience: ['3131231'],
});

// WHEN
api.addRoutes({
integration: new DummyRouteIntegration(),
path: '/books',
authorizer,
});

// THEN
expect(authorizer.authorizerId).toBeDefined();
});

test('should throw error when acessing authorizer before it been bound to route', () => {
// GIVEN
const stack = new Stack();
const t = () => {
const authorizer = new HttpJwtAuthorizer('BooksAuthorizer', 'https://test.us.auth0.com', {
jwtAudience: ['3131231'],
});
const authorizerId = authorizer.authorizerId;
};

// THEN
expect(t).toThrow(Error);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,47 @@ describe('HttpLambdaAuthorizer', () => {
AuthorizerResultTtlInSeconds: 600,
});
});

test('should expose authorizer id after authorizer has been bound to route', () => {
// GIVEN
const stack = new Stack();
const api = new HttpApi(stack, 'HttpApi');

const handler = new Function(stack, 'auth-function', {
runtime: lambda.Runtime.NODEJS_LATEST,
code: Code.fromInline('exports.handler = () => {return true}'),
handler: 'index.handler',
});

const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler);

// WHEN
api.addRoutes({
integration: new DummyRouteIntegration(),
path: '/books',
authorizer,
});

// THEN
expect(authorizer.authorizerId).toBeDefined();
});

test('should throw error when acessing authorizer before it been bound to route', () => {
// GIVEN
const stack = new Stack();

const handler = new Function(stack, 'auth-function', {
runtime: lambda.Runtime.NODEJS_LATEST,
code: Code.fromInline('exports.handler = () => {return true}'),
handler: 'index.handler',
});

const t = () => {
const authorizer = new HttpLambdaAuthorizer('BooksAuthorizer', handler);
const authorizerId = authorizer.authorizerId;
};

// THEN
expect(t).toThrow(Error);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,44 @@ describe('HttpUserPoolAuthorizer', () => {
},
});
});

test('should expose authorizer id after authorizer has been bound to route', () => {
// GIVEN
const stack = new Stack();
const api = new HttpApi(stack, 'HttpApi');
const userPool = new UserPool(stack, 'UserPool');
const userPoolClient1 = userPool.addClient('UserPoolClient1');
const userPoolClient2 = userPool.addClient('UserPoolClient2');
const authorizer = new HttpUserPoolAuthorizer('BooksAuthorizer', userPool, {
userPoolClients: [userPoolClient1, userPoolClient2],
});

// WHEN
api.addRoutes({
integration: new DummyRouteIntegration(),
path: '/books',
authorizer,
});

// THEN
expect(authorizer.authorizerId).toBeDefined();
});

test('should throw error when acessing authorizer before it been bound to route', () => {
// GIVEN
const stack = new Stack();
const userPool = new UserPool(stack, 'UserPool');
const userPoolClient1 = userPool.addClient('UserPoolClient1');
const userPoolClient2 = userPool.addClient('UserPoolClient2');

const t = () => {
const authorizer = new HttpUserPoolAuthorizer('BooksAuthorizer', userPool, {
userPoolClients: [userPoolClient1, userPoolClient2],
});
const authorizerId = authorizer.authorizerId;
};

// THEN
expect(t).toThrow(Error);
});
});
7 changes: 6 additions & 1 deletion packages/aws-cdk-lib/aws-apigatewayv2/lib/http/authorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,14 @@ function undefinedIfNoKeys<A extends { [key: string]: unknown }>(obj: A): A | un
* Explicitly configure no authorizers on specific HTTP API routes.
*/
export class HttpNoneAuthorizer implements IHttpRouteAuthorizer {
/**
* The authorizationType used for IAM Authorizer
*/
public readonly authorizationType = 'NONE';
public bind(_options: HttpRouteAuthorizerBindOptions): HttpRouteAuthorizerConfig {
return {
authorizationType: 'NONE',
authorizationType: this.authorizationType,
};
}
}

Loading