This playbook walks through moving a Cognito user pool to theauth-go. The process
is two stages: export (convert the Cognito data to the intermediate bundle format)
then apply (write the bundle to your theauth-go storage backend).
The two-stage design lets your team audit the JSON before committing to any
production writes. The intermediate bundle is human-readable; you can diff it,
fix it, and run theauth-migrate validate against it as many times as you like.
Prerequisites
theauth-migrate binary (build with go build ./cmd/theauth-migrate)
- AWS CLI configured for your Cognito user pool
- Postgres DSN for your theauth-go database (or use
--storage memory for a dry run)
Step 1: Export users from Cognito
There are two supported input formats:
Option A: Cognito console CSV export
- Open the AWS Console and navigate to Cognito.
- Select your user pool.
- Go to “Users” and click “Export users to CSV” (or use the CSV download button).
- Save the file as
cognito-users.csv.
You can also use the AWS CLI to generate a compatible CSV:
Note: the AWS CLI text output format does not match the Cognito console CSV
exactly. Use the console export for the most reliable input, or use the JSON
option below.
Option B: Cognito JSON export (recommended)
The JSON export from aws cognito-idp list-users preserves more attributes
including MFA settings and custom attributes:
For large user pools, paginate the results:
The tool auto-detects the format from the file extension. Use
--export-format csv or --export-format json to override.
Step 3: Inspect the bundle
Open bundle.json in your editor. Key things to review:
notes: human-readable caveats. For Cognito, the most important is that
Cognito does not export password hashes. Every user will have
"requires_password_reset": true.
users: check that email addresses look correct and source IDs are present.
mfa_enrolled: users who had MFA will have "requires_mfa_reenroll": true
and a record here. They must re-enroll TOTP on first login.
oauth_accounts: empty for Cognito (Cognito does not export social connections
in a way that maps directly to theauth-go OAuth accounts).
Password reset caveat
Cognito stores password hashes using its own internal format and does not
expose them via any API. All migrated users must reset their password on first
login. The bundle will have "requires_password_reset": true for every user.
After the apply step, the CLI prints a list of emails that need a reset token.
Wire this to your email service, or call theauth-go’s built-in endpoint for
each address:
You can also automate this with a script that reads the apply output:
MFA caveat
Cognito TOTP (SOFTWARE_TOKEN_MFA) uses secrets that are encrypted inside
Cognito’s HSM. They are never accessible via any API.
Users who had TOTP enrolled will have "requires_mfa_reenroll": true. When
they next log in, your application should detect this flag (available on the
theauth-go user record metadata or via a separate table you maintain) and
prompt them to re-enroll TOTP via theauth-go’s /auth/totp/enroll endpoint.
Users who had SMS MFA: theauth-go does not support SMS MFA. Evaluate whether
to use TOTP, WebAuthn, or another second factor for these users.
Step 4: Validate the bundle
This performs all structural checks without touching any storage:
- Duplicate user emails
- Duplicate source IDs
- Orphaned OAuth account rows
- Schema version compatibility
Fix any reported errors before proceeding.
Step 5: Dry-run apply
A dry run validates the bundle and detects conflicts (e.g. emails that already
exist in the target database) without writing anything.
Step 6: Apply to production
The applier:
- Validates the bundle.
- Checks for existing users by email (idempotent; duplicates are skipped).
- Inserts users in batches of 500.
- Inserts OAuth accounts.
- Prints a list of emails that need password-reset tokens.
If any row fails, the error is logged and the remaining rows continue.
Partial failures are reported in the exit summary. Re-run the command; it is
safe because duplicate detection prevents double-inserts.
Step 7: Send password-reset emails
Use the list from step 6 to send reset emails. See the script in step 3 for
an example of how to automate this.
Step 8: Update your application
- Update your sign-in flow to point at theauth-go instead of Cognito.
- If you had custom Cognito triggers (pre-token generation, pre-sign-up,
etc.), re-implement them as theauth-go middleware or lifecycle hooks.
- If you had custom Cognito attributes (
custom:*), they are preserved in
the bundle’s user metadata map. Map them to your application’s data model
as appropriate.
Step 9: Monitor and cutover
- Run both Cognito and theauth-go in parallel for one login cycle (optional
but recommended for large migrations).
- Verify that users can log in via theauth-go.
- Disable sign-in via Cognito.
- After 30 days (or your chosen window), delete the Cognito user pool.
Rollback plan
Because the apply step is read-only with respect to Cognito and only writes
to the theauth-go database, rollback is straightforward:
- Point your application back at Cognito.
- Optionally drop the theauth-go user tables and re-apply with a corrected bundle.
Common issues
”cannot determine user id (no ‘sub’ or ‘cognito:username’ column)”
The CSV export is missing the sub or cognito:username column. Use the
JSON export format instead, which always includes the Username field.
Large pools (more than 60,000 users)
aws cognito-idp list-users is capped at 60 results per page. Use the
pagination script in step 1 option B.
Ensure the attribute name starts with custom: in the Cognito schema. Only
attributes with that prefix are mapped to the bundle’s metadata field. Last modified on October 7, 2026