The case for and against Amazon Cognito

In light of the recent Okta breach [1] there’s been renewed discussion about where Amazon Cognito fits into the picture. It’s a question my clients often ask me, so here are my two cents.

The case for Cognito

Integration with other AWS services

Cognito’s tight integration with other AWS services such as API Gateway, AppSync and ALB is by far its greatest strength.

It removes a whole layer of custom code you’d have to write otherwise.

For example, if you decide to use Auth0 and skip Cognito altogether (I will talk about SAML federation in a minute) then you will need to write a Lambda authorizer, like this [2]. It’s not hard, but any code that you ship has a cost. As your system expands, you need to maintain and update the policies that are generated to keep them up-to-date and relevant.

If you require group-based authentication (ie. users have different levels of access according to the Cognito groups they belong to), then the story is very different depending on whether you use AppSync or API Gateway.

AppSync’s @aws_auth directive lets you implement group-based authentication with one line:

Whereas API Gateway’s integration with Cognito only checks if the user exists in the Cognito User Pool. You would need to implement group-based authentication yourself using a Lambda authorizer. For more details, see my post on AppSync vs API Gateway here [3].

Cost

A common reason my clients decide to go with Cognito is because of its price.

Quite simply, Cognito is at least an order of magnitude cheaper than its competitors such as Auth0 and Okta. This is a critical consideration for B2C (business-to-customer) businesses.

I recently researched a number of security solutions (Auth0, Okta, OneLogin, JumpCloud, etc.) and found that most focus on B2E (business to employee) use cases. The pricing model reflects this focus in that the cost per user typically ranges from a few dollars per month to tens of dollars per month depending on what features you need.

Cognito’s cost per MAU (monthly active user) is $0.0055.

Auth0 come closest to this price point as it too was focused on B2C use cases. But it’s a distant 2nd, at ~$0.02 per MAU for the Developer tier. However, Auth0’s price per MAU goes up in tenfold as you upgrade to Developer Pro and Enterprise tiers.

Furthermore, Auth0’s Developer tier only allows up to 50,000 MAUs.

Cognito’s free tier alone (which doesn’t expire after 12 months) allows for 50,000 MAUs. So you pay nothing for that first 50,000 MAUs! Only after that do you start paying $0.0055 per MAU.

As a B2C business, the cost of Auth0 and other solutions can be prohibitive and Cognito becomes a no-brainer. That is, despite the fact that Cognito’s DX (developer experience) is really quite bad…

The case against Cognito

It’s simple. The developer experience is terrible.

Docs, or lack thereof…

Cognito is actually a really powerful service. It’s capable of a lot of things – SAML federation with other services, social sign-in, hosted sign-in pages, etc. But the documentation on how you accomplish these amazing feats ranges from barely there to non-existent. And pretty much all of the available documentation involves “open the console, click this button, then click that button”. It’s as if CloudFormation never existed!

Seriously, why is it that everyone at AWS is constantly telling its customers to follow infrastructure-as-code, only for its documentation to constantly omit CloudFormation?

So, how does one learn to use Cognito?

Well, by trying sh*t out for yourself… Or, by using the Amplify CLI to provision the Cognito User Pool and Cognito Identity Pool and then carefully study how it’s done so you can replicate it yourself.

Wait, did I just say “Identity Pool”?

Oh yes…

User Pool vs Identity Pool vs Sync

So, another thing that confuses newcomers to no end is the fact that Cognito is not one thing. It’s one service that has three distinct sets of features.

Cognito User Pool is the user management system, like your Auth0 and Okta.

Cognito Identity Pool is a mechanism for you to issue temporary AWS credentials to authenticated and unauthenticated users so they can talk to AWS services directly. For example, for IoT devices to publish events to IoT Core, they first need to acquire temporary AWS credentials from a Cognito Identity Pool.

Cognito Sync is… you know what, no one actually uses Cognito Sync so you can pretend it doesn’t exist.

Not polished

As I said before, Cognito is capable of many great feats. However, everything it does feels undercooked. Here are a few examples.

No IdP-initiated workflow

Cognito supports SAML federation.

If you want to use Auth0 or something else but still want to leverage Cognito’s built-in integration with other AWS services then you can configure Cognito to federate identity to Auth0 via SAML. This gives you the best of both worlds, at the expense of paying for the same MAU twice.

