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

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
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(stack, 'authorizerId', authorizer.authorizerId);
new CfnOutput(stack, 'authorizerType', authorizer.authorizerType);
```

```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