← Writing

Auth0 vs Amazon Cognito: Choosing an Authentication Provider on AWS

Every project reaches the same fork in the road, usually in week two: people need to log in. And someone on the call always says it — "it's just a users table and a password hash, we can do that in an afternoon."

You can. You shouldn't. And once you've accepted that, the real question is which managed provider you hand it to. If you're already on AWS, my default answer is Amazon Cognito — but Auth0 is genuinely better at some things, and the gap is not where most people assume it is.

Here's the honest comparison, with the pricing worked out and every claim linked back to the source.

First: why you shouldn't build authentication yourself

This isn't gatekeeping. It's a scope estimate.

Look at what the OWASP Authentication Cheat Sheet says a correct implementation actually requires, and count the line items:

  • Password storage with a real algorithm — bcrypt, Argon2, or scrypt. Not SHA-256, not "SHA-256 with a salt we generate ourselves."
  • Length and breach checks — at least 8 characters with MFA enabled, 15 without, support for passphrases past 64 characters, and rejection of passwords already in a breach corpus like Pwned Passwords.
  • Brute-force and credential-stuffing defense — lockout tracked per account, not per IP, because the attacker has a botnet and you don't.
  • Multi-factor authentication, which OWASP notes research credits with stopping 99.9% of account compromises.
  • Session management — identifiers that are unique and computationally infeasible to predict, plus invalidation and rotation.
  • Password recovery — time-limited nonces, verification email, and re-authentication before anything sensitive changes.
  • Generic error messages — "Invalid user ID or password," always, or you've built a free user-enumeration API.

That's before you've written a line of business logic. And it's a list you have to stay on top of forever: password guidance changes, passkeys arrive, a new OAuth attack class gets published, and your afternoon project quietly becomes someone's permanent part-time job.

The pitch for managed auth is not "it's easier." It's that a vendor employs a security team whose entire job is the list above, and you get their work for somewhere between free and the price of a decent laptop per month. The build-it-yourself math only works if your engineers' time is worthless and your breach risk is zero.

So: buy. The question is from whom.

What Amazon Cognito actually is

Cognito is AWS's identity service, and it's really two products that people constantly conflate:

  • User pools — the user directory and authentication service. Sign-up, sign-in, MFA, password reset, social and enterprise identity federation, and JWTs at the end of it. This is what people mean 95% of the time.
  • Identity pools — exchange an identity (from a user pool or Google or SAML) for temporary AWS IAM credentials, so a mobile client can hit S3 or DynamoDB directly with scoped permissions.

In November 2024 AWS introduced two new feature tiers, Essentials and Plus, alongside the existing capabilities now billed as Lite. Essentials became the default for new user pools and is the one most teams want — it adds Managed Login and passwordless sign-in with passkeys, email, or SMS. Plus layers on threat protection: risk-based adaptive authentication, compromised-credentials detection, and exportable authentication event logs.

The thing that actually sells Cognito on AWS is the integration surface. An API Gateway Cognito authorizer validates tokens before your code runs. An ALB can authenticate users at the load balancer. Identity pools hand out scoped IAM credentials. AppSync speaks Cognito natively. None of that is glue you write — it's a field in a Terraform resource.

resource "aws_cognito_user_pool" "portal" {
  name = "portal-users"

  # Essentials tier — Managed Login and passkeys. "LITE" and "PLUS" also valid.
  user_pool_tier = "ESSENTIALS"

  # Required for the verified_email recovery below to work at all: without it,
  # self-signup users never get a verified email, and ForgotPassword returns
  # InvalidParameterException.
  auto_verified_attributes = ["email"]

  password_policy {
    minimum_length                   = 12
    require_symbols                  = true
    temporary_password_validity_days = 3
  }

  # TOTP, deliberately — not email MFA. Cognito won't send the MFA code and the
  # password-reset code to the same destination.
  mfa_configuration = "OPTIONAL"
  software_token_mfa_configuration { enabled = true }

  account_recovery_setting {
    recovery_mechanism {
      name     = "verified_email"
      priority = 1
    }
  }
}

# Without a domain there is no /oauth2/authorize endpoint, and the
# authorization-code flow configured below has nowhere to start.
resource "aws_cognito_user_pool_domain" "portal" {
  domain       = "portal-example"
  user_pool_id = aws_cognito_user_pool.portal.id
}

