Skip to content

Commit

Permalink
feat(cognito): user pools - sign in with apple (#13160)
Browse files Browse the repository at this point in the history
Added Sign In With Apple provider to `@aws-cdk/aws-cognito`. That's my first PR here, so bear with me, I hope I haven't made any mistakes, I've been following the docs carefully :)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
piotrmoszkowicz committed Mar 3, 2021
1 parent 9e6dc6b commit b965589
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 2 deletions.
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

0 comments on commit b965589

Please sign in to comment.