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 pools - sign in with apple #13160

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cognito/README.md
Expand Up @@ -418,6 +418,7 @@ The following third-party identity providers are currently 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/)
- [Google Login](https://developers.google.com/identity/sign-in/web/sign-in)
- [Sign In With Apple](https://developer.apple.com/sign-in-with-apple/get-started/)

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
Expand Down
6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts
Expand Up @@ -157,6 +157,12 @@ export class OAuthScope {
* Identity providers supported by the UserPoolClient
*/
export class UserPoolClientIdentityProvider {
/**
* Allow users to sign in using 'Sign In With Apple'.
* A `UserPoolIdentityProviderApple` must be attached to the user pool.
*/
public static readonly APPLE = new UserPoolClientIdentityProvider('SignInWithApple');

/**
* Allow users to sign in using 'Facebook Login'.
* A `UserPoolIdentityProviderFacebook` must be attached to the user pool.
Expand Down
63 changes: 63 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/apple.ts
@@ -0,0 +1,63 @@
import { Construct } from 'constructs';
import { CfnUserPoolIdentityProvider } from '../cognito.generated';
import { UserPoolIdentityProviderProps } from './base';
import { UserPoolIdentityProviderBase } from './private/user-pool-idp-base';

/**
* Properties to initialize UserPoolAppleIdentityProvider
*/
export interface UserPoolIdentityProviderAppleProps extends UserPoolIdentityProviderProps {
/**
* The client id recognized by Apple APIs.
* @see https://developer.apple.com/documentation/sign_in_with_apple/clientconfigi/3230948-clientid
*/
readonly clientId: string;
/**
* The teamId for Apple APIs to authenticate the client.
*/
readonly teamId: string;
/**
* The keyId (of the same key, which content has to be later supplied as `privateKey`) for Apple APIs to authenticate the client.
*/
readonly keyId: string;
/**
* The privateKey content for Apple APIs to authenticate the client.
*/
readonly privateKey: string;
/**
* The list of apple permissions to obtain for getting access to the apple profile
* @see https://developer.apple.com/documentation/sign_in_with_apple/clientconfigi/3230955-scope
* @default [ name ]
*/
readonly scopes?: string[];
}

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

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

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

const resource = new CfnUserPoolIdentityProvider(this, 'Resource', {
userPoolId: props.userPool.userPoolId,
providerName: 'SignInWithApple', // must be 'SignInWithApple' when the type is 'SignInWithApple'
providerType: 'SignInWithApple',
providerDetails: {
client_id: props.clientId,
team_id: props.teamId,
key_id: props.keyId,
private_key: props.privateKey,
authorize_scopes: scopes.join(' '),
},
attributeMapping: super.configureAttributeMapping(),
});

this.providerName = super.getResourceNameAttribute(resource.ref);
}
}
9 changes: 9 additions & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/base.ts
Expand Up @@ -4,6 +4,15 @@ import { IUserPool } from '../user-pool';
* An attribute available from a third party identity provider.
*/
export class ProviderAttribute {
/** The email attribute provided by Apple */
public static readonly APPLE_EMAIL = new ProviderAttribute('email');
/** The name attribute provided by Apple */
public static readonly APPLE_NAME = new ProviderAttribute('name');
/** The first name attribute provided by Apple */
public static readonly APPLE_FIRST_NAME = new ProviderAttribute('firstName');
/** The last name attribute provided by Apple */
public static readonly APPLE_LAST_NAME = new ProviderAttribute('lastName');

/** The user id attribute provided by Amazon */
public static readonly AMAZON_USER_ID = new ProviderAttribute('user_id');
/** The email attribute provided by Amazon */
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cognito/lib/user-pool-idps/index.ts
@@ -1,4 +1,5 @@
export * from './base';
export * from './apple';
export * from './amazon';
export * from './facebook';
export * from './google';
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-cognito/package.json
Expand Up @@ -110,7 +110,8 @@
"props-physical-name:@aws-cdk/aws-cognito.UserPoolDomainProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderFacebookProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAmazonProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderGoogleProps"
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderGoogleProps",
"props-physical-name:@aws-cdk/aws-cognito.UserPoolIdentityProviderAppleProps"
]
},
"stability": "stable",
Expand Down
@@ -0,0 +1,118 @@
{
"Resources": {
"pool056F3F7E": {
"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 {####}",
"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 {####}"
}
},
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
"poolclient2623294C": {
"Type": "AWS::Cognito::UserPoolClient",
"Properties": {
"UserPoolId": {
"Ref": "pool056F3F7E"
},
"AllowedOAuthFlows": [
"implicit",
"code"
],
"AllowedOAuthFlowsUserPoolClient": true,
"AllowedOAuthScopes": [
"profile",
"phone",
"email",
"openid",
"aws.cognito.signin.user.admin"
],
"CallbackURLs": [
"https://example.com"
],
"SupportedIdentityProviders": [
{
"Ref": "apple9B5408AC"
},
"COGNITO"
]
}
},
"pooldomain430FA744": {
"Type": "AWS::Cognito::UserPoolDomain",
"Properties": {
"Domain": "nija-test-pool",
"UserPoolId": {
"Ref": "pool056F3F7E"
}
}
},
"apple9B5408AC": {
"Type": "AWS::Cognito::UserPoolIdentityProvider",
"Properties": {
"ProviderName": "SignInWithApple",
"ProviderType": "SignInWithApple",
"UserPoolId": {
"Ref": "pool056F3F7E"
},
"AttributeMapping": {
"family_name": "lastName",
"given_name": "firstName"
},
"ProviderDetails": {
"client_id": "com.amzn.cdk",
"team_id": "CDKTEAMCDK",
"key_id": "CDKKEYCDK1",
"private_key": "PRIV_KEY_CDK",
"authorize_scopes": "email name"
}
}
}
},
"Outputs": {
"SignInLink": {
"Value": {
"Fn::Join": [
"",
[
"https://",
{
"Ref": "pooldomain430FA744"
},
".auth.",
{
"Ref": "AWS::Region"
},
".amazoncognito.com/login?client_id=",
{
"Ref": "poolclient2623294C"
},
"&response_type=code&redirect_uri=https://example.com"
]
]
}
}
}
}
41 changes: 41 additions & 0 deletions packages/@aws-cdk/aws-cognito/test/integ.user-pool-idp.apple.ts
@@ -0,0 +1,41 @@
import { App, CfnOutput, RemovalPolicy, Stack } from '@aws-cdk/core';
import { ProviderAttribute, UserPool, UserPoolIdentityProviderApple } from '../lib';