resource "aws_cognito_user_pool_client" "web" {
  name         = "portal-web"
  user_pool_id = aws_cognito_user_pool.portal.id

  # No client secret for a browser app — it can't keep one.
  generate_secret = false

  allowed_oauth_flows                  = ["code"]
  allowed_oauth_scopes                 = ["openid", "email", "profile"]
  allowed_oauth_flows_user_pool_client = true

  # Adding a social IdP here means also declaring an aws_cognito_identity_provider
  # and referencing its provider_name, so Terraform orders the two correctly —
  # a bare "Google" in this list fails the apply.
  supported_identity_providers = ["COGNITO"]

  callback_urls = ["https://example.com/api/auth/callback"]
  logout_urls   = ["https://example.com"]

  access_token_validity  = 60
  id_token_validity      = 60
  refresh_token_validity = 30
  token_validity_units {
    access_token  = "minutes"
    id_token      = "minutes"
    refresh_token = "days"
  }
}

Authorization-code flow, no implicit grant, short access tokens, MFA available, recovery through verified email — that's a defensible starting position in about sixty lines.

What Auth0 actually is

Auth0 (owned by Okta since 2021) is the identity platform that got there first and, for a long stretch, was simply the best developer experience in the category. It still largely is.

Its strengths are real:

  • Breadth of connections. Dozens of social providers, enterprise SSO over SAML and OIDC, LDAP/AD via a connector, and a "custom database" connection that authenticates against your existing user store while you migrate off it.
  • Actions. Auth0's extensibility model — JavaScript that runs at defined points in the login pipeline (post-login, pre-registration, token issuance). Cognito's equivalent is Lambda triggers, which are more powerful but also more work: you're deploying and versioning a function instead of editing a script.
  • B2B organizations. First-class multi-tenancy. Organizations, per-organization connections and branding, invitations, member roles. If you're selling to companies whose employees log in with their identity provider, this is the feature you're actually buying.
  • Documentation and quickstarts. Auth0's docs are still the benchmark. Cognito's are AWS docs — comprehensive, and organized like a filing cabinet.

The catch is the bill, and it's a big enough catch that it deserves its own section.

Pricing: where the two genuinely diverge

Both have a free tier, and on paper Auth0's looks better. Then you grow.

Amazon Cognito (official pricing), per monthly active user:

TierFree MAUsPrice per MAU above free
Lite10,000$0.0055 (first 90k), then $0.0046
Essentials10,000$0.015
Plus0$0.020

Auth0 (official pricing): the Free plan covers up to 25,000 MAU — a genuinely generous headline number — but without MFA, RBAC, or premium support. Paid B2C Essentials starts at $35/month for 500 MAU, and the published tiers ($70 at 1,000, $175 at 2,500, $3,500 at 50,000) work out to a flat $0.07 per MAU. B2C Professional starts at $240/month and adds enterprise MFA, enhanced attack protection, and custom database connections. B2B starts higher — $150/month for Essentials, $800/month for Professional.

Put a real product through both:

Monthly active usersCognito EssentialsCognito PlusAuth0 B2C Essentials
10,000$0$200$700
25,000$225$500$1,750
50,000$600$1,000$3,500
100,000$1,350$2,000~$7,000

At 50,000 users you're comparing $600 a month to $3,500 a month for the same fundamental capability. That is not a rounding error — it's a junior engineer's salary, and it's the single strongest argument for Cognito that exists.

Three caveats, because a pricing table without caveats is marketing:

  1. The free tier only covers direct and social sign-ins. SAML/OIDC federated users get just 50 free MAUs and cost $0.015/MAU after that, on every tier. If you're B2B and everyone logs in through their employer's IdP, recompute from scratch.
  2. Machine-to-machine tokens bill separately at $0.00225 per token request with no free allowance. A chatty service-to-service integration requesting a token per call instead of caching it can produce a genuinely surprising invoice. Cache your tokens.
  3. SMS and email aren't free. MFA over SMS bills through Amazon SNS, verification email through SES. Small, but not zero — and SMS to some countries is not small at all.

And the risk nobody prices in: on 1 November 2023 Auth0 repriced its plans. B2C Essentials moved from $23/month for 1,000 MAU to $35/month for 500 MAU — same plan name, and an effective rate that went from $0.023 to $0.07 per MAU. Roughly 3×, on a line item most teams had already modeled three years forward.

Cognito's per-MAU rates have held steady across the same period, but don't read that as a guarantee: AWS's own November 2024 tier change made Essentials the default for new pools at $0.015/MAU, nearly 3× the Lite rate that used to be the only option. Nobody raised a price — the default just got more expensive. Per-MAU pricing is a lever your vendor controls and you don't, and that's true on both sides of this comparison.

