jagregory / cognito-local

Local emulator for Amazon Cognito
MIT License
278 stars 67 forks source link

ID token does not include attributes such as given_name, family_name, or preferred_username #360

Open michaelrhyndress opened 1 year ago

michaelrhyndress commented 1 year ago

The ID Token does not include attributes such as given_name, family_name, or preferred_username even though they exist on the user.

Decoded ID token:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "CognitoLocal"
}
{
  "cognito:username": "First.Last@email.com",
  "auth_time": 1672277860,
  "email": "First.Last@email.com",
  "email_verified": false,
  "event_id": "171100f3-aaac-4144-ba08-34d546336515",
  "iat": 1672277860,
  "jti": "8dfcc238-6382-4ba7-af41-8d1e79e97135",
  "sub": "f36cbdd1-253e-4d47-b7e3-04e2d92ea6c6",
  "token_use": "id",
  "exp": 1672364260,
  "aud": "djvkg1i4saxvezvy9fsjbrcci",
  "iss": "http://localhost:9229/local_1k918F3w"
}

Example user in local_ db file

{
  "Users": {
    "First.Last@email.com": {
      "Username": "First.Last@email.com",
      "Password": "123456789",
      "Attributes": [
        {
          "Name": "sub",
          "Value": "f36cbdd1-253e-4d47-b7e3-04e2d92ea6c6"
        },
        {
          "Name": "preferred_username",
          "Value": "1234567@email.com"
        },
        {
          "Name": "email",
          "Value": "First.Last@email.com"
        },
        {
          "Name": "given_name",
          "Value": "First"
        },
        {
          "Name": "family_name",
          "Value": "Last"
        }
...

Commands for setup and generating token:

aws --endpoint http://localhost:9229 cognito-idp list-user-pools --max-results 1
aws --endpoint http://localhost:9229 cognito-idp create-user-pool --pool-name userpool-name --username-attributes email
aws --endpoint http://localhost:9229 cognito-idp create-user-pool-client --user-pool-id $user_pool_id --client-name client-name --allowed-o-auth-scopes "email openid profile" --read-attributes "email" "given_name" "family_name" "preferred_username" 
aws --endpoint http://localhost:9229 cognito-idp admin-create-user --user-pool-id $user_pool_id --desired-delivery-mediums EMAIL --username First.Last@email.com --user-attributes Name=preferred_username,Value=1234567@email.com Name=email,Value=First.Last@email.com Name=given_name,Value=First Name=family_name,Value=Last
aws --endpoint http://localhost:9229 cognito-idp admin-initiate-auth --user-pool-id $user_pool_id --client-id $client_id --auth-flow ADMIN_USER_PASSWORD_AUTH --auth-parameters USERNAME=First.Last@email.com,PASSWORD=123456789
alec-w commented 1 year ago

The PR I've just opened resolved the issue around the given_name and family_name for us (and it could be updated to add the preferred_username as well).

Could do with some guidance on whether this is deemed the correct fix and recommendations on what the new tests should look like.

michaelrhyndress commented 1 year ago

Maybe there is a better way. In cognito it might make sense to make any read, or write, attributes available in the payload

alec-w commented 1 year ago

Maybe there is a better way

I agree, that was more a hack that worked enough for me to continue with some local testing

In cognito it might make sense to make any read, or write, attributes available in the payload

That seems sensible, although I think in AWS Cognito the attributes present on the token depend on which ones the client used to get token has access to read? So it seems that for accurate emulation we want to get the client and find out which ones it has access to, and also potentially add in any default ones that are always present from Cognito. Just need to investigate to confirm which ones (if any) should always be present.

jagregory commented 1 year ago

Thanks for investigating this @alec-w, I think your assessment is correct that the Client would factor into which attributes should be included in the tokens.

The CreateUserPoolClient docs aren't especially helpful (as usual for Cognito), the ReadAttributes description is just: "The read attributes" which doesn't really clarify anything, but WriteAttributes does seem to suggest these are used for access controls: "The user pool attributes that the app client can write to."

Based off that, I think we should:

  1. Get all the attributes which have a value on a User
  2. Remove any attributes which are not present in either ReadAttributes or WriteAttributes* on the Client
  3. Any attributes left can be included in the tokens

* I'm assuming an attribute present in WriteAttributes is implied to be readable, I don't think Cognito has write-only attributes?

If you're able to update your PR @alec-w to do this that would be amazing, otherwise I'll get to this when I can.

Elte156 commented 5 months ago

@alec-w @jagregory I too require this functionality for my application. I see that my real AWS ID Token has:

I then manually added the optional field middle_name When I recheck my ID Token it now has middle_name added to it. image

Available Default Attrs

These are the list of attribute read and write that I can modify from my client: image same list in text format Also listed here in AWS Docs

address
birthdate
email
email_verified
family_name
gender
given_name
locale
middle_name
name
nickname
phone_number
phone_number_verified
picture
preferred_username
profile
updated_at
website
zoneinfo

Write-Only Attrs

I'm assuming an attribute present in WriteAttributes is implied to be readable, I don't think Cognito has write-only attributes?

I unchecked birthdate and it seems an attribute can be write-only and not readable. I added a value for birthdate using AWS GUI. Yet birthdate was NOT present in my ID Token. image

Next Steps

Would it be helpful to run the command below, to get what is listed in ReadAttributes and WriteAttributes?

aws cognito-idp describe-user-pool-client
Elte156 commented 5 months ago

For each app, you can mark attributes as readable or writeable. This applies to both standard and custom attributes. Your app can retrieve the value of attributes that you mark as readable, and can set or modify the value of attributes that you mark as writeable. If your app tries to set a value for an attribute that it isn't authorized to write, Amazon Cognito returns NotAuthorizedException. GetUser requests include an access token with an app client claim; Amazon Cognito only returns values for attributes that your app client can read. Your user's ID token from an app only contains claims that correspond to the readable attributes. All app clients can write user pool required attributes. You can only set the value of an attribute in an Amazon Cognito user pools API request when you also provide a value for any required attributes that don't yet have a value. https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html

Based on the bold text, I think these steps should be implemented (augmented from @jagregory)

  1. Get all the attributes which have a value on a User
  2. Remove from list if not in the ReadAttributes array
  3. Insert list into the ID Token