Skip to content

Commit ff1e5b3

Browse files
feat(apigateway): cognito user pool authorizer (#12786)
feat(apigateway): add support for cognito user pool authorizer closes #5618 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 2aba609 commit ff1e5b3

File tree

7 files changed

+438
-0
lines changed

7 files changed

+438
-0
lines changed

packages/@aws-cdk/aws-apigateway/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ running on AWS Lambda, or any web application.
3232
- [IAM-based authorizer](#iam-based-authorizer)
3333
- [Lambda-based token authorizer](#lambda-based-token-authorizer)
3434
- [Lambda-based request authorizer](#lambda-based-request-authorizer)
35+
- [Cognito User Pools authorizer](#cognito-user-pools-authorizer)
3536
- [Mutual TLS](#mutal-tls-mtls)
3637
- [Deployments](#deployments)
3738
- [Deep dive: Invalidation of deployments](#deep-dive-invalidation-of-deployments)
@@ -580,6 +581,25 @@ Authorizers can also be passed via the `defaultMethodOptions` property within th
580581
explicitly overridden, the specified defaults will be applied across all `Method`s across the `RestApi` or across all `Resource`s,
581582
depending on where the defaults were specified.
582583

584+
### Cognito User Pools authorizer
585+
586+
API Gateway also allows [Amazon Cognito user pools as authorizer](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html)
587+
588+
The following snippet configures a Cognito user pool as an authorizer:
589+
590+
```ts
591+
const userPool = new cognito.UserPool(stack, 'UserPool');
592+
593+
const auth = new apigateway.CognitoUserPoolsAuthorizer(this, 'booksAuthorizer', {
594+
cognitoUserPools: [userPool]
595+
});
596+
597+
books.addMethod('GET', new apigateway.HttpIntegration('http://amazon.com'), {
598+
authorizer: auth,
599+
authorizationType: apigateway.AuthorizationType.COGNITO,
600+
});
601+
```
602+
583603
## Mutual TLS (mTLS)
584604

585605
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.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import * as cognito from '@aws-cdk/aws-cognito';
2+
import { Duration, Lazy, Names, Stack } from '@aws-cdk/core';
3+
import { Construct } from 'constructs';
4+
import { CfnAuthorizer } from '../apigateway.generated';
5+
import { Authorizer, IAuthorizer } from '../authorizer';
6+
import { AuthorizationType } from '../method';
7+
import { IRestApi } from '../restapi';
8+
9+
/**
10+
* Properties for CognitoUserPoolsAuthorizer
11+
*/
12+
export interface CognitoUserPoolsAuthorizerProps {
13+
/**
14+
* An optional human friendly name for the authorizer. Note that, this is not the primary identifier of the authorizer.
15+
*
16+
* @default - the unique construct ID
17+
*/
18+
readonly authorizerName?: string;
19+
20+
/**
21+
* The user pools to associate with this authorizer.
22+
*/
23+
readonly cognitoUserPools: cognito.IUserPool[];
24+
25+
/**
26+
* How long APIGateway should cache the results. Max 1 hour.
27+
* Disable caching by setting this to 0.
28+
*
29+
* @default Duration.minutes(5)
30+
*/
31+
readonly resultsCacheTtl?: Duration;
32+
33+
/**
34+
* The request header mapping expression for the bearer token. This is typically passed as part of the header, in which case
35+
* this should be `method.request.header.Authorizer` where Authorizer is the header containing the bearer token.
36+
* @see https://docs.aws.amazon.com/apigateway/api-reference/link-relation/authorizer-create/#identitySource
37+
* @default `IdentitySource.header('Authorization')`
38+
*/
39+
readonly identitySource?: string;
40+
}
41+
42+
/**
43+
* Cognito user pools based custom authorizer
44+
*
45+
* @resource AWS::ApiGateway::Authorizer
46+
*/
47+
export class CognitoUserPoolsAuthorizer extends Authorizer implements IAuthorizer {
48+
/**
49+
* The id of the authorizer.
50+
* @attribute
51+
*/
52+
public readonly authorizerId: string;
53+
54+
/**
55+
* The ARN of the authorizer to be used in permission policies, such as IAM and resource-based grants.
56+
* @attribute
57+
*/
58+
public readonly authorizerArn: string;
59+
60+
/**
61+
* The authorization type of this authorizer.
62+
*/
63+
public readonly authorizationType?: AuthorizationType;
64+
65+
private restApiId?: string;
66+
67+
constructor(scope: Construct, id: string, props: CognitoUserPoolsAuthorizerProps) {
68+
super(scope, id);
69+
70+
const restApiId = this.lazyRestApiId();
71+
const resource = new CfnAuthorizer(this, 'Resource', {
72+
name: props.authorizerName ?? Names.uniqueId(this),
73+
restApiId,
74+
type: 'COGNITO_USER_POOLS',
75+
providerArns: props.cognitoUserPools.map(userPool => userPool.userPoolArn),
76+
authorizerResultTtlInSeconds: props.resultsCacheTtl?.toSeconds(),
77+
identitySource: props.identitySource || 'method.request.header.Authorization',
78+
});
79+
80+
this.authorizerId = resource.ref;
81+
this.authorizerArn = Stack.of(this).formatArn({
82+
service: 'execute-api',
83+
resource: restApiId,
84+
resourceName: `authorizers/${this.authorizerId}`,
85+
});
86+
this.authorizationType = AuthorizationType.COGNITO;
87+
}
88+
89+
/**
90+
* Attaches this authorizer to a specific REST API.
91+
* @internal
92+
*/
93+
public _attachToApi(restApi: IRestApi): void {
94+
if (this.restApiId && this.restApiId !== restApi.restApiId) {
95+
throw new Error('Cannot attach authorizer to two different rest APIs');
96+
}
97+
98+
this.restApiId = restApi.restApiId;
99+
}
100+
101+
/**
102+
* Returns a token that resolves to the Rest Api Id at the time of synthesis.
103+
* Throws an error, during token resolution, if no RestApi is attached to this authorizer.
104+
*/
105+
private lazyRestApiId() {
106+
return Lazy.string({
107+
produce: () => {
108+
if (!this.restApiId) {
109+
throw new Error(`Authorizer (${this.node.path}) must be attached to a RestApi`);
110+
}
111+
return this.restApiId;
112+
},
113+
});
114+
}
115+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './lambda';
22
export * from './identity-source';
3+
export * from './cognito';

packages/@aws-cdk/aws-apigateway/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"dependencies": {
8181
"@aws-cdk/aws-certificatemanager": "0.0.0",
8282
"@aws-cdk/aws-cloudwatch": "0.0.0",
83+
"@aws-cdk/aws-cognito": "0.0.0",
8384
"@aws-cdk/aws-ec2": "0.0.0",
8485
"@aws-cdk/aws-elasticloadbalancingv2": "0.0.0",
8586
"@aws-cdk/aws-iam": "0.0.0",
@@ -95,6 +96,7 @@
9596
"peerDependencies": {
9697
"@aws-cdk/aws-certificatemanager": "0.0.0",
9798
"@aws-cdk/aws-cloudwatch": "0.0.0",
99+
"@aws-cdk/aws-cognito": "0.0.0",
98100
"@aws-cdk/aws-ec2": "0.0.0",
99101
"@aws-cdk/aws-elasticloadbalancingv2": "0.0.0",
100102
"@aws-cdk/aws-iam": "0.0.0",
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import '@aws-cdk/assert/jest';
2+
import * as cognito from '@aws-cdk/aws-cognito';
3+
import { Duration, Stack } from '@aws-cdk/core';
4+
import { AuthorizationType, CognitoUserPoolsAuthorizer, RestApi } from '../../lib';
5+
6+
describe('Cognito Authorizer', () => {
7+
test('default cognito authorizer', () => {
8+
// GIVEN
9+
const stack = new Stack();
10+
const userPool = new cognito.UserPool(stack, 'UserPool');
11+
12+
// WHEN
13+
const authorizer = new CognitoUserPoolsAuthorizer(stack, 'myauthorizer', {
14+
cognitoUserPools: [userPool],
15+
});
16+
17+
const restApi = new RestApi(stack, 'myrestapi');
18+
restApi.root.addMethod('ANY', undefined, {
19+
authorizer,
20+
authorizationType: AuthorizationType.COGNITO,
21+
});
22+
23+
// THEN
24+
expect(stack).toHaveResource('AWS::ApiGateway::Authorizer', {
25+
Type: 'COGNITO_USER_POOLS',
26+
RestApiId: stack.resolve(restApi.restApiId),
27+
IdentitySource: 'method.request.header.Authorization',
28+
ProviderARNs: [stack.resolve(userPool.userPoolArn)],
29+
});
30+
31+
expect(authorizer.authorizerArn.endsWith(`/authorizers/${authorizer.authorizerId}`)).toBeTruthy();
32+
});
33+
34+
test('cognito authorizer with all parameters specified', () => {
35+
// GIVEN
36+
const stack = new Stack();
37+
const userPool1 = new cognito.UserPool(stack, 'UserPool1');
38+
const userPool2 = new cognito.UserPool(stack, 'UserPool2');
39+
40+
// WHEN
41+
const authorizer = new CognitoUserPoolsAuthorizer(stack, 'myauthorizer', {
42+
cognitoUserPools: [userPool1, userPool2],
43+
identitySource: 'method.request.header.whoami',
44+
authorizerName: 'myauthorizer',
45+
resultsCacheTtl: Duration.minutes(1),
46+
});
47+
48+
const restApi = new RestApi(stack, 'myrestapi');
49+
restApi.root.addMethod('ANY', undefined, {
50+
authorizer,
51+
authorizationType: AuthorizationType.COGNITO,
52+
});
53+
54+
// THEN
55+
expect(stack).toHaveResource('AWS::ApiGateway::Authorizer', {
56+
Type: 'COGNITO_USER_POOLS',
57+
Name: 'myauthorizer',
58+
RestApiId: stack.resolve(restApi.restApiId),
59+
IdentitySource: 'method.request.header.whoami',
60+
AuthorizerResultTtlInSeconds: 60,
61+
ProviderARNs: [stack.resolve(userPool1.userPoolArn), stack.resolve(userPool2.userPoolArn)],
62+
});
63+
64+
expect(authorizer.authorizerArn.endsWith(`/authorizers/${authorizer.authorizerId}`)).toBeTruthy();
65+
});
66+
});

0 commit comments

Comments
 (0)