The honest scorecard

Amazon CognitoAuth0
Cost at scaleStrongest in classExpensive, and the curve is steep
AWS integrationNative — API Gateway, ALB, IAM, AppSyncWorks fine, but you build the glue
Developer experienceImproving, still rough edgesBest in class
DocumentationThorough, hard to navigateExcellent
ExtensibilityLambda triggers — powerful, more setupActions — fast to write and ship
B2B multi-tenancyWeak; you build it on topFirst-class Organizations
Enterprise SSOSupported, $0.015/MAU after 50 freeSupported, gated by plan tier
Compliance postureInherits AWS (SOC, ISO, HIPAA eligible)Strong, well-documented
Getting your users outPassword hashes can't be exportedExport supported

That last row is the one to read twice.

The lock-in question

Cognito lets you import users. It does not let you export password hashes — that capability doesn't exist. AWS publishes a reference architecture for exporting user profiles and groups, and profiles and group membership come out fine. Credentials don't.

So leaving Cognito means one of two things: every user resets their password (measurable churn, and an email that reads like a breach notification), or you run a just-in-time migration — keep the old pool alive, and on each user's first sign-in to the new system, validate against Cognito and transparently re-create them. Auth0 supports exactly this through its custom database connection, and most competent providers have an equivalent.

JIT migration works well. It's also something you want to have thought about at 5,000 users rather than 500,000. The practical advice: don't let Cognito's API leak into your application code. Put your own thin interface in front of it — signIn, getSession, refresh — so the day you swap providers you're rewriting one module, not auditing every route in the codebase.

A second, quieter form of lock-in: some Cognito configuration is immutable. Changing custom attributes or username settings can require destroying and recreating the user pool — which, given the export limitation above, is close to unthinkable once you have real users. Think hard about your attribute schema and whether email is the username before the first real signup, not after.

So which one

Choose Cognito if you're already on AWS. This is the whole argument, and it's a strong one. Your identity lives in the same account, the same Terraform state, the same IAM model, and the same bill as everything else. API Gateway validates your tokens for free. Costs stay predictable and low — usually zero until 10,000 users. And the operational surface you already know (CloudWatch, CloudTrail, IAM policies) is the one you use to run it. Cognito Essentials is a straightforward default for consumer-facing sign-in, and Plus is worth the extra $0.005/MAU if you're handling anything financial or otherwise attractive to attackers.

Choose Auth0 if any of these are true:

  • You're B2B with real multi-tenancy — customer organizations, per-tenant branding, per-tenant enterprise SSO. Cognito can be forced into this shape, but you'll build and then maintain the scaffolding. Auth0 ships it.
  • You're multi-cloud or not on AWS at all. Cognito's advantage is coupling. Off AWS, the coupling is just a constraint.
  • Login is your product's front door and you're iterating on it constantly. Auth0's Actions and hosted experience let you ship auth changes at a speed Cognito can't currently match.
  • Your enterprise buyers hand you a security questionnaire. Auth0's compliance documentation is purpose-built for exactly that conversation.

Choose neither if you're B2B-heavy and want the middle ground — WorkOS, Clerk, FusionAuth, and Descope all target the space between Cognito's price and Auth0's polish. That's a different post.

For most teams I work with, though, the decision is simpler than the debate around it suggests. If your infrastructure is on AWS, use Cognito. Stay aligned, keep the bill boring, and put the money you didn't spend on per-MAU fees into something your users can actually see.

And whichever you pick: wrap it in your own interface, cache your M2M tokens, turn on MFA, and never, ever write your own password hashing.

The short version

  • Don't build it yourself. OWASP's minimum bar is a permanent job, not an afternoon.
  • On AWS, Cognito is the default. Native integration with API Gateway, ALB, and IAM, and roughly 5× cheaper than Auth0 at 50,000 MAU.
  • Auth0 earns its price in B2B — Organizations, per-tenant SSO, and the best developer experience in the category.
  • Watch the fine print: SAML/OIDC users get only 50 free MAUs, M2M tokens bill per request, and Cognito can't export password hashes.
  • Plan your exit on day one. A thin interface over the provider and a just-in-time migration path cost almost nothing now and save you enormously later.

Every figure above links to the vendor's own pricing or documentation. Both change — check them before you put a number in a budget.