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): allow to set read and write attributes in Cognito UserPoolClient #7607

Conversation

tom139
Copy link
Contributor

@tom139 tom139 commented Apr 25, 2020

Commit Message

feat(cognito): allow to set read and write attributes in Cognito UserPoolClient

Add ability to specify which attributes each App Client can read or write.

closes #7407

End Commit Message


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

When creating a userpool client, the user can now specify which
attributes (custom or standard) the app client will be able to read
or write.

Closes aws#7407
Add section to explain hot to make specific attributes available for
read and write to specific clients.

Fixes aws#7407
Copy link
Contributor

@nija-at nija-at left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for submitting this PR.

I have some comments on this below.

packages/@aws-cdk/aws-cognito/README.md Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/README.md Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/README.md Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/README.md Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
Co-authored-by: Niranjan Jayakar <nija@amazon.com>
@mergify mergify bot dismissed nija-at’s stale review May 3, 2020 10:32

Pull request has been modified.

tom139 and others added 4 commits May 3, 2020 12:36
Copy link
Contributor

@nija-at nija-at left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address existing comments.

@arnulfojr
Copy link
Contributor

any updates?

Initial work to add the notion of set of attributes (mixed standard
and custom)

fixes aws#7407
in userpool client

Allow to set read and write attributes in Cognito UserPoolClient

fixes  aws#7407
… tom139/cognito-userpool-client-set-readwrite-attributes
@gitpod-io
Copy link

gitpod-io bot commented Dec 14, 2020

@mergify mergify bot dismissed nija-at’s stale review December 14, 2020 22:51

Pull request has been modified.

@tom139
Copy link
Contributor Author

tom139 commented Dec 14, 2020

Sorry for making you wait so long. 🙏🏽

I started from scratch with the approach suggested by @nija-at inspired by AccessLogFormat. The main change is the addition of the AttributeSet class that represent a set of attributes (both standard and custom).

Naming of the methods may be misleading, but I couldn't think anything better and am open to suggestion.

Usage is:

const pool = new cognito.UserPool(this, 'Pool');
pool.addClient('app-client', {
  // ...
  readAttributes: AttributeSet.allStandard(['custom:my_attribute_1', 'custom:my_attribute_2']),
  writeAttributes: AttributeSet.from({name: true, locale: true}, ['custom:my_attribute_1']),
});

@nija-at
Copy link
Contributor

nija-at commented Dec 18, 2020

Hey @tom139 -

I'm going to be on vacation for the next couple of weeks for the holidays. I'll take a look at this once I'm back.
Happy holidays!

@tom139
Copy link
Contributor Author

tom139 commented Dec 20, 2020

Have a nice time and stay safe! :)

Copy link
Contributor

@nija-at nija-at left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating this PR. Some questions, comments and suggestions below.

packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
/**
* This interface contains all standard attributes recognized by Cognito
* from https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html
* including `preferred_email` and `preferred_phone_number`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious: why are preferred_email and preferred_number_number special?
They don't feature as standard attributes in the cognito docs. why are they considered as standard?

Copy link
Contributor Author

@tom139 tom139 Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. It is not preferred_email and preferred_phone_number to be special, it is email_verified and phone_number_verified.

These are special because are not directly mentioned in the list at the linked page but are present in the cited OpenID Connect Specification and are in fact present among the other standard attributes.

These two attributes are the missing ones from the cited StandardAttributes interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are in fact present among the other standard attributes

What do you mean by this? Were you able to confirm that they are actually supported by Cognito, but just missing in the documentation?

If that's the case, we should just add these to StandardAttributes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have conducted some tests and I confirm Configuring User Pool is missing two attributes: email_verified and phone_number_verified.

Even though it states:

You get a set of default attributes, called "standard attributes," with all user pools. You can also add custom attributes to your user pool definition in the AWS Management Console. This topic describes those attributes in detail and gives you tips on how to set up your user pool.

and does not list them under standard attributes, the following call:

❯ aws cognito-idp create-user-pool --cli-input-json file://userpool.json
❯ cat userpool.json 
{
    "PoolName": "test",
    "Policies": {
        "PasswordPolicy": {
            "MinimumLength": 10,
            "RequireUppercase": true,
            "RequireLowercase": true,
            "RequireNumbers": true,
            "RequireSymbols": true,
            "TemporaryPasswordValidityDays": 10
        }
    },
    "AutoVerifiedAttributes": [
        "email"
    ],
    "UsernameAttributes": [
        "email"
    ],
    "Schema": [
        {
            "Name": "email",
            "AttributeDataType": "String",
            "Mutable": true,
            "Required": true,
            "StringAttributeConstraints": {
                "MinLength": "5",
                "MaxLength": "2048"
            }
        },
        {
            "Name": "email_verified",
            "AttributeDataType": "Boolean",
            "Mutable": true,
            "Required": true
        }
    ],
    "UsernameConfiguration": {
        "CaseSensitive": true
    }
}

works as expected, setting both email and email_verified as required and mutable attributes.

The UI Console has the same problem as it does not show (phone_number|email)_verified in the attribute list:
image

I filed feedbacks for the documentation and the web ui pages.

That said, I will add those 2 attributes to the StandardAttributes interface, so StandardAttributes and StandardAttributesMask will have the same properties.

packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/README.md Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts Outdated Show resolved Hide resolved
@mergify mergify bot dismissed nija-at’s stale review January 12, 2021 23:41

Pull request has been modified.

Co-authored-by: Niranjan Jayakar <nija@amazon.com>
Copy link
Contributor Author

@tom139 tom139 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I addressed everything except setting up a test for detecting drifts between StandardAttributes and StandardAttributesMask.

packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@nija-at nija-at left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks closer. A few more comments. Hopefully the last round of review before merging 😊

packages/@aws-cdk/aws-cognito/lib/user-pool-attr.ts Outdated Show resolved Hide resolved
/**
* Creates an empty ClientAttributes
*/
public static empty(): ClientAttributes {
Copy link
Contributor

@nija-at nija-at Jan 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need the ClientAttributes.empty() method? Customers can just use the constructor new ClientAttributes(), no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much nicer 👍🏽 I did the refactor.

Comment on lines 579 to 596
The default behaviour is to allow read and write permissions on all attributes.

```ts
const pool = new cognito.UserPool(this, 'Pool');
// create a set of attributes that the client will be allowed to set
const clientWriteAttributes = ClientAttributes.empty()
.withStandardAttributes({name: true, email: true})
.withCustomAttributes(['favouritePizza']);
// read attributes are usually more than the writable ones
const clientReadAttributes = clientWriteAttributes
.withStandardAttributes({emailVerified: true})
.withCustomAttributes(['pointsEarned']);
pool.addClient('app-client', {
// ...
readAttributes: clientReadAttributes,
writeAttributes: clientWriteAttributes,
});
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight tweaks to the documentation.

Suggested change
The default behaviour is to allow read and write permissions on all attributes.
```ts
const pool = new cognito.UserPool(this, 'Pool');
// create a set of attributes that the client will be allowed to set
const clientWriteAttributes = ClientAttributes.empty()
.withStandardAttributes({name: true, email: true})
.withCustomAttributes(['favouritePizza']);
// read attributes are usually more than the writable ones
const clientReadAttributes = clientWriteAttributes
.withStandardAttributes({emailVerified: true})
.withCustomAttributes(['pointsEarned']);
pool.addClient('app-client', {
// ...
readAttributes: clientReadAttributes,
writeAttributes: clientWriteAttributes,
});
```
The default behaviour is to allow read and write permissions on all attributes. The following code shows how this can be configured for a client.
```ts
const pool = new cognito.UserPool(this, 'Pool');
const clientWriteAttributes = ClientAttributes.empty()
.withStandardAttributes({name: true, email: true})
.withCustomAttributes(['favouritePizza']);
const clientReadAttributes = clientWriteAttributes
.withStandardAttributes({emailVerified: true})
.withCustomAttributes(['pointsEarned']);
pool.addClient('app-client', {
// ...
readAttributes: clientReadAttributes,
writeAttributes: clientWriteAttributes,
});
```

and replace it with (new ClientAttribute())
@mergify mergify bot dismissed nija-at’s stale review January 18, 2021 21:53

Pull request has been modified.

Copy link
Contributor Author

@tom139 tom139 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the time spent reviewing this!

Just a question more: am I supposed to hit "Resolve Conversation" when I think I addressed the issue, or should I let you do it?

/**
* Creates an empty ClientAttributes
*/
public static empty(): ClientAttributes {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much nicer 👍🏽 I did the refactor.

@nija-at
Copy link
Contributor

nija-at commented Jan 20, 2021

am I supposed to hit "Resolve Conversation" when I think I addressed the issue

Clicking 'resolve conversation' is fine once you've addressed it 😊

@mergify
Copy link
Contributor

mergify bot commented Jan 20, 2021

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 49f6eea
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify
Copy link
Contributor

mergify bot commented Jan 20, 2021

Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork).

@mergify mergify bot merged commit 552e1e9 into aws:master Jan 20, 2021
mohanrajendran pushed a commit to mohanrajendran/aws-cdk that referenced this pull request Jan 24, 2021
…PoolClient (aws#7607)

### Commit Message

feat(cognito): allow to set read and write attributes in Cognito UserPoolClient

Add ability to specify which attributes each App Client can read or write.

closes aws#7407

### End Commit Message
----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@russelldavis
Copy link

russelldavis commented Jan 25, 2021

@tom139 @nija-at this is great, thank you! One question: why does withStandardAttributes take in an object like {name: true, email: true}, instead of a simple list like ["name", "email"]? It's also inconsistent with withCustomAttributes, which takes in a list of names.

@russelldavis
Copy link

Also, regarding #7607 (comment), I'd like to express interest in the additional static method you discussed, which would prepopulate all of the standard attributes.

A common use case (I think) is to allow write access to everything except for a small number of custom attributes. To do this now, I'll have to manually list all of the standard attributes, and keep my manual list up to date if those ever change.

Thanks again.

@russelldavis
Copy link

To follow up on that last comment, here's the code I ended up writing:

const standardWriteAttributes = new ClientAttributes().withStandardAttributes({
  address: true,
  birthdate: true,
  email: true,
  familyName: true,
  gender: true,
  givenName: true,
  locale: true,
  middleName: true,
  fullname: true,
  nickname: true,
  phoneNumber: true,
  profilePicture: true,
  preferredUsername: true,
  profilePage: true,
  timezone: true,
  lastUpdateTime: true,
  website: true,
});

const standardReadAttributes = standardWriteAttributes.withStandardAttributes({
  emailVerified: true,
  phoneNumberVerified: true,
});

new UserPoolClient(this, "UserPoolClient", {
  // Allow everything except for writing custom attributes.
  readAttributes: standardReadAttributes.withCustomAttributes(...Object.keys(customAttributes)),
  writeAttributes: standardWriteAttributes,
});

Would be great if those lists were exposed, perhaps via withAllStandardReadAttributes() and withAllStandardWriteAttributes() functions.

Note that they need to be separate lists, because emailVerified and phoneNumberVerified cannot be set as writable. I learned this the hard way when cdk deploy gave me an error of "Invalid write attributes specified" without telling me which ones were invalid (another reason having these predefined would be helpful.)

@tom139 tom139 deleted the tom139/cognito-userpool-client-set-readwrite-attributes branch January 25, 2021 07:29
@tom139
Copy link
Contributor Author

tom139 commented Jan 25, 2021

Hi, thank you for your feedback! I totally agree with you. What do you think should be the best way to implement this?

Having two all attributes methods

as you suggested: one for Writing and one for Reading attributes.
.withAllStandardReadAttributes() for all attributes,
.withAllStandardWriteAttributes() for all attributes except *Verified.

Usage:

const readAttr = (new ClientAttributes()).withAllStandardReadAttributes();
const writeAttr = (new ClientAttributes()).withAllStandardWriteAttributes();

Having only one method

withAllStandardAttributes() that adds all standard attributes and adding a check for removing *Verified attribute at UserPoolClient creation time

Usage:

const readAttr = (new ClientAttributes()).withAllStandardAttributes();
const writeAttr = (new ClientAttributes()).withAllStandardAttributes();

I think the latter would be is easier to use, what do you think?

@tom139
Copy link
Contributor Author

tom139 commented Jan 25, 2021

Sorry, I forgot to write about different design choices for withCustomAttributes() vs _withStandardAttributes()`.

Having an interface for the standard attributes allow the user to easily find the available values through code completion; and in my opinion it is more valuable than consistency among with*Attributes() methods.

@russelldavis
Copy link

What do you think should be the best way to implement this?

I like the idea of having as few methods as possible, but in this case I think keeping them separate is best. Automatically removing the *Verified attributes could cause some confusion — a developer would reasonably assume those attributes are writable if they put them in writeAttributes and everything succeeds, but they'd be wrong. "Explicit is better than implicit."

@russelldavis
Copy link

I'm with you about the value of code completion. Fortunately there's a way to have that without sacrificing consistency. For example:

type StandardAttribute = "name" | "email";
function withStandardAttributes(...attributes: StandardAttribute[]);

When you type withStandardAttributes(" in VSCode, it will give you code completion, and show an error if you type a value not in the list.

@tom139
Copy link
Contributor Author

tom139 commented Jan 25, 2021

Yeah, it is very cool! Unfortunately we must ensure that JSII can synthesise this information in other languages too… I don't think this will work outside of Typescript. 😔

@nija-at could you help us on that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow to set read and write attributes in Cognito UserPoolClient
5 participants