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(cognito): user pool resource server #11118

Merged
merged 14 commits into from
Nov 9, 2020
Merged
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
41 changes: 41 additions & 0 deletions packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -549,6 +550,46 @@ pool.addClient('app-client', {
});
```

### Resource Servers
iRoachie marked this conversation as resolved.
Show resolved Hide resolved

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');

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: [ readOnlyScope, fullAccessScope ],
});

const readOnlyClient = pool.addClient('read-only-client', {
// ...
oAuth: {
// ...
scopes: [ OAuthScope.resourceServer(userServer, readOnlyScope) ],
},
});

const fullAccessClient = pool.addClient('full-access-client', {
// ...
oAuth: {
// ...
scopes: [ OAuthScope.resourceServer(userServer, fullAccessScope) ],
},
});
```


### Domains

After setting up an [app client](#app-clients), the address for the user pool's sign-up and sign-in webpages can be
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-cognito/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export * from './user-pool-attr';
export * from './user-pool-client';
export * from './user-pool-domain';
export * from './user-pool-idp';
export * from './user-pool-idps';
export * from './user-pool-idps';
export * from './user-pool-resource-server';
8 changes: 8 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, ResourceServerScope } from './user-pool-resource-server';

/**
* Types of authentication flow
Expand Down Expand Up @@ -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, scope: ResourceServerScope) {
return new OAuthScope(`${server.userPoolResourceServerId}/${scope.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
Expand Down
116 changes: 116 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-resource-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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;
}

/**
* Props to initialize ResourceServerScope
*/
export interface ResourceServerScopeProps {
/**
* The name of the scope
*/
readonly scopeName: string;

/**
* A description of the scope.
*/
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
*/
export interface UserPoolResourceServerOptions {
/**
* A unique resource server identifier for the resource server.
*/
readonly identifier: string;

/**
* A friendly name for the resource server.
* @default - same as `identifier`
*/
readonly userPoolResourceServerName?: string;

/**
* Oauth scopes
* @default - No scopes will be added
*/
readonly scopes?: ResourceServerScope[];
}

/**
* 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 {
/**
* 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) {
super(scope, id, {
physicalName: props.identifier,
});

const resource = new CfnUserPoolResourceServer(this, 'Resource', {
identifier: this.physicalName,
name: props.userPoolResourceServerName ?? this.physicalName,
scopes: props.scopes,
userPoolId: props.userPool.userPoolId,
});

this.userPoolResourceServerId = resource.ref;
iRoachie marked this conversation as resolved.
Show resolved Hide resolved
}
}
14 changes: 14 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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.
Expand Down Expand Up @@ -601,6 +602,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.
*/
Expand All @@ -626,6 +633,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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{
"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": "users",
"UserPoolId": {
"Ref": "myuserpool01998219"
},
"Scopes": [
{
"ScopeDescription": "read only",
"ScopeName": "read"
}
]
}
},
"myuserpoolclientC5FA41EC": {
"Type": "AWS::Cognito::UserPoolClient",
"Properties": {
"UserPoolId": {
"Ref": "myuserpool01998219"
},
"AllowedOAuthFlows": [
"client_credentials"
],
"AllowedOAuthFlowsUserPoolClient": true,
"AllowedOAuthScopes": [
{
"Fn::Join": [
"",
[
{
"Ref": "myuserpoolmyserver50C4D8E9"
},
"/read"
]
]
}
],
"ClientName": "users-app",
"GenerateSecret": true,
"SupportedIdentityProviders": [
"COGNITO"
]
}
}
},
"Outputs": {
"poolid": {
"Value": {
"Ref": "myuserpool01998219"
}
},
"clientid": {
"Value": {
"Ref": "myuserpoolclientC5FA41EC"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { App, CfnOutput, Stack } from '@aws-cdk/core';
import { OAuthScope, ResourceServerScope, UserPool } from '../lib';

const app = new App();
const stack = new Stack(app, 'integ-user-pool-resource-server');

iRoachie marked this conversation as resolved.
Show resolved Hide resolved
/*
* 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 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"
*/
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: [readScope],
});

const client = userPool.addClient('client', {
userPoolClientName: 'users-app',
generateSecret: true,
oAuth: {
flows: {
clientCredentials: true,
},
scopes: [
OAuthScope.resourceServer(userServer, readScope),
],
},
});

new CfnOutput(stack, 'pool-id', {
value: userPool.userPoolId,
});

new CfnOutput(stack, 'client-id', {
value: client.userPoolClientId,
});
Loading