/*
* Stack verification steps
* * Visit the URL provided by stack output 'SignInLink' in a browser, and verify the 'Sign In With Apple' link shows up.
* * If you plug in valid 'Sign In With Apple' credentials, the federated log in should work.
*/
const app = new App();
const stack = new Stack(app, 'integ-user-pool-idp-apple');

const userpool = new UserPool(stack, 'pool', {
removalPolicy: RemovalPolicy.DESTROY,
});

new UserPoolIdentityProviderApple(stack, 'apple', {
userPool: userpool,
clientId: 'com.amzn.cdk',
teamId: 'CDKTEAMCDK',
keyId: 'CDKKEYCDK1',
privateKey: 'PRIV_KEY_CDK',
scopes: ['email', 'name'],
attributeMapping: {
familyName: ProviderAttribute.APPLE_LAST_NAME,
givenName: ProviderAttribute.APPLE_FIRST_NAME,
},
});

const client = userpool.addClient('client');

const domain = userpool.addDomain('domain', {
cognitoDomain: {
domainPrefix: 'nija-test-pool',
},
});

new CfnOutput(stack, 'SignInLink', {
value: domain.signInUrl(client, {
redirectUri: 'https://example.com',
}),
});
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-cognito/test/user-pool-client.test.ts
Expand Up @@ -487,13 +487,14 @@ describe('User Pool Client', () => {
UserPoolClientIdentityProvider.FACEBOOK,
UserPoolClientIdentityProvider.AMAZON,
UserPoolClientIdentityProvider.GOOGLE,
UserPoolClientIdentityProvider.APPLE,
],
});

// THEN
expect(stack).toHaveResource('AWS::Cognito::UserPoolClient', {
ClientName: 'AllEnabled',
SupportedIdentityProviders: ['COGNITO', 'Facebook', 'LoginWithAmazon', 'Google'],
SupportedIdentityProviders: ['COGNITO', 'Facebook', 'LoginWithAmazon', 'Google', 'SignInWithApple'],
});
});

Expand Down