Skip to content

TocConsulting/cognito-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

19 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

CognitoApi Logo

CognitoApi ๐Ÿ”

Python Terraform AWS Docker License: MIT

CognitoApi is an authentication and user management api based on the solid AWS Cognito service. It lets you build your applications without thinking about the authentication part.

A detailed documentation and a dedicated website about this project with a lot of material is located here: CognitoApi.

Demo ๐ŸŽฌ

Real curl calls โ€” request payload and JSON response โ€” captured live against AWS Cognito:

CognitoApi REST API demo: curl request and JSON response

Key Features โœจ

  • ๐Ÿ›ก๏ธ Security: The solution is very secure and uses MFA (Multi Factor Authentication) which adds an important security layer to your apps.

  • โ˜๏ธ AWS Cognito as a backend: The CognitoApi is based at its heart on the AWS Cognito service, known to be very secure and cost effective.

  • ๐Ÿ’ฐ Cost: AWS Cognito is free for the first 50K users (or MAU: Monthly Active Users), so it's a good choice for bootstrapping your app without thinking about the cost.

  • ๐Ÿ‘ฅ User Management Lifecycle: The solution supports the needed users management life cycle from the creation till the deletion.

  • ๐Ÿค– Fully automated: Nothing to do except deploying using Terraform.

  • ๐ŸŒ Cors support: The Cors is already supported, you can plug your front without any effort.

Pre-requisites ๐Ÿ“‹

Before installing the CognitoApi, please ensure that you already installed:

  • ๐Ÿ—๏ธ Terraform: a recent version is recommended, in order to deploy all the underlying AWS infrastructure.

  • ๐Ÿณ Docker: to build all lambdas and their layers in an automatic way.

  • ๐Ÿ”ง AWS CLI: to perform some of the project bootstrapping actions like creating the S3 bucket and DynamoDB table for Terraform states and collaboration.

  • ๐Ÿ Python 3 with a recent boto3 (pip install boto3): the passkey-enable Terraform step runs a small local script (helper_scripts/enable_passkeys.py). Set the interpreter via the passkey-bootstrap-python tfvar (defaults to python3). Only needed if you enable passkeys.

Installation ๐Ÿ“ฅ

Warning First you need to configure at least one deployment environment, let's call it dev. Copy the template and fill in your own values โ€” the real file is gitignored, so your values never get committed:

cp terraform/environments/dev/terraform.tfvars.dev.example terraform/environments/dev/terraform.tfvars.dev

Then edit terraform/environments/dev/terraform.tfvars.dev.

And here is the explanation of the relevant parameters:

Parameter Description Example
aws-region The region where to deploy your authentication API eu-west-1
terraform-state-bucket This S3 bucket will hold your terraform states files my-tf-state-bucket
auth-api-dns-name This is the dns to use for your API auth.dev.example.com
auth-api-acm-certificate-name This is the domain name for which the certificate should be issued *.dev.example.com
route53-zone-name The Route 53 hosted zone name (the domain) used for the DNS records of your API dev.example.com
certificate-name-tag The tag name that will be set for the ACM certificate used by your API wildcard.dev.example.com
auth-api-name The authentication API name myco-auth-api
auth-api-description The authentication API description MyCo Auth API
auth-api-r53-zone-id The route53 zone ID to use (corresponding to route53-zone-name) Z1029992156D8O
auth-api-stage-name The API gateway stage name to use development
cognito-reply-to-email-address The email that will be used when a user wants to answer your emails hello@myapp.io
cognito-from-email-address The email that will be used to send emails by AWS Cognito hello@myapp.io
cognito-ses-email-arn The ARN of the SES verified email identity to use arn:aws:ses:eu-west-1:012345678901:identity/hello@myapp.io
from-email Sender address used for the MFA reset recovery-code email hello@myapp.io
email-mfa-reset-subject Subject of the MFA reset recovery-code email Reset your authenticator (MFA)
email-mfa-reset-message Body of the MFA reset recovery-code email (must contain {code}) Your MFA reset code is {code}. ...

The process of installation is as follows:

export AWS_PROFILE=MyAwsDevProfile
git clone https://github.com/TocConsulting/cognito-api.git
cd terraform
ENVIRONMENT=dev make test
ENVIRONMENT=dev make plan
ENVIRONMENT=dev make apply

