From c3a39c1309d17b89e07c0a90e48af5c57f5531b1 Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Mon, 26 Oct 2020 10:59:50 -0400 Subject: [PATCH 01/11] feat(aws-cognito): Add L2 for UserPoolResourceServer Also allow adding resource server via userPool.addResourceServer --- packages/@aws-cdk/aws-cognito/README.md | 42 ++++++++++ packages/@aws-cdk/aws-cognito/lib/index.ts | 3 +- .../lib/user-pool-resource-server.ts | 84 +++++++++++++++++++ .../@aws-cdk/aws-cognito/lib/user-pool.ts | 14 ++++ ...eg.user-pool-resource-server.expected.json | 50 +++++++++++ .../test/integ.user-pool-resource-server.ts | 20 +++++ .../test/user-pool-resource-server.test.ts | 71 ++++++++++++++++ .../aws-cognito/test/user-pool.test.ts | 30 +++++++ 8 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json create mode 100644 packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts create mode 100644 packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 7160d9cf60efc..bd3310eb75602 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -546,6 +546,48 @@ pool.addClient('app-client', { }); ``` +### Resource Servers + +In your application you may need to attach custom permissions when using oAuth. Resource Servers allow us to provide custom scopes that we can attach to an app client. For example, you might be dealing with user data, and want to give different permissions to different clients. + +```ts +const pool = new cognito.UserPool(this, 'Pool'); + +pool.addResourceServer('ResourceServer', { + identifier: 'users', + scopes: [ + { + scopeName: 'read', + scopeDescription: 'Read-only access', + }, + { + scopeName: '*', + scopeDescription: 'Full access', + }, + ] +}); + +pool.addClient('read-only-client', { + // ... + oAuth: { + flows: { + clientCredentials: true, + }, + scopes: [OAuthScope.custom('users/read')], + }, +}); + +pool.addClient('full-access-client', { + // ... + oAuth: { + flows: { + clientCredentials: true, + }, + scopes: [OAuthScope.custom('users/*')], + }, +}); +``` + ### Domains After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be diff --git a/packages/@aws-cdk/aws-cognito/lib/index.ts b/packages/@aws-cdk/aws-cognito/lib/index.ts index 9aaccf45bf47e..764031cd0ea7b 100644 --- a/packages/@aws-cdk/aws-cognito/lib/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/index.ts @@ -4,4 +4,5 @@ export * from './user-pool'; export * from './user-pool-attr'; export * from './user-pool-client'; export * from './user-pool-domain'; -export * from './user-pool-idp'; \ No newline at end of file +export * from './user-pool-idp'; +export * from './user-pool-resource-server'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts new file mode 100644 index 0000000000000..3023f9a0a8cef --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts @@ -0,0 +1,84 @@ +import { IResource, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnUserPoolResourceServer } from './cognito.generated'; +import { IUserPool } from './user-pool'; + +/** + * Represents a Cognito user pool resource server + */ +export interface IUserPoolResourceServer extends IResource { + /** + * Resource server id + * @attribute + */ + readonly userPoolResourceServerId: string; +} + +/** + * Options to create a scope for a UserPoolResourceServer + */ +export interface IUserPoolResourceServerScope { + /** + * The name of the scope + */ + readonly scopeName: string; + + /** + * A description of the scope. + */ + readonly scopeDescription: string; +} + + +/** + * Options to create a UserPoolResourceServer + */ +export interface UserPoolResourceServerOptions { + /** + * A unique resource server identifier for the resource server. + */ + readonly identifier: string; + + /** + * A friendly name for the resource server. + * @default - will use the identifier + */ + readonly name?: string; + + /** + * Oauth scopes + * @default - No scopes will be added + */ + readonly scopes?: IUserPoolResourceServerScope[]; +} + +/** + * Properties for the UserPoolResourceServer construct + */ +export interface UserPoolResourceServerProps extends UserPoolResourceServerOptions { + /** + * The user pool to add this resource server to + */ + readonly userPool: IUserPool; +} + +/** + * Defines a User Pool OAuth2.0 Resource Server + */ +export class UserPoolResourceServer extends Resource implements IUserPoolResourceServer { + public readonly userPoolResourceServerId: string; + + constructor(scope: Construct, id: string, props: UserPoolResourceServerProps) { + super(scope, id); + + const resource = new CfnUserPoolResourceServer(this, 'Resource', { + identifier: props.identifier, + name: props.name ?? props.identifier, + scopes: props.scopes, + userPoolId: props.userPool.userPoolId, + }); + + this.userPoolResourceServerId = resource.ref; + } + +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 1f3feba34c669..6ae18e82e7edd 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -8,6 +8,7 @@ import { ICustomAttribute, StandardAttribute, StandardAttributes } from './user- import { UserPoolClient, UserPoolClientOptions } from './user-pool-client'; import { UserPoolDomain, UserPoolDomainOptions } from './user-pool-domain'; import { IUserPoolIdentityProvider } from './user-pool-idp'; +import { UserPoolResourceServer, UserPoolResourceServerOptions } from './user-pool-resource-server'; /** * The different ways in which users of this pool can sign up or sign in. @@ -600,6 +601,12 @@ export interface IUserPool extends IResource { */ addDomain(id: string, options: UserPoolDomainOptions): UserPoolDomain; + /** + * Add a new resource server to this user pool. + * @see https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-resource-servers.html + */ + addResourceServer(id: string, options: UserPoolResourceServerOptions): UserPoolResourceServer; + /** * Register an identity provider with this user pool. */ @@ -625,6 +632,13 @@ abstract class UserPoolBase extends Resource implements IUserPool { }); } + public addResourceServer(id: string, options: UserPoolResourceServerOptions): UserPoolResourceServer { + return new UserPoolResourceServer(this, id, { + userPool: this, + ...options, + }); + } + public registerIdentityProvider(provider: IUserPoolIdentityProvider) { this.identityProviders.push(provider); } diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json new file mode 100644 index 0000000000000..0581c6aeafd6c --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json @@ -0,0 +1,50 @@ +{ + "Resources": { + "myuserpool01998219": { + "Type": "AWS::Cognito::UserPool", + "Properties": { + "AccountRecoverySetting": { + "RecoveryMechanisms": [ + { + "Name": "verified_phone_number", + "Priority": 1 + }, + { + "Name": "verified_email", + "Priority": 2 + } + ] + }, + "AdminCreateUserConfig": { + "AllowAdminCreateUserOnly": true + }, + "EmailVerificationMessage": "The verification code to your new account is {####}", + "EmailVerificationSubject": "Verify your new account", + "SmsVerificationMessage": "The verification code to your new account is {####}", + "UserPoolName": "MyUserPool", + "VerificationMessageTemplate": { + "DefaultEmailOption": "CONFIRM_WITH_CODE", + "EmailMessage": "The verification code to your new account is {####}", + "EmailSubject": "Verify your new account", + "SmsMessage": "The verification code to your new account is {####}" + } + } + }, + "myuserpoolmyserver50C4D8E9": { + "Type": "AWS::Cognito::UserPoolResourceServer", + "Properties": { + "Identifier": "users", + "Name": "internal-users", + "UserPoolId": { + "Ref": "myuserpool01998219" + }, + "Scopes": [ + { + "ScopeDescription": "read only", + "ScopeName": "read" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts new file mode 100644 index 0000000000000..1bd3e737ce28f --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts @@ -0,0 +1,20 @@ +import { App, Stack } from '@aws-cdk/core'; +import { UserPool } from '../lib'; + +const app = new App(); +const stack = new Stack(app, 'integ-user-pool-resource-server'); + +const userPool = new UserPool(stack, 'myuserpool', { + userPoolName: 'MyUserPool', +}); + +userPool.addResourceServer('myserver', { + identifier: 'users', + name: 'internal-users', + scopes: [ + { + scopeName: 'read', + scopeDescription: 'read only', + }, + ], +}); diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts new file mode 100644 index 0000000000000..0593fbea26a7c --- /dev/null +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts @@ -0,0 +1,71 @@ +import '@aws-cdk/assert/jest'; +import { Stack } from '@aws-cdk/core'; +import { UserPool, UserPoolResourceServer } from '../lib'; + +describe('User Pool Resource Server', () => { + test('default setup', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolResourceServer(stack, 'Server', { + userPool: pool, + identifier: 'users', + }); + + //THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolResourceServer', { + Identifier: 'users', + Name: 'users', + UserPoolId: stack.resolve(pool.userPoolId), + }); + }); + + test('can assign a custom name', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolResourceServer(stack, 'Server', { + userPool: pool, + identifier: 'users', + name: 'internal-users', + }); + + //THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolResourceServer', { + Identifier: 'users', + Name: 'internal-users', + }); + }); + + test('can assign scopes', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + + // WHEN + new UserPoolResourceServer(stack, 'Server', { + userPool: pool, + identifier: 'users', + scopes: [ + { + scopeName: 'read', + scopeDescription: 'read only access', + }, + ], + }); + + //THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolResourceServer', { + Scopes: [ + { + ScopeDescription: 'read only access', + ScopeName: 'read', + }, + ], + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts index c4cb35832925c..0e05b11fff048 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool.test.ts @@ -883,6 +883,36 @@ describe('User Pool', () => { }); }); + test('addResourceServer', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const userpool = new UserPool(stack, 'Pool'); + userpool.addResourceServer('ResourceServer', { + identifier: 'users', + scopes: [ + { + scopeName: 'read', + scopeDescription: 'Read-only access', + }, + ], + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Cognito::UserPoolResourceServer', { + Identifier: 'users', + Name: 'users', + UserPoolId: stack.resolve(userpool.userPoolId), + Scopes: [ + { + ScopeDescription: 'Read-only access', + ScopeName: 'read', + }, + ], + }); + }); + test('addDomain', () => { // GIVEN const stack = new Stack(); From ee9be6ee592de16ac3be66113377e0e0a6aa9d79 Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Mon, 26 Oct 2020 11:03:53 -0400 Subject: [PATCH 02/11] eol --- packages/@aws-cdk/aws-cognito/lib/index.ts | 2 +- packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts | 3 +-- .../aws-cognito/test/user-pool-resource-server.test.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/index.ts b/packages/@aws-cdk/aws-cognito/lib/index.ts index 764031cd0ea7b..76761d0e1ba20 100644 --- a/packages/@aws-cdk/aws-cognito/lib/index.ts +++ b/packages/@aws-cdk/aws-cognito/lib/index.ts @@ -5,4 +5,4 @@ export * from './user-pool-attr'; export * from './user-pool-client'; export * from './user-pool-domain'; export * from './user-pool-idp'; -export * from './user-pool-resource-server'; \ No newline at end of file +export * from './user-pool-resource-server'; diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts index 3023f9a0a8cef..baa851f0dcbfa 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts @@ -80,5 +80,4 @@ export class UserPoolResourceServer extends Resource implements IUserPoolResourc this.userPoolResourceServerId = resource.ref; } - -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts index 0593fbea26a7c..5f9e351429578 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts @@ -68,4 +68,4 @@ describe('User Pool Resource Server', () => { ], }); }); -}); \ No newline at end of file +}); From 28c6471b4452d5e760cada236e6dd43fcd4e0610 Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Mon, 26 Oct 2020 11:53:38 -0400 Subject: [PATCH 03/11] Added import and change name to match test --- .../lib/user-pool-resource-server.ts | 19 +++++++++++++++---- .../test/integ.user-pool-resource-server.ts | 2 +- .../test/user-pool-resource-server.test.ts | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts index baa851f0dcbfa..4cf2490b4a5a5 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts @@ -17,7 +17,7 @@ export interface IUserPoolResourceServer extends IResource { /** * Options to create a scope for a UserPoolResourceServer */ -export interface IUserPoolResourceServerScope { +export interface ResourceServerScope { /** * The name of the scope */ @@ -43,13 +43,13 @@ export interface UserPoolResourceServerOptions { * A friendly name for the resource server. * @default - will use the identifier */ - readonly name?: string; + readonly userPoolResourceServerName?: string; /** * Oauth scopes * @default - No scopes will be added */ - readonly scopes?: IUserPoolResourceServerScope[]; + readonly scopes?: ResourceServerScope[]; } /** @@ -66,6 +66,17 @@ export interface UserPoolResourceServerProps extends UserPoolResourceServerOptio * Defines a User Pool OAuth2.0 Resource Server */ export class UserPoolResourceServer extends Resource implements IUserPoolResourceServer { + /** + * Import a user pool resource client given its id. + */ + public static fromUserPoolResourceServerId(scope: Construct, id: string, userPoolResourceServerId: string): IUserPoolResourceServer { + class Import extends Resource implements IUserPoolResourceServer { + public readonly userPoolResourceServerId = userPoolResourceServerId; + } + + return new Import(scope, id); + } + public readonly userPoolResourceServerId: string; constructor(scope: Construct, id: string, props: UserPoolResourceServerProps) { @@ -73,7 +84,7 @@ export class UserPoolResourceServer extends Resource implements IUserPoolResourc const resource = new CfnUserPoolResourceServer(this, 'Resource', { identifier: props.identifier, - name: props.name ?? props.identifier, + name: props.userPoolResourceServerName ?? props.identifier, scopes: props.scopes, userPoolId: props.userPool.userPoolId, }); diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts index 1bd3e737ce28f..d16f96c05e4eb 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts @@ -10,7 +10,7 @@ const userPool = new UserPool(stack, 'myuserpool', { userPool.addResourceServer('myserver', { identifier: 'users', - name: 'internal-users', + userPoolResourceServerName: 'internal-users', scopes: [ { scopeName: 'read', diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts index 5f9e351429578..17c2045230ca5 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-resource-server.test.ts @@ -29,9 +29,9 @@ describe('User Pool Resource Server', () => { // WHEN new UserPoolResourceServer(stack, 'Server', { + userPoolResourceServerName: 'internal-users', userPool: pool, identifier: 'users', - name: 'internal-users', }); //THEN From 3ecfee5b3e34d890e6ba8cf123654ca14c075ba0 Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Thu, 29 Oct 2020 10:34:53 -0400 Subject: [PATCH 04/11] Add entry to toc --- packages/@aws-cdk/aws-cognito/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index bd3310eb75602..0e5f176bc5021 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -42,6 +42,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw - [Import](#importing-user-pools) - [Identity Providers](#identity-providers) - [App Clients](#app-clients) + - [Resource Servers](#resource-servers) - [Domains](#domains) ## User Pools From a3e8e7483d9e019938941859b42b9165a9f33d2d Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Thu, 29 Oct 2020 10:35:19 -0400 Subject: [PATCH 05/11] Update packages/@aws-cdk/aws-cognito/README.md Co-authored-by: Niranjan Jayakar --- packages/@aws-cdk/aws-cognito/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 0e5f176bc5021..78c87fb685f3c 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -549,7 +549,14 @@ pool.addClient('app-client', { ### Resource Servers -In your application you may need to attach custom permissions when using oAuth. Resource Servers allow us to provide custom scopes that we can attach to an app client. For example, you might be dealing with user data, and want to give different permissions to different clients. +A resource server is a server for access-protected resources. It handles authenticated requests from an app that has an +access token. See [Defining Resource +Servers](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html) +for more information. + +An application may choose to model custom permissions via OAuth. Resource Servers provide this capability via custom scopes +that are attached to an app client. The following example sets up a resource server for the 'users' resource for two different +app clients and configures the clients to use these scopes. ```ts const pool = new cognito.UserPool(this, 'Pool'); From 85e5dd6b828fc9a0d7d1145a0b04bbfed61f621e Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Thu, 29 Oct 2020 10:35:27 -0400 Subject: [PATCH 06/11] Update packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts Co-authored-by: Niranjan Jayakar --- packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts index 4cf2490b4a5a5..93084f18a25ec 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts @@ -41,7 +41,7 @@ export interface UserPoolResourceServerOptions { /** * A friendly name for the resource server. - * @default - will use the identifier + * @default - same as `identifier` */ readonly userPoolResourceServerName?: string; From 5e0d24140e589482c58e29943611d99b9f4eeabb Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Thu, 29 Oct 2020 11:56:34 -0400 Subject: [PATCH 07/11] test: Add full integ suite --- packages/@aws-cdk/aws-cognito/README.md | 12 ++++-- ...eg.user-pool-resource-server.expected.json | 37 +++++++++++++++++- .../test/integ.user-pool-resource-server.ts | 38 +++++++++++++++++-- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 78c87fb685f3c..3ea3d32fde7da 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -561,7 +561,7 @@ app clients and configures the clients to use these scopes. ```ts const pool = new cognito.UserPool(this, 'Pool'); -pool.addResourceServer('ResourceServer', { +const resourceServer = pool.addResourceServer('ResourceServer', { identifier: 'users', scopes: [ { @@ -575,8 +575,9 @@ pool.addResourceServer('ResourceServer', { ] }); -pool.addClient('read-only-client', { +const readOnlyClient = pool.addClient('read-only-client', { // ... + generateSecret: true, oAuth: { flows: { clientCredentials: true, @@ -585,8 +586,9 @@ pool.addClient('read-only-client', { }, }); -pool.addClient('full-access-client', { +const fullAccessClient = pool.addClient('full-access-client', { // ... + generateSecret: true, oAuth: { flows: { clientCredentials: true, @@ -594,8 +596,12 @@ pool.addClient('full-access-client', { scopes: [OAuthScope.custom('users/*')], }, }); + +readOnlyClient.node.addDependency(resourceServer); +fullAccessClient.node.addDependency(resourceServer); ``` + ### Domains After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json index 0581c6aeafd6c..efcfc7308e3af 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json @@ -34,7 +34,7 @@ "Type": "AWS::Cognito::UserPoolResourceServer", "Properties": { "Identifier": "users", - "Name": "internal-users", + "Name": "users", "UserPoolId": { "Ref": "myuserpool01998219" }, @@ -45,6 +45,41 @@ } ] } + }, + "myuserpoolclientC5FA41EC": { + "Type": "AWS::Cognito::UserPoolClient", + "Properties": { + "UserPoolId": { + "Ref": "myuserpool01998219" + }, + "AllowedOAuthFlows": [ + "client_credentials" + ], + "AllowedOAuthFlowsUserPoolClient": true, + "AllowedOAuthScopes": [ + "users/read" + ], + "ClientName": "users-app", + "GenerateSecret": true, + "SupportedIdentityProviders": [ + "COGNITO" + ] + }, + "DependsOn": [ + "myuserpoolmyserver50C4D8E9" + ] + } + }, + "Outputs": { + "poolid": { + "Value": { + "Ref": "myuserpool01998219" + } + }, + "clientid": { + "Value": { + "Ref": "myuserpoolclientC5FA41EC" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts index d16f96c05e4eb..335ba429fedb0 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts @@ -1,16 +1,23 @@ -import { App, Stack } from '@aws-cdk/core'; -import { UserPool } from '../lib'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { OAuthScope, UserPool } from '../lib'; const app = new App(); const stack = new Stack(app, 'integ-user-pool-resource-server'); +/* + * Stack verification steps: + * Cognito will only allow you to add a custom scope on a user pool client that is defined by a resource server. + * Checking the app client scopes will verify if the resource server was made and is working correctly. + * The exports userPoolId and userPoolClientId are exported here to test + * + * * `aws cognito-idp describe-user-pool-client --user-pool-id $userPoolId --client-id $userPoolClientId` should return "users/read" in "AllowedOAuthScopes" + */ const userPool = new UserPool(stack, 'myuserpool', { userPoolName: 'MyUserPool', }); -userPool.addResourceServer('myserver', { +const resourceServer = userPool.addResourceServer('myserver', { identifier: 'users', - userPoolResourceServerName: 'internal-users', scopes: [ { scopeName: 'read', @@ -18,3 +25,26 @@ userPool.addResourceServer('myserver', { }, ], }); + +const client = userPool.addClient('client', { + userPoolClientName: 'users-app', + generateSecret: true, + oAuth: { + flows: { + clientCredentials: true, + }, + scopes: [ + OAuthScope.custom('users/read'), + ], + }, +}); + +client.node.addDependency(resourceServer); + +new CfnOutput(stack, 'pool-id', { + value: userPool.userPoolId, +}); + +new CfnOutput(stack, 'client-id', { + value: client.userPoolClientId, +}); \ No newline at end of file From 29906f3012ca7dc3e804272c39dd8086cc12307d Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Fri, 30 Oct 2020 11:48:33 -0400 Subject: [PATCH 08/11] Update packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts Co-authored-by: Niranjan Jayakar --- .../@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts index 93084f18a25ec..a42abbc15e08e 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts @@ -80,11 +80,13 @@ export class UserPoolResourceServer extends Resource implements IUserPoolResourc public readonly userPoolResourceServerId: string; constructor(scope: Construct, id: string, props: UserPoolResourceServerProps) { - super(scope, id); + super(scope, id, { + physicalName: props.identifier, + }); const resource = new CfnUserPoolResourceServer(this, 'Resource', { - identifier: props.identifier, - name: props.userPoolResourceServerName ?? props.identifier, + identifier: this.physicalName, + name: props.userPoolResourceServerName ?? this.physicalName, scopes: props.scopes, userPoolId: props.userPool.userPoolId, }); From b79e1df706d1fcedd9103d4fb3ecb1c9cd5d6fd7 Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Fri, 30 Oct 2020 11:48:42 -0400 Subject: [PATCH 09/11] Update packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts Co-authored-by: Niranjan Jayakar --- .../aws-cognito/test/integ.user-pool-resource-server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts index 335ba429fedb0..1ec7291fb3243 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts @@ -7,7 +7,7 @@ const stack = new Stack(app, 'integ-user-pool-resource-server'); /* * Stack verification steps: * Cognito will only allow you to add a custom scope on a user pool client that is defined by a resource server. - * Checking the app client scopes will verify if the resource server was made and is working correctly. + * Checking the app client scopes will verify if the resource server is configured correctly. * The exports userPoolId and userPoolClientId are exported here to test * * * `aws cognito-idp describe-user-pool-client --user-pool-id $userPoolId --client-id $userPoolClientId` should return "users/read" in "AllowedOAuthScopes" @@ -47,4 +47,4 @@ new CfnOutput(stack, 'pool-id', { new CfnOutput(stack, 'client-id', { value: client.userPoolClientId, -}); \ No newline at end of file +}); From acadd39c03ed424b297ebb7616d9e15b2c0b7320 Mon Sep 17 00:00:00 2001 From: Kyle Roach Date: Sun, 8 Nov 2020 00:42:47 -0400 Subject: [PATCH 10/11] feat: Make server/client relation explicit --- packages/@aws-cdk/aws-cognito/README.md | 9 +++------ .../aws-cognito/lib/user-pool-client.ts | 8 ++++++++ ...nteg.user-pool-resource-server.expected.json | 17 ++++++++++++----- .../test/integ.user-pool-resource-server.ts | 6 ++---- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index 3ea3d32fde7da..a3648db980e18 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -561,7 +561,7 @@ app clients and configures the clients to use these scopes. ```ts const pool = new cognito.UserPool(this, 'Pool'); -const resourceServer = pool.addResourceServer('ResourceServer', { +const userServer = pool.addResourceServer('ResourceServer', { identifier: 'users', scopes: [ { @@ -582,7 +582,7 @@ const readOnlyClient = pool.addClient('read-only-client', { flows: { clientCredentials: true, }, - scopes: [OAuthScope.custom('users/read')], + scopes: [OAuthScope.resourceServer(userServer, 'read')], }, }); @@ -593,12 +593,9 @@ const fullAccessClient = pool.addClient('full-access-client', { flows: { clientCredentials: true, }, - scopes: [OAuthScope.custom('users/*')], + scopes: [OAuthScope.resourceServer(userServer, '*')], }, }); - -readOnlyClient.node.addDependency(resourceServer); -fullAccessClient.node.addDependency(resourceServer); ``` diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 630000df485ba..5a0bfec366204 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -2,6 +2,7 @@ import { IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnUserPoolClient } from './cognito.generated'; import { IUserPool } from './user-pool'; +import { IUserPoolResourceServer } from './user-pool-resource-server'; /** * Types of authentication flow @@ -133,6 +134,13 @@ export class OAuthScope { return new OAuthScope(name); } + /** + * Adds a custom scope that's tied to a resource server in your stack + */ + public static resourceServer(server: IUserPoolResourceServer, scopeName: string) { + return new OAuthScope(`${server.userPoolResourceServerId}/${scopeName}`); + } + /** * The name of this scope as recognized by CloudFormation. * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html#cfn-cognito-userpoolclient-allowedoauthscopes diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json index efcfc7308e3af..ffc765b879357 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.expected.json @@ -57,17 +57,24 @@ ], "AllowedOAuthFlowsUserPoolClient": true, "AllowedOAuthScopes": [ - "users/read" + { + "Fn::Join": [ + "", + [ + { + "Ref": "myuserpoolmyserver50C4D8E9" + }, + "/read" + ] + ] + } ], "ClientName": "users-app", "GenerateSecret": true, "SupportedIdentityProviders": [ "COGNITO" ] - }, - "DependsOn": [ - "myuserpoolmyserver50C4D8E9" - ] + } } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts index 1ec7291fb3243..5fb6236625dd2 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts @@ -16,7 +16,7 @@ const userPool = new UserPool(stack, 'myuserpool', { userPoolName: 'MyUserPool', }); -const resourceServer = userPool.addResourceServer('myserver', { +const userServer = userPool.addResourceServer('myserver', { identifier: 'users', scopes: [ { @@ -34,13 +34,11 @@ const client = userPool.addClient('client', { clientCredentials: true, }, scopes: [ - OAuthScope.custom('users/read'), + OAuthScope.resourceServer(userServer, 'read'), ], }, }); -client.node.addDependency(resourceServer); - new CfnOutput(stack, 'pool-id', { value: userPool.userPoolId, }); From 27157264de49edccdcd2064188c201ece2910f8f Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Mon, 9 Nov 2020 12:57:32 +0000 Subject: [PATCH 11/11] ResourceServerScope is now a class --- packages/@aws-cdk/aws-cognito/README.md | 28 ++++---------- .../aws-cognito/lib/user-pool-client.ts | 6 +-- .../lib/user-pool-resource-server.ts | 24 +++++++++++- .../test/integ.user-pool-resource-server.ts | 12 ++---- .../aws-cognito/test/user-pool-client.test.ts | 37 ++++++++++++++++++- 5 files changed, 73 insertions(+), 34 deletions(-) diff --git a/packages/@aws-cdk/aws-cognito/README.md b/packages/@aws-cdk/aws-cognito/README.md index a3648db980e18..5a3982e86ad34 100644 --- a/packages/@aws-cdk/aws-cognito/README.md +++ b/packages/@aws-cdk/aws-cognito/README.md @@ -561,39 +561,27 @@ app clients and configures the clients to use these scopes. ```ts const pool = new cognito.UserPool(this, 'Pool'); +const readOnlyScope = new ResourceServerScope({ scopeName: 'read', scopeDescription: 'Read-only access' }); +const fullAccessScope = new ResourceServerScope({ scopeName: '*', scopeDescription: 'Full access' }); + const userServer = pool.addResourceServer('ResourceServer', { identifier: 'users', - scopes: [ - { - scopeName: 'read', - scopeDescription: 'Read-only access', - }, - { - scopeName: '*', - scopeDescription: 'Full access', - }, - ] + scopes: [ readOnlyScope, fullAccessScope ], }); const readOnlyClient = pool.addClient('read-only-client', { // ... - generateSecret: true, oAuth: { - flows: { - clientCredentials: true, - }, - scopes: [OAuthScope.resourceServer(userServer, 'read')], + // ... + scopes: [ OAuthScope.resourceServer(userServer, readOnlyScope) ], }, }); const fullAccessClient = pool.addClient('full-access-client', { // ... - generateSecret: true, oAuth: { - flows: { - clientCredentials: true, - }, - scopes: [OAuthScope.resourceServer(userServer, '*')], + // ... + scopes: [ OAuthScope.resourceServer(userServer, fullAccessScope) ], }, }); ``` diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index 5a0bfec366204..34e6f13f75cae 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -2,7 +2,7 @@ import { IResource, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnUserPoolClient } from './cognito.generated'; import { IUserPool } from './user-pool'; -import { IUserPoolResourceServer } from './user-pool-resource-server'; +import { IUserPoolResourceServer, ResourceServerScope } from './user-pool-resource-server'; /** * Types of authentication flow @@ -137,8 +137,8 @@ export class OAuthScope { /** * Adds a custom scope that's tied to a resource server in your stack */ - public static resourceServer(server: IUserPoolResourceServer, scopeName: string) { - return new OAuthScope(`${server.userPoolResourceServerId}/${scopeName}`); + public static resourceServer(server: IUserPoolResourceServer, scope: ResourceServerScope) { + return new OAuthScope(`${server.userPoolResourceServerId}/${scope.scopeName}`); } /** diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts index a42abbc15e08e..d78d61e956587 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts @@ -15,9 +15,9 @@ export interface IUserPoolResourceServer extends IResource { } /** - * Options to create a scope for a UserPoolResourceServer + * Props to initialize ResourceServerScope */ -export interface ResourceServerScope { +export interface ResourceServerScopeProps { /** * The name of the scope */ @@ -29,6 +29,26 @@ export interface ResourceServerScope { readonly scopeDescription: string; } +/** + * A scope for ResourceServer + */ +export class ResourceServerScope { + /** + * The name of the scope + */ + public readonly scopeName: string; + + /** + * A description of the scope. + */ + public readonly scopeDescription: string; + + constructor(props: ResourceServerScopeProps) { + this.scopeName = props.scopeName; + this.scopeDescription = props.scopeDescription; + } +} + /** * Options to create a UserPoolResourceServer diff --git a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts index 5fb6236625dd2..8610d7a3e1296 100644 --- a/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts +++ b/packages/@aws-cdk/aws-cognito/test/integ.user-pool-resource-server.ts @@ -1,5 +1,5 @@ import { App, CfnOutput, Stack } from '@aws-cdk/core'; -import { OAuthScope, UserPool } from '../lib'; +import { OAuthScope, ResourceServerScope, UserPool } from '../lib'; const app = new App(); const stack = new Stack(app, 'integ-user-pool-resource-server'); @@ -16,14 +16,10 @@ const userPool = new UserPool(stack, 'myuserpool', { userPoolName: 'MyUserPool', }); +const readScope = new ResourceServerScope({ scopeName: 'read', scopeDescription: 'read only' }); const userServer = userPool.addResourceServer('myserver', { identifier: 'users', - scopes: [ - { - scopeName: 'read', - scopeDescription: 'read only', - }, - ], + scopes: [readScope], }); const client = userPool.addClient('client', { @@ -34,7 +30,7 @@ const client = userPool.addClient('client', { clientCredentials: true, }, scopes: [ - OAuthScope.resourceServer(userServer, 'read'), + OAuthScope.resourceServer(userServer, readScope), ], }, }); diff --git a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts index eeddb55bc1ff1..93c8937dba68b 100644 --- a/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts +++ b/packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts @@ -1,7 +1,7 @@ import { ABSENT } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; -import { OAuthScope, UserPool, UserPoolClient, UserPoolClientIdentityProvider, UserPoolIdentityProvider } from '../lib'; +import { OAuthScope, ResourceServerScope, UserPool, UserPoolClient, UserPoolClientIdentityProvider, UserPoolIdentityProvider } from '../lib'; describe('User Pool Client', () => { test('default setup', () => { @@ -310,6 +310,41 @@ describe('User Pool Client', () => { }); }); + test('OAuth scopes - resource server', () => { + // GIVEN + const stack = new Stack(); + const pool = new UserPool(stack, 'Pool'); + const scope = new ResourceServerScope({ scopeName: 'scope-name', scopeDescription: 'scope-desc' }); + const resourceServer = pool.addResourceServer('ResourceServer', { + identifier: 'resource-server', + scopes: [scope], + }); + + // WHEN + pool.addClient('Client', { + oAuth: { + flows: { clientCredentials: true }, + scopes: [ + OAuthScope.resourceServer(resourceServer, scope), + ], + }, + }); + + // THEN + expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', { + AllowedOAuthScopes: [ + { + 'Fn::Join': [ + '', [ + stack.resolve(resourceServer.userPoolResourceServerId), + '/scope-name', + ], + ], + }, + ], + }); + }); + test('OAuthScope - openid is included when email or phone is specified', () => { // GIVEN const stack = new Stack();