Cognito collects a user’s attributes, it enables simple, secure user authentication, authorization and user management for web and mobile apps.
Overview of Cognito
In this section, we’ll take a 5000 feet view of how Cognito integrates with a web app.
Cognito requires users to verify their email or phone number to enable password recovery flow. The way that verification
works is by sending a numeric code to the user’s email or phone number (using SMS) and the user the app then calling
CognitoAuth.confirmSignUp like step #6 of the diagram above.
Next, we’ll see how to:
- Create a new React webapp using
- Add a
- Create authenticated and unauthenticated
- Provision a Cognito user pool using Terraform
Integration with React
We can use Cognito to secure certain sections of our React app, making sure that only authenticated users have access to them.
We’ll start by creating a new React web app using
npx create-react-app demo-app --template typescript
The core piece of this integration is the
useAuth hook which is based on https://usehooks.com/useAuth/
and adapted to use Cognito’s API.
After installing the Cognito API package from Amazon, and the
npm install --save @aws-amplify/auth use-async-effect
we’ll go ahead and create a new file for our
useAuth hook under
src/hooks/ and we’ll call it
Next, we’ll configure the Cognito Auth object with the AWS Region, Cognito User Pool Id and Cognito Web Client Id. You can see these values are being included from environment variables, so it’s easy to configure our app depending on where it runs (local development, continuous integration, staging, production, etc.).
Now let’s create some types for our hook’s API:
User type has the properties we expect for a user. The
User.country property is an example of a
Cognito custom attribute
Credentials type will be used in our
CognitoUserWithChallenge is a bit of a crutch that we need, to make the Typescript compiler happy. The
object returned by the
CognitoAuth.signIn function (from the
can contain a property called
challengeName however in the typings for the
CognitoAuth.signIn that property is not
present therefore we have to supplement the type in our code. The
challengeName property is used to convey that the
user needs to respond to a challenge to verify their identity. So for example when the user signs up they are emailed a
code that they must input as a challenge response. For more information
Finally, we’ll get to the implementation of our hook. In reality, we have a private hook
Context and all are exposed through the public
First, we’ll dissect the
useProvideAuth private hook which has the bulk of the code:
useProvideAuth hook has three public functions:
CognitoAuth.signInand if the result of the call has a
challengeNamereturns that, otherwise converts the returned
userstate and also toggles the
authInProgressstate to false.
logOut: sets the
CognitoAuth.signOutto invalidate the user’s token.
CognitoAuth.signUpwith the required user attributes plus their password.
A couple of other interesting points about this hook is the use of
useAsyncEffect (from the
package) which has the same semantics of plain
useEffect but allows for using
async functions. We need this hook
because we determine, on render, if the user is logged in by calling the
getUser function which in itself is
async. We also set a listener through the AWS
Hub class which will notify us of different auth-related events that
Now that our hook is ready, we’ll go ahead and set up a
Context so our app can take advantage of this infrastructure:
In the snippet above, we create a
Context of type
Auth | null, where
we also created a tiny component
<AuthProvider> which passes an instance of
Auth to the
useContext(AuthContext) and ensures that the returned
Auth is never
After having the auth infrastructure in place, we now need to start using in our app, the first step is to wrap our
component hierarchy in an
<AuthProvider>, like such:
You’ll notice a few points in the snippet above:
- we are using
react-router-domfor our navigation.
- we have wrapped our app in the
<AuthProvider>that we just created, which means that the
useAuthhook is now usable to all our components.
- we have a couple of utility route components that are not part of
<AuthenticatedRoute> is using
useAuth to determine if the authentication is in progress (and if so display
an empty page), if we have an authenticated user we then route to the specified component and if we don’t have an
authenticated user then we redirect to the
/login page, preserving the
location in the state, so we can redirect to
it once the user successfully logs in.
is basically a mirror of
<AuthenticatedRoute> in that it will only render the
Route if the user is not
authenticated, redirecting to
The following is a very simple log-in page, for illustration purposes only, using the
useAuth hook, although you’ll
probably want to use a proper react form library and maybe some kind of UI library too.
Using Terraform to provision Cognito
The following Terraform module was used to provision the Cognito user pool used in this blog post:
Some interesting sections of this module:
- Lines 24-34: The
password_policyblock is where we configure the requirements of a valid password. As a tip for better UX, make sure that your signup page form, validates the user’s password client-side using the same requirements. You can also leverage a package like
react-password-strength-barto nudge the user to create strong passwords.
- Line 37-73: The
schemablock is where we define attributes for the user’s profile. Custom attributes must use the
custom:prefix on their names.
- Line 80: The
username_attributesarray indicates that we are using the
aws_cognito_user_pool_client resource declares a web client for our user pool, which is, you guessed it, our React
app. This is where we configure the validity periods for the tokens returned by Cognito in lines 104-111. Line
generate_secret = false ensures that a secret is not generated for this client which is necessary because the
browser Cognito js library doesn’t support secrets.
Lines 123-134 indicate which attributes, as defined in the user pool itself, are readable and/or writable by the web client.
AWS Cognito is a good option when it comes to user authentication and management. It’s specially useful when you are already bought into the AWS ecosystem and it’s also cheaper than some other alternatives.
It’s not too difficult to integrate React and Cognito, however Cognito’s reference documentation for their
amazon-cognito-identity-js) that make it hard to pinpoint exactly which one
is necessary for Cognito to work. Amazon also tends to push Amplify as a whole
for the auth solution when it’s not really necessary to add all that code to your app.
At Xtages we are building an all-in-one solution for CI/CD and hosting of apps, all with minimal configuration and no infrastructure to manage. We recently introduced a free plan (no credit card required) to make it easier to get started.