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
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
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
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
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
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
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
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
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)