The last commands will:

  • Set up your AWS profile by exporting it inside the terminal, please change the name of the profile MyAwsDevProfile to yours and set it also inside the file terraform/live/services/auth-microservice/provider.tf.

  • Test the terraform infrastructure files.

  • Perform a terraform plan to check if everything is okay and gives you an idea about all resources that will be deployed.

  • Perform a terraform apply to deploy all the needed infrastructure inside your AWS account.

Please refer to the Makefile help: Makefile help

Teardown ๐Ÿงน

ENVIRONMENT=dev make destroy          # destroy EVERYTHING: the stack THEN its backend (state bucket + lock table)
ENVIRONMENT=dev make destroy-stack    # destroy the stack only (keep the backend / state history)
ENVIRONMENT=dev make destroy-backend  # remove the backend only: state S3 bucket + DynamoDB lock table

The state bucket and lock table are created by the bootstrap outside Terraform (they hold the state, so they can't manage themselves), so a plain terraform destroy can't remove them. make destroy handles that for you: it destroys the stack and then deletes the backend, leaving nothing behind. Use destroy-stack when you want to keep the state history, and make clean to wipe local generated files (.terraform, backend.tf, __pycache__, build zips, etc.).

Architecture ๐Ÿ›๏ธ

The deployed infrastructure looks like this:

CognitoApi

Deployed resources ๐Ÿš€

Resource Type Count Purpose
API Gateway 1 To expose the authentication Rest API
Cognito User Pool 1 To hold all the users and manage their lifecycle
Lambdas 27 API handlers (incl. 6 passkey endpoints) + 3 custom-auth triggers (MFA reset) + the custom-message trigger
Lambdas layers 2 Shared code for lambdas (jsonschema, pyjwt)
S3 bucket 1 Terraform remote state (created during bootstrap)
DynamoDB table 1 For terraform infrastructure collaboration lock
ACM Certificate 1 For the Rest API
Route53 records 2 For the ACM verification and the API custom domain

Cost estimation ๐Ÿ’ต

It's hard, because it depends on the context of everyone, but let's use these parameters:

  • ๐ŸŒ The deployment region is eu-west-1.

  • ๐Ÿ‘ฅ Let's assume 50K users and all of them perform a login (2 requests) every day on the app (the RefreshToken is valid for 24 hours, so the app can use it to refresh the user session), that means 24 requests, so in total: 26 requests per user per day, which means: 1.3 million requests for maintaining sessions on a daily basis. This also means that users are connected 24 hours per day all time (very unlikely to happen).

  • ๐Ÿงฎ Let's assume that all backend lambdas uses 256MB of memory and for each call the average duration of each lambda is 1 second (which is excessive!). They will be called 1.3 million every day.

Based on all these assumptions, the authentication and the user management of your application for 50k users will costs you around: 150$ per month, this means 0.003$ per user and per month! Hard to find on the market a serious competitor at this level.

Implementation notes ๐Ÿ“

We did a lot of technical choices inside this project:

  • ๐Ÿ—๏ธ The use of Terraform as the main IAC (Infrastructure As Code) is absolutely innocent :). The main reason of this choice instead of Cloudformation or any other IAC tool is the popularity and the simplicity. I've been using personally Terraform from almost the first release and I have been, in general, quite satisfied with it.

  • ๐Ÿ” MFAs are mandatory and I chose to activate the Google Authenticator MFA by default, mainly because there are no additional costs.

  • ๐Ÿ“ฑ AWS Cognito does not offer a direct API to reset a lost TOTP authenticator under mandatory MFA. CognitoApi solves this without storing anything: no QR code or secret is ever persisted (the old S3 bucket is gone). A user who lost their authenticator recovers through a dedicated CUSTOM_AUTH recovery flow (/v1/mfa-reset/*) that requires both their password and a one-time code emailed by Cognito's own CreateAuthChallenge trigger. Only after both proofs does Cognito issue a session to re-enroll a fresh TOTP; the recovery session is then immediately revoked. MFA stays mandatory and Cognito-enforced the whole time - the recovery factor lives outside the pool's MFA factors, so enrollment and normal login are never affected. No SMS and no extra cost.

  • ๐Ÿ“‹ The CognitoApi is using a Makefile to pilot all the infrastructure deployment, this Makefile is designed to handle multi environment deployment, to do so just create a configuration environment file named: environments/MyEnvName/terraform.tfvars.MyEnvName. If you want to handle the multi environments using Terraform workspaces, please share your version with us.

  • ๐Ÿ”‘ No API key. CognitoApi does not use an API key. An API key is an identifier, not authentication: it travels in every request and is visible in any browser that calls the API, so it only ever gave a false sense of security. Endpoint protection is handled at the right layers instead - see Protecting against abuse & financial DDoS below.

  • ๐Ÿ”’ Password policy is: 14 characters minimum length that contains at least 1 number, at least 1 special character, at least 1 uppercase letter and at least 1 lowercase letter.

  • ๐Ÿ›ก๏ธ Abuse / Financial-DDoS protection is not bundled (to keep the base deployment free of paid services), but it is strongly recommended for production - see the dedicated section below for the WAF + throttling approach.

  • โฑ๏ธ Access and ID Tokens are set to be valid for one hour, the Refresh Token is valid for 24 hours. If you want to modify these values, just set the target values for: id-token-validity, access-token-validity and refresh-token-validity variables.

  • ๐Ÿ” The Advanced security features of Cognito are disabled because of their cost.

Protecting against abuse and financial DDoS

CognitoApi has no API key. An API key is an identifier, not authentication - it rides in every request and is readable in any browser that calls the API, so it never actually protected anything. Real protection belongs at two different layers, and conflating them is what made the old API key misleading.

The threat: "financial DDoS"

Every request that reaches a Lambda is billed (Lambda invocation + duration + API Gateway request). A public auth API exposes endpoints that, by design, must be callable before a user has any credential

  • login, forgot-password. An attacker can hammer those millions of times and run up your bill, even though they never authenticate. No API key or token stops this (the attacker is unauthenticated by definition), and an API key in the browser is trivially copied. This is an edge / volumetric problem, not an authorization problem.

How to stop it (recommended for any public deployment)

The principle is to reject abusive traffic as early and as cheaply as possible - before it invokes a Lambda:

  1. AWS WAF on the API Gateway stage (the primary control). WAF runs before the Lambda integration, so blocked requests cost no Lambda compute:
    • Rate-based rules per source IP (e.g. block an IP exceeding N requests / 5 min) - caps each source's blast radius.
    • CAPTCHA / Challenge action on the sensitive public endpoints (login, forgot-password, registration) - a silent challenge that humans pass and bot floods fail, at the edge.
    • AWS Managed Rules: IP-reputation and anonymous-IP (Tor/VPN/proxy) lists.
  2. API Gateway throttling - per-method and per-stage rate + burst limits (no API key needed). Throttled requests return 429 without invoking Lambda, bounding the expensive part globally.
  3. CloudFront in front of API Gateway - absorbs/blocks volume at the AWS edge and gives AWS Shield Standard (L3/L4) for free. Add Shield Advanced (paid) for DDoS cost-protection insurance.

These are paid/optional services, so they are not bundled in the base deployment - but they are the right answer and should be enabled before exposing CognitoApi publicly.

Authorization (a separate concern)

Where a caller does have a credential, authorize properly instead of relying on a shared secret:

  • Already enforced: GET /v1/userinfo and DELETE /v1/users/{id} are gated by a Cognito User Pools JWT authorizer (validated at API Gateway from the pool's tokens - free, unlimited, no per-user API keys, which are also capped at 10,000/account/region). Extend the same authorizer to the other signed-in endpoints (e.g. logout, change-password, passkeys) as you harden.
  • Admin actions (user create / delete) - gate these with a Cognito client-credentials token + an admin scope, whose secret lives only in your backend.

โš ๏ธ As shipped today: no API key, a Cognito JWT authorizer on userinfo + delete-user, and the remaining endpoints open. Removing the key was deliberate - it was never real protection - but add WAF + throttling (and broaden the JWT/M2M authorizers above) before any public, production exposure.

API Documentation ๐Ÿ“š

Note The endpoints take no API key. Calls only need the inputs each endpoint documents (and, where shown, a user Authorization: Bearer token). Before exposing the API publicly, read Protecting against abuse & financial DDoS.

You can find a Postman collection here: postman/CognitoApi.postman_collection.json. You need to set the following variables inside Postman:

Variable Description
API_BASE_URL which is the dns name to use to call your auth api
EMAIL the email of the user you want to create
PASSWORD the password to use when you create a test user
VerificationType must be set to SOFTWARE_TOKEN_MFA

The TOTP inside Postman is generated for you automatically using the MFA_SECRET environment variable, which is set from the API call output, once the user has been confirmed.

Let's see how this API works:

User Management Lifecycle ๐Ÿ‘ฅ

  • Create a new user: Use a POST method on the endpoint: v1/users with the payload:
{
    "full_name": "Jane Doe",
    "email": "user@example.com",
    "mobile_phone_number": "+3301234567"
}

And you will get an answer that looks like this:

{
    "email": "user@example.com",
    "user_id": "129514d4-b081-7004-e470-b6adacd32db4",
    "status": "CREATED"
}

After this call the new user will receive an email containing a temporary password, that they need to use with the endpoint: v1/users/{{USER_ID}}/confirm to confirm the creation.

Create User API Call Create User Email

  • Confirm a new user: Use a POST method on the endpoint: v1/users/{{USER_ID}}/confirm with the payload:
{
    "email": "user@example.com",
    "temporary_password": "WpbyHbcIc#pNo3",
    "new_password": "WpbyHbcIc#pNp9"
}

And you will get an answer that looks like this:

{
    "email": "user@example.com",
    "user_id": "129514d4-b081-7004-e470-b6adacd32db4",
    "qr_code_secret": "UB42J65BKO473DIOOXGWOQZYT7AZKAS7W3AAHVTVT5IPRV",
    "otpauth_uri": "otpauth://totp/CognitoApi%3Auser%40example.com?secret=UB42J65BKO473DIOOXGWOQZYT7AZKAS7W3AAHVTVT5IPRV&issuer=CognitoApi",
    "mfa_session": "AYABeHVf5KBad3fI-guCzukJTdY...",
    "status": "PENDING_MFA"
}

Nothing is stored server-side: your client renders the QR code from otpauth_uri (or qr_code_secret), and you pass mfa_session back to the next call.

Confirm New User API Call

  • Confirm the MFA: Use a POST method on the endpoint: v1/users/{{USER_ID}}/confirm-mfa with the payload (the mfa_session comes from the previous response):
{
    "email": "{{EMAIL}}",
    "otp": "123456",
    "mfa_session": "AYABeHVf5KBad3fI-guCzukJTdY..."
}

And you will get an answer that looks like this:

{
    "email": "user@example.com",
    "user_id": "129514d4-b081-7004-e470-b6adacd32db4",
    "mfa_status": "CONFIRMED"
}

Confirm The New MFA API Call

  • For a forgotten password: Use a POST method on the endpoint: v1/forgot-password with the payload:
{
    "email": "user@example.com"
}

And you will get an answer that looks like this (the same generic response is returned whether or not the account exists, to avoid user enumeration):

{
    "email": "user@example.com",
    "status": "PASSWORD_FORGOT_CONFIRMATION_SENT"
}

Forgot Password API Call Forgot Password Email

  • To set a forgotten password: Use a POST method on the endpoint: /v1/users/{{USER_ID}}/confirm-password with the payload:
{
    "email": "user@example.com",
    "new_password": "#Y3KdGR9QKg_a9",
    "verification_code": "304482"
}

And you will get an answer that looks like this:

{
    "email": "user@example.com",
    "user_id": "129514d4-b081-7004-e470-b6adacd32db4",
    "status": "NEW_PASSWORD_SET_SUCCESSFULLY"
}

Set Forgotten Password API Call

MFA Reset (lost authenticator) ๐Ÿ”

If a user loses their authenticator app, they can re-establish MFA without an admin and without resetting their password. The flow proves both the password and control of the email, then issues a brand-new authenticator. Nothing is stored server-side and MFA stays mandatory the whole time.

  • Step 1 - start the reset: Use a POST method on the endpoint: v1/mfa-reset/start with the payload:
{
    "email": "user@example.com"
}

Cognito emails a one-time recovery code ("Reset your authenticator (MFA)") and returns an opaque session (the same generic response is returned whether or not the account exists, to avoid user enumeration):

{
    "status": "MFA_RESET_CODE_SENT",
    "session": "AYABeJKqmUy2EDG08Dw5N0wz..."
}
  • Step 2 - verify the code + password: Use a POST method on the endpoint: v1/mfa-reset/verify with the payload:
{
    "email": "user@example.com",
    "session": "AYABeJKqmUy2EDG08Dw5N0wz...",
    "code": "865079",
    "password": "WpbyHbcIc#pNp9"
}

On success a brand-new authenticator secret is issued (render it as a QR from otpauth_uri), along with an access token for the final step:

{
    "qr_code_secret": "KOGSXDSCFTF7D5QWXH4VCIKP...",
    "otpauth_uri": "otpauth://totp/CognitoApi%3Auser%40example.com?secret=KOGSXDSCFTF7D5QWXH4VCIKP...&issuer=CognitoApi",
    "access_token": "eyJraWQiOiJrM2ZpdkRvb3ls...",
    "status": "NEW_TOTP_ISSUED"
}
  • Step 3 - confirm the new authenticator: Use a POST method on the endpoint: v1/mfa-reset/confirm with a code from the freshly scanned authenticator and the access token from step 2:
{
    "access_token": "eyJraWQiOiJrM2ZpdkRvb3ls...",
    "otp": "921618"
}

The old authenticator is automatically replaced and the recovery session is revoked:

{
    "status": "MFA_RESET_COMPLETE"
}

User Authentication ๐Ÿ”

  • Perform the first step of the login process: Use a POST method on the endpoint: v1/login with the payload:
{
    "email": "user@example.com",
    "password": "WpbyHbcIc#pNp9"
}

And you will get an answer that looks like this:

{
    "email": "user@example.com",
    "verification_session": "AYABeLHXhcXCnAA3E29UUMbVqKgAHQABAAdTZXJ2aWNlABBDb2duaXRvVXNlclBvb2xz",
    "verification_type": "SOFTWARE_TOKEN_MFA"
}

1st step of the login API Call

After this call, the user needs to perform the MFA verification step using the verification session.

  • Perform the second step of the login process (MFA challenge): Use a POST method on the endpoint: v1/mfa-verify with the payload:
{
    "email": "user@example.com",
    "verification_type": "SOFTWARE_TOKEN_MFA",
    "verification_session": "AYABeLHXhcXCnAA3E29UUMbVqKgAHQABAAdTZXJ2aWNlABBDb2duaXRvVXNlclBvb2xz",
    "otp_code": "012345"
}

And you will get an answer that looks like this:

{
    "id_token": "eyJraWQiOiJpK0dwZFZLVUY1eG1ESml6Ukk2YTVWYTV6ZEtyXC8zeElyR",
    "access_token": "eyJraWQiOiJudUFPSENpcStPZnk3enF5TjFBZERSSEpQcUtZS1EwS",
    "refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNB",
    "expires_in": 3600
}

MFA step of the login API Call

At this moment, the user has been logged successfully.

  • To get new tokens using your RefreshToken: Use a POST method on the endpoint: v1/refresh-token with the payload:
{
    "email": "user@example.com",
    "refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ."
}

And you will get an answer that looks like this:

{
    "email": "user@example.com",
    "id_token": "eyJraWQiOiJpK0dwZFZLVUY1eG1ESml6Ukk2YTVWYTV6ZEtyXC8zeElyR2owZk",
    "access_token": "eyJraWQiOiJudUFPSENpcStPZnk3enF5TjFBZERSSEpQcUtZS1EwSU9mUGd",
    "refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVA",
    "expires_in": 3600
}

RefreshToken API Call

  • To get information about the connected user: Use a GET method on the endpoint: v1/userinfo with the header Authorization: Bearer {{ID_TOKEN}} and you will get an answer that looks like this:
{
    "name": "Jane Doe",
    "user_id": "129514d4-b081-7004-e470-b6adacd32db4",
    "email": "user@example.com",
    "phone_number": "+33601234567",
    "groups": []
}

Userinfo API Call

  • To logout: Use a POST method on the endpoint: v1/logout with the payload:
{
    "email": "user@example.com",
    "access_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoi"
}

And you will get an answer that looks like this:

{
    "user_status": "logout"
}

Logout API Call

API Endpoints Summary ๐Ÿ“‹

Endpoint Method Purpose
v1/users POST Create a new user
v1/users/{USER_ID} DELETE Delete a user
v1/users/{USER_ID}/confirm POST Confirm a new user
v1/users/{USER_ID}/confirm-mfa POST Confirm MFA setup
v1/resend-confirmation-code POST Resend the new-user confirmation code
v1/forgot-password POST Initiate self-service password reset (emails a code)
v1/users/{USER_ID}/confirm-password POST Complete the self-service password reset
v1/users/{USER_ID}/reset-password POST Admin-force a user's password reset
v1/users/{USER_ID}/change-password POST Change password while signed in (old + new)
v1/mfa-reset/start POST Start an MFA reset (emails a recovery code)
v1/mfa-reset/verify POST Verify code + password, issue a new authenticator
v1/mfa-reset/confirm POST Confirm the new authenticator, complete the reset
v1/login POST Initial login step
v1/mfa-verify POST Complete MFA verification
v1/refresh-token POST Get new tokens using refresh token
v1/userinfo GET Get authenticated user information
v1/logout POST Log out a user
v1/passkeys/register/start POST Begin passkey (WebAuthn) registration
v1/passkeys/register/complete POST Finish passkey registration
v1/passkeys/list POST List a user's registered passkeys
v1/passkeys/delete POST Delete one of a user's passkeys
v1/passkeys/login/start POST Begin passwordless passkey sign-in
v1/passkeys/login/complete POST Finish passwordless passkey sign-in

Passkeys (WebAuthn) ๐Ÿ”‘

CognitoApi supports passwordless, phishing-resistant passkeys (Touch ID / Windows Hello / phone). A user-verified passkey satisfies the mandatory-MFA requirement on its own, so sign-in needs no password and no TOTP - while MFA stays mandatory and Cognito-enforced.

  • Register (while signed in): POST /v1/passkeys/register/start โ†’ browser navigator.credentials.create() โ†’ POST /v1/passkeys/register/complete.
  • Sign in (passwordless): POST /v1/passkeys/login/start โ†’ browser navigator.credentials.get() โ†’ POST /v1/passkeys/login/complete โ†’ tokens.

Trying it locally

A zero-build tester lives in demos/passkey/passkey-test.html. Two things are deployer-specific because of how WebAuthn works - passkeys are bound to a domain (your pool's RelyingPartyId), so you cannot test from localhost or a mismatched domain:

  1. Set webauthn-relying-party-id in your tfvars to a domain you own (e.g. example.com).
  2. Serve the page over HTTPS from a host under that domain (e.g. app.example.com):
cd demos/passkey
brew install mkcert nss && mkcert -install          # one-time (trusts a local CA)
echo "127.0.0.1 app.example.com" | sudo tee -a /etc/hosts
PASSKEY_HOST=app.example.com python3 serve_https.py  # auto-creates the TLS cert
# open https://app.example.com:8443/passkey-test.html

In the page: fill your API base URL + email, click Login โ†’ fill access_token, then Register passkey, then Sign in with passkey. The full passkey experience is also built into the React example app.

Demo Application ๐Ÿš€

Want to see CognitoApi in action? Check out our integration example with a React frontend:

React Demo App

This example app demonstrates:

  • ๐Ÿ”‘ Complete authentication flow integration
  • ๐Ÿ”„ Token refresh handling
  • ๐Ÿ‘ค User registration and confirmation process
  • ๐Ÿ”’ MFA implementation
  • ๐Ÿ’ผ Session management

The demo application is a great starting point to understand how to integrate CognitoApi with your frontend and provides a practical reference for implementation.

# Clone the demo app
git clone https://github.com/TocConsulting/cognito-api-react-example-app.git

# Follow the setup instructions in the repo's README

Supported Regions ๐ŸŒŽ

The solution is designed to work in any AWS region where Cognito is available:

Region Flag Region Name Region Code
๐Ÿ‡บ๐Ÿ‡ธ US East (N. Virginia) us-east-1
๐Ÿ‡บ๐Ÿ‡ธ US East (Ohio) us-east-2
๐Ÿ‡บ๐Ÿ‡ธ US West (N. California) us-west-1
๐Ÿ‡บ๐Ÿ‡ธ US West (Oregon) us-west-2
๐Ÿ‡จ๐Ÿ‡ฆ Canada (Central) ca-central-1
๐Ÿ‡ช๐Ÿ‡บ Europe (Frankfurt) eu-central-1
๐Ÿ‡ฌ๐Ÿ‡ง Europe (London) eu-west-2
๐Ÿ‡ฎ๐Ÿ‡ช Europe (Ireland) eu-west-1
๐Ÿ‡ซ๐Ÿ‡ท Europe (Paris) eu-west-3
๐Ÿ‡ธ๐Ÿ‡ฌ Asia Pacific (Singapore) ap-southeast-1
๐Ÿ‡ฆ๐Ÿ‡บ Asia Pacific (Sydney) ap-southeast-2
๐Ÿ‡ฏ๐Ÿ‡ต Asia Pacific (Tokyo) ap-northeast-1

License ๐Ÿ“„

MIT License