Skip to content

Commit

Permalink
feat(cognito): user pool identity provider with support for Facebook …
Browse files Browse the repository at this point in the history
…& Amazon (#8134)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
Niranjan Jayakar authored Jun 3, 2020
1 parent 6cf458a commit 1ad919f
Show file tree
Hide file tree
Showing 16 changed files with 677 additions and 7 deletions.
51 changes: 49 additions & 2 deletions packages/@aws-cdk/aws-cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aw
- [Emails](#emails)
- [Lambda Triggers](#lambda-triggers)
- [Import](#importing-user-pools)
- [Identity Providers](#identity-providers)
- [App Clients](#app-clients)
- [Domains](#domains)

Expand Down Expand Up @@ -334,6 +335,36 @@ const otherAwesomePool = UserPool.fromUserPoolArn(stack, 'other-awesome-user-poo
'arn:aws:cognito-idp:eu-west-1:123456789012:userpool/us-east-1_mtRyYQ14D');
```

### Identity Providers

Users that are part of a user pool can sign in either directly through a user pool, or federate through a third-party
identity provider. Once configured, the Cognito backend will take care of integrating with the third-party provider.
Read more about [Adding User Pool Sign-in Through a Third
Party](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-identity-federation.html).

The following third-party identity providers are currentlhy supported in the CDK -

* [Login With Amazon](https://developer.amazon.com/apps-and-games/login-with-amazon)
* [Facebook Login](https://developers.facebook.com/docs/facebook-login/)

The following code configures a user pool to federate with the third party provider, 'Login with Amazon'. The identity
provider needs to be configured with a set of credentials that the Cognito backend can use to federate with the
third-party identity provider.

```ts
const userpool = new UserPool(stack, 'Pool');

const provider = new UserPoolIdentityProviderAmazon(stack, 'Amazon', {
clientId: 'amzn-client-id',
clientSecret: 'amzn-client-secret',
userPool: userpool,
});
```

In order to allow users to sign in with a third-party identity provider, the app client that faces the user should be
configured to use the identity provider. See [App Clients](#app-clients) section to know more about App Clients.
The identity providers should be configured on `identityProviders` property available on the `UserPoolClient` construct.

### App Clients

An app is an entity within a user pool that has permission to call unauthenticated APIs (APIs that do not have an
Expand Down Expand Up @@ -417,6 +448,22 @@ pool.addClient('app-client', {
});
```

All identity providers created in the CDK app are automatically registered into the corresponding user pool. All app
clients created in the CDK have all of the identity providers enabled by default. The 'Cognito' identity provider,
that allows users to register and sign in directly with the Cognito user pool, is also enabled by default.
Alternatively, the list of supported identity providers for a client can be explicitly specified -

```ts
const pool = new UserPool(this, 'Pool');
pool.addClient('app-client', {
// ...
supportedIdentityProviders: [
UserPoolClientIdentityProvider.AMAZON,
UserPoolClientIdentityProvider.COGNITO,
]
});
```

### 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 Expand Up @@ -446,7 +493,7 @@ pool.addDomain('CustomDomain', {

Read more about [Using the Amazon Cognito
Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain-prefix.html) and [Using Your Own
Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html)
Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-add-custom-domain.html).

The `signInUrl()` methods returns the fully qualified URL to the login page for the user pool. This page comes from the
hosted UI configured with Cognito. Learn more at [Hosted UI with the Amazon Cognito
Expand Down Expand Up @@ -474,4 +521,4 @@ const domain = userpool.addDomain('Domain', {
const signInUrl = domain.signInUrl(client, {
redirectUrl: 'https://myapp.com/home', // must be a URL configured under 'callbackUrls' with the client
})
```
```
4 changes: 3 additions & 1 deletion packages/@aws-cdk/aws-cognito/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export * from './cognito.generated';
export * from './user-pool';
export * from './user-pool-attr';
export * from './user-pool-client';
export * from './user-pool-domain';
export * from './user-pool-domain';
export * from './user-pool-idp';
export * from './user-pool-idps';
61 changes: 60 additions & 1 deletion packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,43 @@ export class OAuthScope {
}
}

/**
* Identity providers supported by the UserPoolClient
*/
export class UserPoolClientIdentityProvider {
/**
* Allow users to sign in using 'Facebook Login'.
* A `UserPoolIdentityProviderFacebook` must be attached to the user pool.
*/
public static readonly FACEBOOK = new UserPoolClientIdentityProvider('Facebook');

/**
* Allow users to sign in using 'Login With Amazon'.
* A `UserPoolIdentityProviderAmazon` must be attached to the user pool.
*/
public static readonly AMAZON = new UserPoolClientIdentityProvider('LoginWithAmazon');

/**
* Allow users to sign in directly as a user of the User Pool
*/
public static readonly COGNITO = new UserPoolClientIdentityProvider('COGNITO');

/**
* Specify a provider not yet supported by the CDK.
* @param name name of the identity provider as recognized by CloudFormation property `SupportedIdentityProviders`
*/
public static custom(name: string) {
return new UserPoolClientIdentityProvider(name);
}

/** The name of the identity provider as recognized by CloudFormation property `SupportedIdentityProviders` */
public readonly name: string;

private constructor(name: string) {
this.name = name;
}
}

/**
* Options to create a UserPoolClient
*/
Expand Down Expand Up @@ -182,6 +219,15 @@ export interface UserPoolClientOptions {
* @default true for new stacks
*/
readonly preventUserExistenceErrors?: boolean;

/**
* The list of identity providers that users should be able to use to sign in using this client.
*
* @default - supports all identity providers that are registered with the user pool. If the user pool and/or
* identity providers are imported, either specify this option explicitly or ensure that the identity providers are
* registered with the user pool using the `UserPool.registerIdentityProvider()` API.
*/
readonly supportedIdentityProviders?: UserPoolClientIdentityProvider[];
}

/**
Expand Down Expand Up @@ -262,7 +308,7 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
callbackUrLs: callbackUrls && callbackUrls.length > 0 ? callbackUrls : undefined,
allowedOAuthFlowsUserPoolClient: props.oAuth ? true : undefined,
preventUserExistenceErrors: this.configurePreventUserExistenceErrors(props.preventUserExistenceErrors),
supportedIdentityProviders: [ 'COGNITO' ],
supportedIdentityProviders: this.configureIdentityProviders(props),
});

this.userPoolClientId = resource.ref;
Expand Down Expand Up @@ -326,4 +372,17 @@ export class UserPoolClient extends Resource implements IUserPoolClient {
}
return prevent ? 'ENABLED' : 'LEGACY';
}

private configureIdentityProviders(props: UserPoolClientProps): string[] | undefined {
let providers: string[];
if (!props.supportedIdentityProviders) {
const providerSet = new Set(props.userPool.identityProviders.map((p) => p.providerName));
providerSet.add('COGNITO');
providers = Array.from(providerSet);
} else {
providers = props.supportedIdentityProviders.map((p) => p.name);
}
if (providers.length === 0) { return undefined; }
return Array.from(providers);
}
}
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Construct, IResource, Resource } from '@aws-cdk/core';

/**
* Represents a UserPoolIdentityProvider
*/
export interface IUserPoolIdentityProvider extends IResource {
/**
* The primary identifier of this identity provider
* @attribute
*/
readonly providerName: string;
}

/**
* User pool third-party identity providers
*/
export class UserPoolIdentityProvider {

/**
* Import an existing UserPoolIdentityProvider
*/
public static fromProviderName(scope: Construct, id: string, providerName: string): IUserPoolIdentityProvider {
class Import extends Resource implements IUserPoolIdentityProvider {
public readonly providerName: string = providerName;
}

return new Import(scope, id);
}

private constructor() {}
}
52 changes: 52 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/amazon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Construct } from '@aws-cdk/core';
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
import { UserPoolIdentityProviderBase, UserPoolIdentityProviderProps } from './base';

/**
* Properties to initialize UserPoolAmazonIdentityProvider
*/
export interface UserPoolIdentityProviderAmazonProps extends UserPoolIdentityProviderProps {
/**
* The client id recognized by 'Login with Amazon' APIs.
* @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier
*/
readonly clientId: string;
/**
* The client secret to be accompanied with clientId for 'Login with Amazon' APIs to authenticate the client.
* @see https://developer.amazon.com/docs/login-with-amazon/security-profile.html#client-identifier
*/
readonly clientSecret: string;
/**
* The types of user profile data to obtain for the Amazon profile.
* @see https://developer.amazon.com/docs/login-with-amazon/customer-profile.html
* @default [ profile ]
*/
readonly scopes?: string[];
}

/**
* Represents a identity provider that integrates with 'Login with Amazon'
* @resource AWS::Cognito::UserPoolIdentityProvider
*/
export class UserPoolIdentityProviderAmazon extends UserPoolIdentityProviderBase {
public readonly providerName: string;

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

const scopes = props.scopes ?? [ 'profile' ];

const resource = new CfnUserPoolIdentityProvider(this, 'Resource', {
userPoolId: props.userPool.userPoolId,
providerName: 'LoginWithAmazon', // must be 'LoginWithAmazon' when the type is 'LoginWithAmazon'
providerType: 'LoginWithAmazon',
providerDetails: {
client_id: props.clientId,
client_secret: props.clientSecret,
authorize_scopes: scopes.join(' '),
},
});

this.providerName = super.getResourceNameAttribute(resource.ref);
}
}
25 changes: 25 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Construct, Resource } from '@aws-cdk/core';
import { IUserPool } from '../user-pool';
import { IUserPoolIdentityProvider } from '../user-pool-idp';

/**
* Properties to create a new instance of UserPoolIdentityProvider
*/
export interface UserPoolIdentityProviderProps {
/**
* The user pool to which this construct provides identities.
*/
readonly userPool: IUserPool;
}

/**
* Options to integrate with the various social identity providers.
*/
export abstract class UserPoolIdentityProviderBase extends Resource implements IUserPoolIdentityProvider {
public abstract readonly providerName: string;

public constructor(scope: Construct, id: string, props: UserPoolIdentityProviderProps) {
super(scope, id);
props.userPool.registerIdentityProvider(this);
}
}
57 changes: 57 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/facebook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Construct } from '@aws-cdk/core';
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
import { UserPoolIdentityProviderBase, UserPoolIdentityProviderProps } from './base';

/**
* Properties to initialize UserPoolFacebookIdentityProvider
*/
export interface UserPoolIdentityProviderFacebookProps extends UserPoolIdentityProviderProps {
/**
* The client id recognized by Facebook APIs.
*/
readonly clientId: string;
/**
* The client secret to be accompanied with clientUd for Facebook to authenticate the client.
* @see https://developers.facebook.com/docs/facebook-login/security#appsecret
*/
readonly clientSecret: string;
/**
* The list of facebook permissions to obtain for getting access to the Facebook profile.
* @see https://developers.facebook.com/docs/facebook-login/permissions
* @default [ public_profile ]
*/
readonly scopes?: string[];
/**
* The Facebook API version to use
* @default - to the oldest version supported by Facebook
*/
readonly apiVersion?: string;
}

/**
* Represents a identity provider that integrates with 'Facebook Login'
* @resource AWS::Cognito::UserPoolIdentityProvider
*/
export class UserPoolIdentityProviderFacebook extends UserPoolIdentityProviderBase {
public readonly providerName: string;

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

const scopes = props.scopes ?? [ 'public_profile' ];

const resource = new CfnUserPoolIdentityProvider(this, 'Resource', {
userPoolId: props.userPool.userPoolId,
providerName: 'Facebook', // must be 'Facebook' when the type is 'Facebook'
providerType: 'Facebook',
providerDetails: {
client_id: props.clientId,
client_secret: props.clientSecret,
authorize_scopes: scopes.join(','),
api_version: props.apiVersion,
},
});

this.providerName = super.getResourceNameAttribute(resource.ref);
}
}
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './base';
export * from './amazon';
export * from './facebook';
Loading

0 comments on commit 1ad919f

Please sign in to comment.