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(apigateway): cognito user pool authorizer #12786

Merged
merged 6 commits into from
Feb 3, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
20 changes: 20 additions & 0 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ running on AWS Lambda, or any web application.
- [IAM-based authorizer](#iam-based-authorizer)
- [Lambda-based token authorizer](#lambda-based-token-authorizer)
- [Lambda-based request authorizer](#lambda-based-request-authorizer)
- [Cognito User Pools based authorizer](#cognito-user-pools-based-authorizer)
ayush987goyal marked this conversation as resolved.
Show resolved Hide resolved
- [Mutual TLS](#mutal-tls-mtls)
- [Deployments](#deployments)
- [Deep dive: Invalidation of deployments](#deep-dive-invalidation-of-deployments)
Expand Down Expand Up @@ -580,6 +581,25 @@ Authorizers can also be passed via the `defaultMethodOptions` property within th
explicitly overridden, the specified defaults will be applied across all `Method`s across the `RestApi` or across all `Resource`s,
depending on where the defaults were specified.

### Cognito User Pools based authorizer
ayush987goyal marked this conversation as resolved.
Show resolved Hide resolved

API Gateway also allows [Amazon Cognito user pools as authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html)

The following snippet configures a Cognito user pool as an authorizer:

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

const auth = new apigateway.CognitoAuthorizer(this, 'booksAuthorizer', {
ayush987goyal marked this conversation as resolved.
Show resolved Hide resolved
cognitoUserPools: [userPool]
});

books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
authorizer: auth,
authorizationType: apigateway.AuthorizationType.COGNITO,
});
```

## Mutual TLS (mTLS)

Mutual TLS can be configured to limit access to your API based by using client certificates instead of (or as an extension of) using authorization headers.
Expand Down
115 changes: 115 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/authorizers/cognito.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as cognito from '@aws-cdk/aws-cognito';
import { Duration, Lazy, Names, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnAuthorizer } from '../apigateway.generated';
import { Authorizer, IAuthorizer } from '../authorizer';
import { AuthorizationType } from '../method';
import { IRestApi } from '../restapi';

/**
* Properties for CognitoAuthorizer
*/
export interface CognitoAuthorizerProps {
/**
* An optional human friendly name for the authorizer. Note that, this is not the primary identifier of the authorizer.
*
* @default - the unique construcrt ID
ayush987goyal marked this conversation as resolved.
Show resolved Hide resolved
*/
readonly authorizerName?: string;

/**
* The user pools to associate with this authorizer.
*/
readonly cognitoUserPools: cognito.IUserPool[];

/**
* How long APIGateway should cache the results. Max 1 hour.
* Disable caching by setting this to 0.
*
* @default Duration.minutes(5)
*/
readonly resultsCacheTtl?: Duration;

/**
* The request header mapping expression for the bearer token. This is typically passed as part of the header, in which case
* this should be `method.request.header.Authorizer` where Authorizer is the header containing the bearer token.
* @see https://docs.aws.amazon.com/apigateway/api-reference/link-relation/authorizer-create/#identitySource
* @default `IdentitySource.header('Authorization')`
*/
readonly identitySource?: string;
}

ayush987goyal marked this conversation as resolved.
Show resolved Hide resolved
/**
* Cognito user pools based custom authorizer
*
* @resource AWS::ApiGateway::Authorizer
*/
export class CognitoAuthorizer extends Authorizer implements IAuthorizer {
/**
* The id of the authorizer.
* @attribute
*/
public readonly authorizerId: string;

/**
* The ARN of the authorizer to be used in permission policies, such as IAM and resource-based grants.
* @attribute
*/
public readonly authorizerArn: string;

/**
* The authorization type of this authorizer.
*/
public readonly authorizationType?: AuthorizationType;

private restApiId?: string;

constructor(scope: Construct, id: string, props: CognitoAuthorizerProps) {
super(scope, id);

const restApiId = this.lazyRestApiId();
const resource = new CfnAuthorizer(this, 'Resource', {
name: props.authorizerName ?? Names.uniqueId(this),
restApiId,
type: 'COGNITO_USER_POOLS',
providerArns: props.cognitoUserPools.map(userPool => userPool.userPoolArn),
authorizerResultTtlInSeconds: props.resultsCacheTtl?.toSeconds(),
identitySource: props.identitySource || 'method.request.header.Authorization',
});

this.authorizerId = resource.ref;
this.authorizerArn = Stack.of(this).formatArn({
service: 'execute-api',
resource: restApiId,
resourceName: `authorizers/${this.authorizerId}`,
});
this.authorizationType = AuthorizationType.COGNITO;
}

/**
* Attaches this authorizer to a specific REST API.
* @internal
*/
public _attachToApi(restApi: IRestApi): void {
if (this.restApiId && this.restApiId !== restApi.restApiId) {
throw new Error('Cannot attach authorizer to two different rest APIs');
}

this.restApiId = restApi.restApiId;
}

/**
* Returns a token that resolves to the Rest Api Id at the time of synthesis.
* Throws an error, during token resolution, if no RestApi is attached to this authorizer.
*/
private lazyRestApiId() {
return Lazy.string({
produce: () => {
if (!this.restApiId) {
throw new Error(`Authorizer (${this.node.path}) must be attached to a RestApi`);
}
return this.restApiId;
},
});
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apigateway/lib/authorizers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './lambda';
export * from './identity-source';
export * from './cognito';
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-apigateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"dependencies": {
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-cognito": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-elasticloadbalancingv2": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
Expand All @@ -95,6 +96,7 @@
"peerDependencies": {
"@aws-cdk/aws-certificatemanager": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"@aws-cdk/aws-cognito": "0.0.0",
"@aws-cdk/aws-ec2": "0.0.0",
"@aws-cdk/aws-elasticloadbalancingv2": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
Expand Down
66 changes: 66 additions & 0 deletions packages/@aws-cdk/aws-apigateway/test/authorizers/cognito.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import '@aws-cdk/assert/jest';
import * as cognito from '@aws-cdk/aws-cognito';
import { Duration, Stack } from '@aws-cdk/core';
import { AuthorizationType, CognitoAuthorizer, RestApi } from '../../lib';

describe('Cognito Authorizer', () => {
test('default cognito authorizer', () => {
// GIVEN
const stack = new Stack();
const userPool = new cognito.UserPool(stack, 'UserPool');

// WHEN
const authorizer = new CognitoAuthorizer(stack, 'myauthorizer', {
cognitoUserPools: [userPool],
});

const restApi = new RestApi(stack, 'myrestapi');
restApi.root.addMethod('ANY', undefined, {
authorizer,
authorizationType: AuthorizationType.COGNITO,
});

// THEN
expect(stack).toHaveResource('AWS::ApiGateway::Authorizer', {
Type: 'COGNITO_USER_POOLS',
RestApiId: stack.resolve(restApi.restApiId),
IdentitySource: 'method.request.header.Authorization',
ProviderARNs: [stack.resolve(userPool.userPoolArn)],
});

expect(authorizer.authorizerArn.endsWith(`/authorizers/${authorizer.authorizerId}`)).toBeTruthy();
});

test('cognito authorizer with all parameters specified', () => {
// GIVEN
const stack = new Stack();
const userPool1 = new cognito.UserPool(stack, 'UserPool1');
const userPool2 = new cognito.UserPool(stack, 'UserPool2');

// WHEN
const authorizer = new CognitoAuthorizer(stack, 'myauthorizer', {
cognitoUserPools: [userPool1, userPool2],
identitySource: 'method.request.header.whoami',
authorizerName: 'myauthorizer',
resultsCacheTtl: Duration.minutes(1),
});

const restApi = new RestApi(stack, 'myrestapi');
restApi.root.addMethod('ANY', undefined, {
authorizer,
authorizationType: AuthorizationType.COGNITO,
});

// THEN
expect(stack).toHaveResource('AWS::ApiGateway::Authorizer', {
Type: 'COGNITO_USER_POOLS',
Name: 'myauthorizer',
RestApiId: stack.resolve(restApi.restApiId),
IdentitySource: 'method.request.header.whoami',
AuthorizerResultTtlInSeconds: 60,
ProviderARNs: [stack.resolve(userPool1.userPoolArn), stack.resolve(userPool2.userPoolArn)],
});

expect(authorizer.authorizerArn.endsWith(`/authorizers/${authorizer.authorizerId}`)).toBeTruthy();
});
});
Loading