Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 7, 2022 01:50 pm GMT

Basic Authentication with AWS Cognito & NextJS

Ive written quite a few articles about authentication before. This is yet another user auth article.

However, this time its a little different. Previous articles have been about managing user authentication yourself. In this article, we will be leveraging AWS Cognito and its user pools for the same functionality.

You can check out some of my previous articles on handling user auth manually here:

  1. How to Create Registration & Authentication with Express & PassportJS.
  2. How to Handle Password Reset in ExpressJS.
  3. How to Verify Users in ExpressJS

Setup

This article assumes that you have an AWS Cognito user pool set up and that you have some NextJS boilerplate code set up as well. I will write another article explaining how to set up a Cognito user pool using the AWS console.

First, lets understand the project structure.

. components  layouts   InputLayout.js  AuthLinkText.js  InputField.js  InputHelperText.js  Label.js  SubmitButton.js hooks  useAuth.js  useRegister.js  useValidationSchema.js pages  api   confirm    index.js    send.js   password    reset.js    reset\_code.js   login.js   register.js  password   reset.js   reset\_code.js  \_app.js  confirm.js  index.js  login.js  register.js public  favicon.ico  vercel.svg styles  Home.module.css  globals.css README.md next.config.js package-lock.json package.json

Most of these are nothing special as theyre created automatically when you set up a NextJS project.

The components folder contains all the re-usable custom components like form input fields and layouts.

For this article, I will focus on the hooks and pages folders. If youd like to look at the full code, you can find it on Github.

The hooks folder has 3 files:

  1. useAuth.js contains a hook that will handle user sign in and password reset.
  2. useRegister.js contains a hook that will handle user registration and verification.
  3. useValidationSchema.js contains the form validation schemas. We wont be covering this in the article. Feel free to check the repo out for more details on this.

I prefer to use hooks because they allow me to separate the business logic from the UI logic in the component.

The pages folder has an API sub-directory. This is where all the backend code lives.

NextJS uses file-system based routing so the file structure is what determines the API endpoints.

All the other files and sub-directories outside the API sub-directory will be treated as frontend pages.

Make sure to install the @aws-sdk/client-cognito-identity-provider package.

You can refer to the full CognitoIdentityServiceProvider SDK for more in-depth explanations of what is discussed in this article.

Sign Up

First, lets handle user registration. Navigate to the pages/api/register.js and add the following code:

import { CognitoIdentityProviderClient, SignUpCommand } from '@aws-sdk/client-cognito-identity-provider'const { COGNITO_REGION, COGNITO_APP_CLIENT_ID } = process.envexport default async function handler(req, res) {    if (req.method !== 'POST') return res.status(405).send()    const params = {        ClientId: COGNITO_APP_CLIENT_ID,        Password: req.body.password,        Username: req.body.username,        UserAttributes: [            {                Name: 'email',                Value: req.body.email            }        ]    }    const cognitoClient = new CognitoIdentityProviderClient({        region: COGNITO\_REGION    })    const signUpCommand = new SignUpCommand(params)    try {        const response = await cognitoClient.send(signUpCommand)        return res.status(response['$metadata'].httpStatusCode).send()    } catch (err) {        console.log(err)        return res.status(err['$metadata'].httpStatusCode).json({ message: err.toString() })    }}

This is a handler for the /api/register endpoint. Include a guard clause to make sure only POST requests are allowed, return a 405 error for any other type of request.

In the params, we have the following:

  1. ClientId App client Id that you created for this user pool in the AWS console.
  2. Password The users chosen password.
  3. Username -The users chosen username.
  4. UserAttributes Any additional attributes provided by the user upon signing up. The default list of attributes includes address, nickname, birthdate, phone number, email, family name, preferred username, gender, profile, given name, zoneinfo, locale, updated at, middle name, website and name. You can add custom attributes as well (for example, Department if youre creating an auth system for an organisation).

Keep in mind that if youre setting a custom user attribute you need to follow the following format:

{    Name: 'custom:<AttributeName>',                 Value: '<AttributeValue>'}

In the case of adding a department attribute, it would look like this:

{    Name: 'custom:Department',                  Value: 'Engineering'}

Then we send the SignUpCommand to trigger a user sign up. Return a status 200 response if all goes well, otherwise, return the error code from Cognito and the stringified version of the error.

Cognito will usually return an error that looks like this:

UsernameExistsException: User already exists...{  '$fault': 'client',  '$metadata': {    httpStatusCode: 400,    requestId: '9442223e-ef29-40c1-880e-6689638d8042',    extendedRequestId: undefined,    cfId: undefined,    attempts: 1,    totalRetryDelay: 0  },  __type: 'UsernameExistsException'}

Grab the status code and message and then forward them to the front end.

When the request is successful, Cognito will return a response that looks like this:

{  '$metadata': {    httpStatusCode: 200,    requestId: '67f6d99c-d13c-4677-ab46-957d88f62bb9',    extendedRequestId: undefined,    cfId: undefined,    attempts: 1,    totalRetryDelay: 0  },  AuthenticationResult: {    AccessToken: "...",    ExpiresIn: 3600,    NewDeviceMetadata: undefined,    RefreshToken: "...",    TokenType: "Bearer"  },  ChallengeName: undefined,  ChallengeParameters: {},  Session: undefined}

For this application, were only interested in the Authentication result as it contains the access and refresh tokens.

Flesh out the useRegister hook in order to make use of the API endpoint we created above.

Create a method called register with the following logic:

import { useRouter } from 'next/router'export default function useRegister() {    const router = useRouter()    const register = (values, { setSubmitting }) => {        fetch('/api/register', {            method: 'POST',            headers: {                'Content-Type': 'application/json'            },            body: JSON.stringify(values)        }).then(res => {            if (!res.ok) throw res            router.push({                pathname: '/confirm',                query: { username: values?.username }            },                "/confirm")        }).catch(err => {            console.error(err)        }).finally(() => {            setSubmitting(false)        })    }    const confirm = (values, { setSubmitting }) => {        // Confirm the user    }    return {        register,        confirm    }}

The register method is the submit method for our registration form. The values object contains the form data and setSubmitting allows us to update the loading state once were done processing the request.

This will be a common theme with most of the hooks in this project.

Set the Content-Type header to application/json to help NextJS parse the request body.

In this method, we call the register endpoint and pass all the form values (which include username, email, and password). If the request is successful, we redirect to the confirm page and prompt the user to verify their email address. More on this in the verification section.

You might notice we have an empty confirm method here. This method will handle the request to verify the user. We will cover this in the verification stage as well.

Here is what the registration form will look like, notice we import the useRegister hook and pass the register method as the form submit handler.

import { Formik } from "formik";import InputLayout from "../components/layouts/InputLayout";import Label from "../components/Label";import InputField from "../components/InputField";import InputHelperText from "../components/InputHelperText";import AuthLinkText from "../components/AuthLinkText";import SubmitButton from "../components/SubmitButton";import useValidationSchema from "../hooks/useValidationSchema";import useRegister from '../hooks/useRegister'import Link from "next/link";export default function Register() {    const { registerSchema } = useValidationSchema()    const { register } = useRegister()    return (        <div style={{            padding: "10px"        }}>            <Formik                initialValues={{                    username: "",                    email: "",                    password: "",                    confirm_password: ""                }}                validationSchema={registerSchema}                onSubmit={register}                validateOnMount={false}                validateOnChange={false}                validateOnBlur={false}>                {({                    isSubmitting,                    errors,                    values,                    handleSubmit,                    handleChange,                    handleBlur                }) => (                    <form onSubmit={handleSubmit}>                        <InputLayout>                            <Label>Username</Label>                            <InputField                                type="text"                                name="username"                                placeholder="Username"                                onChange={handleChange}                                onBlur={handleBlur}                                value={values?.username}                            />                            <InputHelperText isError>{errors?.username}</InputHelperText>                        </InputLayout>                        <InputLayout>                            <Label>Email</Label>                            <InputField                                type="email"                                name="email"                                placeholder="Email"                                onChange={handleChange}                                onBlur={handleBlur}                                value={values?.email}                            />                            <InputHelperText isError>{errors?.email}</InputHelperText>                        </InputLayout>                        <InputLayout>                            <Label>Password</Label>                            <InputField                                type="password"                                name="password"                                placeholder="Password"                                onChange={handleChange}                                onBlur={handleBlur}                                value={values?.password}                            />                            <InputHelperText isError>{errors?.password}</InputHelperText>                        </InputLayout>                        <InputLayout>                            <Label>Confirm password</Label>                            <InputField                                type="password"                                name="confirm_password"                                placeholder="Confirm password"                                onChange={handleChange}                                onBlur={handleBlur}                                value={values?.confirm_password}                            />                            <InputHelperText isError>{errors?.confirm_password}</InputHelperText>                        </InputLayout>                        <InputLayout>                            <AuthLinkText href="/login">Already have an account? Log in</AuthLinkText>                        </InputLayout>                        <SubmitButton isSubmitting={isSubmitting} />                    </form>                )}            </Formik>        </div>    )}

Sign In

Now that weve registered our users, its time to allow them to log into our application by authenticating them against our Cognito user pool.

Add the following code to the pages/api/login.js file.

import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider"const { COGNITO_REGION, COGNITO_APP_CLIENT_ID } = process.envexport default async function handler(req, res) {    if (req.method !== 'POST') return res.status(405).send()    const params = {        AuthFlow: 'USER_PASSWORD_AUTH',        ClientId: COGNITO_APP_CLIENT_ID,        AuthParameters: {            USERNAME: req.body.username,            PASSWORD: req.body.password        }    }    const cognitoClient = new CognitoIdentityProviderClient({        region: COGNITO_REGION    })    const initiateAuthCommand = new InitiateAuthCommand(params)    try {        const response = await cognitoClient.send(initiateAuthCommand)        return res.status(response['$metadata'].httpStatusCode).json({            ...response.AuthenticationResult        })    } catch(err) {        console.log(err)        return res.status(err['$metadata'].httpStatusCode).json({ message: err.toString() })    }}

At the moment, were handling only username/password auth. To do this we need to use the USER_PASSWORD_AUTH flow. You can find out more about this and other authentication flows here.

The final parameter is AuthParameters. This simply contains the username and password passed to the endpoint upon form submission. Be careful here and make sure the auth parameters are in all uppercase as you see here, otherwise, they will not be recognised.

Now lets divert our attention to the useAuth hook. Add this code to the hooks/useAuth.js file:

import { useRouter } from "next/router";export default function useAuth(){  const router = useRouter()  const login = (values, { setSubmitting }) => {    fetch('/api/login', {      method: 'POST',      headers: {        'Content-Type': 'application/json'      },      body: JSON.stringify(values)    }).then(res => {      if (!res.ok) throw res    }).then(data => {      console.log(data)    }).catch(async err => {      const responseData = await err.json()      if (responseData?.message?.includes("UserNotConfirmedException:")) {        // Trigger confirmation code email        await fetch('/api/confirm/send', {          method: 'POST',          headers: {            'Content-Type': 'application/json'          },          body: JSON.stringify({ username: values.username })        })        await router.push(      {            pathname: "/confirm",            query: {username: values.username},          },          "/confirm")      }    }).finally(() => {      setSubmitting(false)    })  }  const resetPasswordRequest = (values, { setSubmitting }) => {    // Send the password reset code  }  const resetPassword = (values, { setSubmitting }) => {    // Send request to reset password  }  return {    login,    resetPasswordRequest,    resetPassword  }}

Here we have a login method that is similar to the register method we created above. The values object represents our form data (username and password).

Its important to note that Cognito will not allow a user to be authenticated if they have not yet been verified/confirmed. So attempting this login with an unconfirmed user will return an error.

To handle this, trigger an endpoint to send a confirmation email to the user and then redirect them to the confirm page so they can be verified. More on this endpoint in the verification section.

Our login page has the following code:

import { Formik } from "formik";import InputLayout from "../components/layouts/InputLayout";import Label from "../components/Label";import InputField from "../components/InputField";import InputHelperText from "../components/InputHelperText";import AuthLinkText from "../components/AuthLinkText";import SubmitButton from "../components/SubmitButton";import useAuth from "../hooks/useAuth";import useValidationSchema from "../hooks/useValidationSchema";import { useRouter } from "next/router";export default function Login() {  const router = useRouter();  const { success } = router.query;  const { loginSchema } = useValidationSchema();  const { login } = useAuth();  return (    <div style={{      padding: "10px"    }}>      {        success === "true" &&        <div style={{          paddingTop: "10px",          paddingBottom: "10px",          color: "green"        }}>          {'You\\'re signed up!'}        </div>      }      <Formik        initialValues={{          username: "",          password: ""        }}        validationSchema={loginSchema}        onSubmit={login}        validateOnMount={false}        validateOnChange={false}        validateOnBlur={false}>        {({          isSubmitting,          errors,          values,          handleChange,          handleBlur,          handleSubmit        }) => (          <form onSubmit={handleSubmit}>            <InputLayout>              <Label>Username</Label>              <InputField                type="text"                name="username"                placeholder="Username or email"                onChange={handleChange}                onBlur={handleBlur}                value={values?.username}              />              <InputHelperText isError>{errors?.username}</InputHelperText>            </InputLayout>            <InputLayout>              <Label>Password</Label>              <InputField                type="password"                name="password"                placeholder="Password"                onChange={handleChange}                onBlur={handleBlur}                value={values?.password}              />              <InputHelperText isError>{errors?.password}</InputHelperText>            </InputLayout>            <InputLayout>              <AuthLinkText href="https://dev.to/password/reset_code">{'Forgot password?'}</AuthLinkText>            </InputLayout>            <InputLayout>              <AuthLinkText href="https://dev.to/register">{'Don\\'t have an account? Register.'}</AuthLinkText>            </InputLayout>            <SubmitButton isSubmitting={isSubmitting} />          </form>        )}      </Formik>    </div>  );}

Just like before, we import the useAuth hook and make use of its login method as the form submission handler.

We will discuss the 2 password reset methods in the useAuth hook in the password reset section.

Verification

So weve registered our users, but we cannot authenticate them because they are not verified. Lets fix that.

A few things to note before we continue:

  1. I have set up my user pool to use an email code for verification. This is where AWS will send an email with a code that the user has to enter manually on your app. Other authentication methods include: a clickable verification link and a verification code sent to their phone number. If you are using any of the other verification methods (except verification code to a phone number), then you can go ahead and skip this section.
  2. Cognito will automatically trigger the chosen verification method upon successful registration. Verification can also be triggered via the SDK.

In the pages/api/confirm/index.js file, add the following code:

import {    CognitoIdentityProviderClient,    ConfirmSignUpCommand} from "@aws-sdk/client-cognito-identity-provider"const { COGNITO_REGION, COGNITO_APP_CLIENT_ID } = process.envexport default async function handler (req, res) {    if (req.method !== 'POST') return res.status(405).send()    const params = {        ClientId: COGNITO_APP_CLIENT_ID,        ConfirmationCode: req.body.code,        Username: req.body.username    }    const cognitoClient = new CognitoIdentityProviderClient({        region: COGNITO_REGION    })    const confirmSignUpCommand = new ConfirmSignUpCommand(params)    try {        const response = await cognitoClient.send(confirmSignUpCommand)        console.log(response)        return res.status(response['$metadata'].httpStatusCode).send()    } catch (err) {        console.log(err)        return res.status(err['$metadata'].httpStatusCode).json({ message: err.toString() })    }}

Here create and send confirmSignUpCommand with the following parameters:

  1. ClientId.
  2. ConfirmationCode The code sent to the users email/phone by Cognito.
  3. Username The username of the user youd like to verify.

Note that because this is the index file within the confirm directory, we do not need to specify the filename when calling this endpoint. The endpoint would just be /api/confirm

In the same confirm directory, we have a file called send.js. This file is responsible for manually triggering the confirmation code email.

Add the following code to this file:

import {    CognitoIdentityProviderClient,    ResendConfirmationCodeCommand} from "@aws-sdk/client-cognito-identity-provider"const { COGNITO_REGION, COGNITO_APP_CLIENT_ID } = process.envexport default async function handler (req, res) {    if (req.method !== 'POST') return res.status(405).send()    const params = {        ClientId: COGNITO_APP_CLIENT_ID,        Username: req.body.username    }    const cognitoClient = new CognitoIdentityProviderClient({        region: COGNITO_REGION    })    const resendConfirmationCodeCommand = new ResendConfirmationCodeCommand(params)    try {        const response = await cognitoClient.send(resendConfirmationCodeCommand)        console.log(response)        return res.status(response['$metadata'].httpStatusCode).send()    } catch (err) {        console.log(err)        return res.stat(err['$metadata'].httpStatusCode).json({ message: err.toString() })    }}

Here, simply create and send ResendConfirmationCodeCommand with a params object containing the ClientId and the username of the user youd like to verify.

Cognito will search for the user with the specified username, then send a verification code to their email/phone number depending on your settings and/or which one is provided.

You might be wondering why this command is called ResendConfirmationCodeCommand.

Cognito will automatically send a verification code to the user upon signing up.

If you are triggering the registration manually, then you are always re-sending the code.

Remember that confirm method in the useRegister hook? Its time to flesh it out.

Add the following logic in that method:

const confirm = (values, { setSubmitting }) => {        fetch('/api/confirm', {            method: 'POST',            headers: {                'Content-Type': 'application/json'            },            body: JSON.stringify(values)        }).then(res => {            if (!res.ok) throw res            router.push({                pathname: '/login',                query: { confirmed: true }            },                "/login")        }).catch(err => {            console.error(err)        }).finally(() => {            setSubmitting(false)        })    }

From this method, call the /api/confirm endpoint with the form values. If the confirmation is successful, redirect the user to the login page so they can sign in.

Heres a look at the confirm page in the pages/confirm.js file:

import { Formik } from "formik";import InputLayout from "../components/layouts/InputLayout";import Label from "../components/Label";import InputField from "../components/InputField";import InputHelperText from "../components/InputHelperText";import SubmitButton from "../components/SubmitButton";import useValidationSchema from "../hooks/useValidationSchema";import useRegister from "../hooks/useRegister";import { useRouter } from "next/router";export default function Confirm(){    const router = useRouter();    const { username } = router.query;    const { confirm } = useRegister();    const { confirmSchema } = useValidationSchema();    return (        <div style={{            padding: "10px"        }}>            <Formik                initialValues={{                    username: username,                    code: ""                }}                onSubmit={confirm}                validationSchema={confirmSchema}                validateOnMount={false}                validateOnChange={false}                validateOnBlur={false}>                {                    ({                        isSubmitting,                        errors,                        values,                        handleSubmit,                        handleChange,                        handleBlur                     }) => (                        <form onSubmit={handleSubmit}>                            <InputLayout>                                <Label>Confirmation Code</Label>                                <InputField                                    type={"text"}                                    name={"code"}                                    placeholder={"Code"}                                    onChange={handleChange}                                    onBlur={handleBlur}                                    value={values?.code}                                />                                <InputHelperText isError>{errors?.code}</InputHelperText>                            </InputLayout>                            <SubmitButton isSubmitting={isSubmitting} />                        </form>                    )                }            </Formik>        </div>    )}

Password reset

One of the most important features in any auth system is the ability to reset passwords.

Fortunately, password reset is made dead simple by Cognito.

The password reset flow is similar to the verification flow but with some extra steps:

  1. The user clicks the Forgot password link and is redirected to a page where they are prompted to enter their username.
  2. Send a confirmation code to the users email/number.
  3. If the code is sent successfully, redirect the user to a reset page where they enter the code, along with their new password.

First, prepare the endpoint to trigger the verification email containing the code.

Add the following code to pages/api/password/reset_code.js:

import { CognitoIdentityProviderClient, ForgotPasswordCommand } from '@aws-sdk/client-cognito-identity-provider'const { COGNITO_REGION, COGNITO_APP_CLIENT_ID } = process.envexport default async function handler (req, res) {    if (req.method !== 'POST') return res.status(405).send()    const params = {        ClientId: COGNITO_APP_CLIENT_ID,        Username: req.body.username    }    const cognitoClient = new CognitoIdentityProviderClient({        region: COGNITO_REGION    })    const forgotPasswordCommand = new ForgotPasswordCommand(params)    try {        const response = await cognitoClient.send(forgotPasswordCommand)        console.log(response)        return res.status(response['$metadata'].httpStatusCode).send()    } catch (err) {        console.log(err)        return res.status(err['$metadata'].httpStatusCode).json({ message: toString() })    }}

Here we accept the ClientId and username params for the ForgotPasswordCommand command. Cognito will find a user with the matching username and send them a verification code using the configured method.

In the pages/api/password/reset.js file, add the code below:

import {    CognitoIdentityProviderClient,    ConfirmForgotPasswordCommand} from "@aws-sdk/client-cognito-identity-provider"const { COGNITO_REGION, COGNITO_APP_CLIENT_ID } = process.envexport default async function handler(req, res) {    if (req.method !== 'POST') return res.status(405).send()    const params = {        ClientId: COGNITO_APP_CLIENT_ID,        ConfirmationCode: req.body.code,        Password: req.body.password,        Username: req.body.username    }    const cognitoClient = new CognitoIdentityProviderClient({        region: COGNITO_REGION    })    const confirmForgotPasswordCommand = new ConfirmForgotPasswordCommand(params)    try {        const response = await cognitoClient.send(confirmForgotPasswordCommand)        console.log(response)        return res.status(response['$metadata'].httpStatusCode).send()    } catch (err) {        console.log(err)        return res.status(err['$metadata'].httpStatusCode).json({ message: err.toString() })    }}

Here we accept the ConfirmationCode that was sent to the user, their new password and their username.

Cognito will take care of all the logic of making sure that the code provided is valid and is the one that was sent to the user with the specified username.

Youll remember that in the useAuth hook, we had 2 empty methods resetPasswordRequest and resetPassword. We are going to flesh those out now.

Add the following logic to those methods:

const resetPasswordRequest = (values, { setSubmitting }) => {    fetch('/api/password/reset\_code', {      method: 'POST',      headers: {        'Content-Type': 'application/json'      },      body: JSON.stringify(values)    }).then(res => {      if (!res.ok) throw res      router.push({        pathname: '/password/reset',        query: { username: values.username }      },        "/password/reset")    }).catch(err => {      console.error(err)    }).finally(() => {      setSubmitting(false)    })  }  const resetPassword = (values, { setSubmitting }) => {    fetch('/api/password/reset', {      method: 'POST',      headers: {        'Content-Type': 'application/json'      },      body: JSON.stringify(values)    }).then(res => {      if (!res.ok) throw res      router.push({        pathname: '/login',        query: { reset: true }      },        "/login")    }).catch(err => {      console.error(err)    }).finally(() => {      setSubmitting(false)    })  }

The resetPasswordRequest method triggers the /api/password/reset_code endpoint.

From the users perspective, they are redirected to a page where they enter their username. Once they submit the form, they receive an email and are redirected to the password reset page (if the email was successfully sent).

The resetPassword method triggers the /api/password/reset endpoint to actually reset the password.

Heres how the forgot password form looks:

import { Formik } from "formik";import InputLayout from "../../components/layouts/InputLayout";import Label from "../../components/Label";import InputField from "../../components/InputField";import InputHelperText from "../../components/InputHelperText";import SubmitButton from "../../components/SubmitButton";import useValidationSchema from "../../hooks/useValidationSchema";import useAuth from '../../hooks/useAuth';export default function ResetCode(){    const { resetPasswordRequestSchema } = useValidationSchema();    const { resetPasswordRequest } = useAuth();    return (        <div style={{            padding: "10px"        }}>            <Formik                initialValues={{                    username: ""                }}                onSubmit={resetPasswordRequest}                validationSchema={resetPasswordRequestSchema}                validateOnMount={false}                validateOnChange={false}                validateOnBlur={false}                >                {                    ({                        isSubmitting,                        errors,                        values,                        handleSubmit,                        handleChange,                        handleBlur                    }) => (                        <form onSubmit={handleSubmit}>                            <InputLayout>                                <Label>Username</Label>                                <InputField                                    type={"text"}                                    name={"username"}                                    placeholder={"Username"}                                    onChange={handleChange}                                    onBlur={handleBlur}                                    value={values?.username}                                />                                <InputHelperText isError>{errors?.username}</InputHelperText>                            </InputLayout>                            <SubmitButton isSubmitting={isSubmitting} />                        </form>                    )                }            </Formik>        </div>    )}

Heres how the password reset form looks:

import { Formik } from "formik";import InputLayout from "../../components/layouts/InputLayout";import Label from "../../components/Label";import InputField from "../../components/InputField";import InputHelperText from "../../components/InputHelperText";import SubmitButton from "../../components/SubmitButton";import useValidationSchema from "../../hooks/useValidationSchema";import useAuth from '../../hooks/useAuth';import { useRouter } from "next/router";export default function Reset(){    const router = useRouter()    const { username } = router.query    const { resetPasswordSchema } = useValidationSchema();    const { resetPassword } = useAuth()    return (        <div style={{            padding: "10px"        }}>            <Formik                initialValues={{                    username: username,                    code: "",                    password: "",                    confirm\_password: ""                }}                validationSchema={resetPasswordSchema}                onSubmit={resetPassword}                validateOnMount={false}                validateOnChange={false}                validateOnBlur={false}            >                {                    ({                        isSubmitting,                        errors,                        values,                        handleSubmit,                        handleBlur,                        handleChange                    }) => (                        <form onSubmit={handleSubmit}>                            <InputLayout>                                <Label>Reset code</Label>                                <InputField                                    type={"text"}                                    name={"code"}                                    placeholder={"Reset code"}                                    onChange={handleChange}                                    onBlur={handleBlur}                                    value={values?.code}                                />                                <InputHelperText isError>{errors?.code}</InputHelperText>                            </InputLayout>                            <InputLayout>                                <Label>New password</Label>                                <InputField                                    type={"password"}                                    name={"password"}                                    placeholder={"New password"}                                    onChange={handleChange}                                    onBlur={handleBlur}                                    value={values?.password}                                />                                <InputHelperText isError>{errors?.password}</InputHelperText>                            </InputLayout>                            <InputLayout>                                <Label>Confirm password</Label>                                <InputField                                    type={"password"}                                    name={"confirm_password"}                                    placeholder={"Confirm password"}                                    onChange={handleChange}                                    onBlur={handleBlur}                                    value={values?.confirm_password}                                />                                <InputHelperText isError>{errors?.confirm_password}</InputHelperText>                            </InputLayout>                            <SubmitButton isSubmitting={isSubmitting} />                        </form>                    )                }            </Formik>        </div>    )}

Caveat

A major caveat with the implementation weve just created is that 2 or more users can actually register with the same email.

We dont want any user to receive a code for another users password reset.

For this and many other reasons, you may want to force unique emails for each user.

We can easily achieve this using Cognito triggers which are event triggers that run custom lambda functions. These allow us to customise our workflows.

We can trigger lambdas during any of the following events (and more):

  1. Pre-signup Right before Cognito signup is triggered.
  2. Pre-authentication Right before the user is authenticated by Cognito.
  3. Custom message Right before a verification/confirmation message is sent. We can dynamically edit the message here.
  4. Post-authentication After the user is successfully authenticated. If the authentication fails, this will not be triggered.
  5. Post-confirmation After a user is verified/confirmed. You can use this to send a welcome email or enable certain privileges that are only available to confirmed users.

These are only a few of the triggers available to us. You can find more information on these triggers here.

In our case, the trigger were most interested in is the Pre-signup trigger. We can check if there are any current users who have the same email address as the one weve just received for signup. If so, throw an error and fail early before the Cognito signup.

I will publish an article that demonstrates how to achieve this.


Original Link: https://dev.to/kelvinvmwinuka/basic-authentication-with-aws-cognito-nextjs-4dal

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To