DEV Community

Robert Slootjes
Robert Slootjes

Posted on

Cognito User Pool Tips & Tricks

When implementing Cognito in a project I ran into some things that others might run in too. Hopefully this article will prevent that. Nearly all of these tips & tricks assume you are defining your stack using CloudFormation. I would highly recommend using Infrastructure as Code for all of your projects as manually setting up infrastructure by clicking in the console is error prone and doesn't give you the ability to easily remove your resources and/or to roll out multiple environments (or to start over if you messed up something).

Deletion Policy

Accidents with stacks can happen so first of all, make sure to set the Deletion Policy to Retain to ensure you don't lose precious user data when something unexpected happens to your stack.

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    DeletionPolicy: Retain
Enter fullscreen mode Exit fullscreen mode

Optionally, highly recommended, you can choose to add some logic based on environment so you only set Retain on your production environment and set to Delete (which is the default) on other environments.

Case Insensitive Username

Make the user name case insensitive to make sure someone creating an account with Myname@domain.com can also log in with myname@domain.com. Do not count on your users ability to always type their email address or nickname exactly the same.

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UsernameConfiguration:
        CaseSensitive: False
Enter fullscreen mode Exit fullscreen mode

Password Policy

Set a proper password policy, I went with the configuration below:

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      Policies:
        PasswordPolicy:
          MinimumLength: 12
          RequireLowercase: True
          RequireNumbers: True
          RequireSymbols: True
          RequireUppercase: True
Enter fullscreen mode Exit fullscreen mode

Token Validity

You can easily configure the validity of your tokens. I decided to keep Access + ID Tokens valid for 1 day and the refresh token is valid for 30 days.

Resources:
  CognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      TokenValidityUnits:
        AccessToken: days
        IdToken: days
        RefreshToken: days
      AccessTokenValidity: 1
      IdTokenValidity: 1
      RefreshTokenValidity: 30
Enter fullscreen mode Exit fullscreen mode

More details about these options here and here.

Enable all standard attributes

When describing a user pool you need to define which attributes you want to use. While the console and documentation clearly state you can not enable standard attributes once the user pool has been created this is easily overlooked when using CloudFormation.

Since enabling these attributes does not cost anything and you might need them in the future I would recommend to configure all of them regardless if you need them now. If you attempt to add the standard attributes later on it will work but they are added as custom attributes instead (and will be prefixed with custom:). There is a limit of 50 on custom attributes. The limit of 50 seems to be changed recently as other documentation pages still mention 25 as the limit which I ran into myself earlier this year too.

Admin Only Registration

If you only plan to create new users server side, you should prevent users from creating their own accounts:

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: True
Enter fullscreen mode Exit fullscreen mode

Required attributes

Similar to enabling standard attributes, it's not possible to change the required attributes after your user pool has been created. Make sure you verify your use cases and see if you've configured the required attributes correctly.

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      Schema:
        - Name: nickname
          AttributeDataType: String
          Required: true
Enter fullscreen mode Exit fullscreen mode

If you're only going to use Admin API commands to manage accounts, you can still create accounts without providing these attributes.

Mutable attributes

If you want to have attributes that can be written only once you can configure them to be immutable:

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      Schema:
        - Name: registration
          AttributeDataType: String
          Mutable: false
Enter fullscreen mode Exit fullscreen mode

You could for instance use this to write certain details on registration which are written once that never can be changed again.

The address attribute

The "address" field is a bit confusing. According to the documentation they follow the OpenID Connect specification however it's not really clear how to populate this field. The simple solution is you need to submit it as a JSON string and then it kind of accepts anything, not only the documented fields.

Storing JSON in attributes

Similar to the address attribute you can of course store JSON in other standard or custom attributes too to prevent hitting the limit of custom fields. While you might have enough available custom attributes now, you don't know what you need to add in the future. You could for instance store the last login information (timestamp, country code, IP address) as JSON and use only a single custom field for it and if you later decide to also store the user agent, you can just add it without sacrificing another custom attribute.

Configure Read & Write attributes

When creating a user pool client it is possible to configure which attributes are readable and writable by the user itself. This is something you can easily forget and can be pretty dangerous if you want to manage this only from a server after doing your own validation. If for instance you keep track of a users payment status with a custom attribute the user can just write a simple script and update the status themselves using their own personal access token and the Cognito API. Also, there might be attributes that you just use internally without wanting to expose these to the end user. You could use the DeveloperOnlyAttribute to prevent users from writing to the attribute however AWS recommends to use ReadAttributes & WriteAttributes for it now.

With ReadAttributes you can control which attributes are available to read by the end user. These are also the attributes that will be returned in the ID Token generated by Cognito.

With WriteAttributes you can control which are available to write (modify) by the end user. If you do not define any writeable attribute, by default Cognito will make all your attributes writeable. To prevent this I created the custom attribute "client_storage" which I made writeable just to make sure all the other attributes are now no longer writeable by the end user.

Resources:
  CognitoUserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      ReadAttributes:
        - given_name
        - family_name
        - nickname
        - email
        - picture
        - email_verified
        - custom:meta
      WriteAttributes:
        - custom:client_storage
Enter fullscreen mode Exit fullscreen mode

Modifying the ID Token

Cognito allows for quite some customization using Lambda Triggers. One of these things is to modify the data available in the ID Token.

In the above example you can see the "custom:meta" read attribute which contains a few details on the user that could be useful to the client (ie: account status or payment status). You can transform any existing attribute, remove them or add new ones.

Conclusion

Overall I'm very excited about Cognito User Pools as it is super powerful. On the other hand it's easy to make (crucial) mistakes with the configuration that will haunt you forever. I hope these tips are helpful to others.

Top comments (0)