BUT, for SSO use cases, you also need to use its Authorization endpoint [4] in order to avoid a 2nd sign-in screen – one for the SSO portal, and then one for Cognito-protected API because Cognito doesn’t support IdP-initiated workflow.

Lack of customization for the hosted sign-in page

Cognito can host a sign-in screen for you. But, it doesn’t offer much in the way of customization. So if you want anything other than the blank sign-in screen that comes out of the box, you’d have to build the whole thing yourself.

Woes with linking accounts

Cognito supports social sign-in and you can even link a user’s various accounts together. But, it only works if the user creates an account with a username and password first.

Even if the user creates a username and password account first before attempting to sign in with a social identity provider, it still won’t go smoothly… Because on that first sign-in with a social identity provider, you will attempt to link the new account to the user’s username and password account, and linking the accounts would fail the sign-in request.

Why?

I don’t know…

What’s the workaround?

Well, you can handle the error and retry the sign-in request on the client side…

I can go on, but you get the point.

Can’t export user passwords

You can’t export users’ passwords from Cognito. While this is applaudable from a security point-of-view, it can be a PITA if you ever need to move away from Cognito.

It means you can’t easily migrate the user database to another service. Typically this leads to a slow and dragged-on migration process where users have to go through a password reset process:

  1. user has to enter their current password so you can authenticate the user against Cognito
  2. the user sets a new password, which is then captured in the new system

And since some percentage of your users have lapsed and are never coming back, you also need to (where possible) set a deadline. Anyone who doesn’t come back and reset their password will lose their account.

If you can’t unilaterally delete a user’s account (maybe there are legal ramifications) then be prepared for a prolonged migration process that will take years to complete… if it ever completes.

A much better approach would be to introduce some form of password authentication mechanism, such as using magic links. Luckily, it’s possible to implement these custom authentication flows with Cognito. See my post on how to implement magic links with Cognito here [5].

Can’t change attributes

When you create a Cognito User Pool, you have to decide what attributes you want to collect from a user during sign-up. Once the user pool is created, you can’t change the list of attributes.

This is very inconvenient to say the very least.

Single region

And last but not least, there’s no built-in support for cross-region replication.

Multi-region, active-active is considered the gold standard when it comes to building resilient applications on AWS. The stateless part of the application is easy to replicate to multiple regions and DynamoDB offers Global Tables [6]. Even S3 offers built-in cross-region replication [7] for all your blob storage needs.

Cognito is often a key component in a serverless application, so it’s a shame that there’s no cross-region support yet. It means that you are still vulnerable to single-region outages if the outage affects Cognito in some way.

Summary

I know I have spent a lot more words on the case against Cognito in this post. But for all its faults, it’s still my default choice on a new project because the case for it is a slam dunk where the arguments make sense. All the problems I mentioned are annoyances or challenges that one can live with or work around if given enough effort.

I love using Auth0, it’s miles ahead of Cognito on developer experience and I wish everyone there the best in the future and hope the Okta acquisition works to their benefit.

But if you’re a B2C business that needs to serve a million MAU and you don’t have a deep pocket then guess what, Cognito is still your best bet!

And finally, here’s my thought process when I think about whether to use Cognito or not.

Please don’t take this flow chart too seriously, it’s intended as tongue-in-cheek and ignores many consideration points (that I’ve even mentioned above, such as the need to export users to another system).

However, it represents my general feeling towards Cognito – that I’d use it if I can, and if I can’t I would prefer to use SAML federation to bring another provider into the fold.

Why?

Because of its built-in integration with other AWS services.

I hope you’ve found this post useful. If you want to learn more about running serverless in production and what it takes to build production-ready serverless applications then check out my upcoming workshop, Production-Ready Serverless [8]!

In the workshop, I will give you a quick introduction to AWS Lambda and the Serverless framework, and take you through topics such as:

  • testing strategies
  • how to secure your APIs
  • API Gateway best practices
  • CI/CD
  • configuration management
  • security best practices
  • event-driven architectures
  • how to build observability into serverless applications

and much more!

Links

[1] Okta says hackers stole data for all customer support users in cyber breach

[2] Auth0: Secure AWS API Gateway Endpoints Using Custom Authorizers

[3] Five reasons to consider AppSync over API Gateway

[4] Cognito’s Authorization endpoint

[5] How to implement magic links with Cognito

[6] DynamoDB Global Tables

[7] S3: When to use Cross-Region Replication

[8] Production-Ready Serverless workshop