- Caleb Olojo
- 0 comments
- 15 minutes of reading
- JavaScript,Next.js,Performance,safety
About the author
Caleb is a front-end developer and technical writer. He enjoys creating JAMStack applications that are aesthetic, accessible and dynamic on and for the web. O...More about Caleb↬
e-newsletter
.
Trusted by over 200,000 people.
In general, authentication presents a stumbling block for many people when trying to get started with a particular framework, and Next.js is no slouch.
However, there are many resources for building authenticated applications with Next.js. Well, there's even oneopen source projectwhich handles authentication literally from scratch.
However, the scope of this article is not about the whole concept of authentication. We've selected just one specific pattern of the authentication process: the "password reset" flow and how it can be implemented on the client side, the front end, of a web application.
In this article, you'll learn how to use this function with the Axios dataset retrieval tool, the built-in function for dynamic routes in Next.js, and theuse routers
Hook.
An overview of the password reset process
Since the advent of the Internet, engineers have struggled to provide solutions to problems that arose in the early days of the Internet, and Internet software security is no exception.
There is that popular saying that goes like this: "Users will always forget their passwords", and this is the absolute truth. Many people even dread the password reset page because when I think about it, after spending a lot of time trying to guess their passwords unsuccessfully, they get frustrated or angry when they land on that particular page.
When we design user interfaces, we should also try our best to make the user experience as pleasant as possible. As much as we'd love to easily walk you through the flow of the password reset process, the UX of that flow should also be prioritized.
The general flow of the password reset process is shown below.
- User is frustrated after unsuccessfully trying to sign in. Click on the "Reset password" link and you will be redirected to the corresponding page. The interface you see is the typical web form that uses your email address or username.
- When they enter their email address or username in the input field, they click the button with the usual text "Email me a recovery link".
- You will receive confirmation that a secure link has been sent to your email. Sometimes this confirmation text can appear in a card-like component or modal that will disappear over time.
Use:For security reasons and a good user experience, it's good to use text very similar to this: "An email has been sent to your inbox. Click the link when you receive it." , as long as it does not indicate that the email address or username you entered exists in the database. This approach prevents attackers from knowing that this email exists, sabotaging any phishing attempts they might attempt using this UX in terms of UX, the text does not assure the user that the credentials entered are correct, which in turn allows it to verify the submitted credentials.
- The link sent to your email address will contain a JWT and your
User ID
or, in this case, your email address. - If they click on this link, they will be redirected to the path/page where they can enter the new password. The route the user will be on can have the following form
https://localhost:3000/reset-password/user-email/JWToken
- The final part of the flow is to verify that the generated JWT is associated with the user's account. Otherwise, we throw an error by displaying the error message received from the backend.
Now that you've seen how the password reset flow is created, let's see how it can be implemented with Next.js.
Understand dynamic routes
In this section, we'll explore the concept of dynamic paths by illustrating it with the folder structure of a Next.js project and see how we integrate it with the password reset feature. But first, let's set up a Next.js application.
npx create-next-app Application name
The above command does that for us. The Next.js team has already delivered a new update to the framework and has also introduced some new folders and features in the default project structure. However, we will not go into too much detail on this aspect as it is beyond the scope of this article. You can read more about updateson hereIf you want.
In the snippet below, you can see the basic structure of the files we will be interacting with in this article.
└── pages / ├── forgot password/ │ └── [token]/ │ └── [email].js ├── _app.js └── index.js
Above you can see that the files in the folder structure are quite small. That's because I want to be as brief as possible in this article.
And since the implementation of the "password reset" flow is our main concern, I think it's better if we have less confusion. Now let's understand this structure a little.
You will find that we have them.Forgot your password
folder inpages
Directory containing some files. However, the naming convention for these files differs significantly from the way other files are named. File names (token and email.js) are enclosed in square brackets.
Folders and files with that name are called dynamic paths, and as they are in thepages
directory, they are automatically converted to browser-accessible paths. They are dynamic because the values these routes assume are not static, ie they change over time.
This filename pattern is often seen in action when you decide to create a blog or when interacting with data that changes based on the type of user logged into an application. You can see how I used this Next.js function back thenstructure of my blog. You can get more information about this atNext.js docs.
I'mForgot your password
Folder, the path where the user interface can be accessed with the Forgot Password form. Check it out below.
http://localhost:3000/forgot-password/token/email
As it is a dynamic route, thePlate
yE-mail
URL parameters always change based on the user's attempt to reset their password. User A's token and email address are different from user B's.
More after the jump! Continue reading below↓
Reading URL Parameters with User Router Hook
To beuse routers
Hook in Next.js can be used to achieve many practical UI UI implementations, from the common idea of implementing an active navbar element with the.path
Key for more complex functions.
Let's see how we can also read URL parameters from dynamic routesuse routers
hook now, okay? To do this, you must first import the module into your page/component.
import { useRouter } from 'next/router' export default function PageComponent({ children }) { const router = useRouter() return ( <React.Fragment> {/* page content is included in */} <div> { children}</div> </React.Fragment> )}
The snippet above shows the basic usage of the hook. Since we are interested in URL query parameters, it is better to destructure.inquire
hook method instead of doing something like this:router.query
. We'll do something similar below.
import {useRouter} from 'next/router'const {query} = useRouter()
Now we can create variables that will store the desired URL parameters. The following snippet shows how you can do this.
const token = consulta.tokenconst email = consulta.email
Note that thequery.token
yConsult.Email
The values result from the file names. Folder structure call inForgot your password
folder where we have them[E-mail].js
y[Plate]
files. If you rename these files to[Benutzer-Email].js
y[user token]
respectively, the assignment pattern of this variable will look like the following.
const token = consult.userTokenconst email = consult.userEmail
You can always log these variables to the console to see the result.
Now that you understand how these parameters are retrieved from the URL, let's start building the structure of the form.
creating the forms
In this section, we explain the form creation process and how you can use Axios to retrieve data via any API endpoint. We won't focus on designing these shapes and explaining the structure. I assume you already know how to structure and style a simple React form. So let's start with the form design in the forgotten password way.
import React from 'react'import axios from 'axios'import { ErrModal, SuccessModal } from '../components/Modals'export const DefaultResetPassword = () => { const [email, setEmail] = React.useState('') const [loading, setLoading] = React.useState(false) const handleForgot = () => { } // we'll see this later ( <div> <form onSubmit={handleForgot} className="reset-password"> < h1 > Forgot your password</h1> <p>You're not alone, we've all been there.</p> <div> <label htmlFor="email">Email address</label> <input type= "email " name ="email" id="email" placeholder="your email address" value={email} onChange={(e) => setEmail(e.target.value)} required /> </ div > <button name="reset-pwd-button" className="reset-pwd"> {!loading? 'Get secure link': 'Sending...'} </button> </form> </div > ) }
The above snippet shows the basic structure of the interface you will see when you get to the forgotten password path. You'll notice the text in the paragraph tag below the bold "I forgot my password" text.
<p>You are not alone. We've all been here</p>
Using a text type like the one above improves the user experience for people who land on your app's forgotten password page. They assure you that it's no big deal that they forgot their passwords, so don't feel bad about it.
You don't necessarily have to use the exact wording above. You can only make sure that any text you use has a tone ofEmpathy.
Now let's move on to the important part of this form, where we need to declare a function that will send the email that the user enters in the input field to the backend.
import { authEndpoints } from '../endpoints'export const DefaultResetPassword = () => { const handleForgot = async (e) => { e.preventDefault() try { setLoading(true) const response = await axios({ method: „POST“, URL: authEndpoints.recover, Daten: { E-Mail, }, Header: { „Content-Type“: „application/json“, }, }) setResestSuccess(response.data.msg) setLoading(false) setResetError( '') } catch (error) { setLoading(false) const { data } = error.response setResetError(data.msg) setResestSuccess(null) } } return <div>{/* ...vorherige Formularkomponente */} </div>}
In the snippet above, you'll notice that we're importing the API endpoint we're sending a POST request to, which is why we're passing it as a variable to theURL
Enter the Axios method.
The POST request gets the user's email address as a payload, which in turn is validated on the backend, and a JWT is generated for that email address, which is used to authorize the reset process user password.
setResestSuccess(response.data.msg)setLoading(false)setResetError('')catch (error) { setLoading(false) const { data } = error.response setResetError(data.msg) setResestSuccess(null)}
If you look at the snippet above you will see that we are using some state return functions from already declared state variables.
An example is thesetLoading
Function whose value is defined asreal
on themTry
Block. Its value is set to false if the data was sent successfully. And if not, we have onecapture
Block that "captures" the error and displays the error message that we destructured from the endpoint.
You'll also notice that there are some status callback functions in the snippet above, likesetResSuccess
yestablecerResetError
.
Setters result from declaring state variables. See them below.
import Reagieren von 'react'import { ErrModal, SuccessModal } from '../components/Modals'export const DefaultResetPassword = () => { const [resetSuccess, setResestSuccess] = React.useState() const [resetError, setResetError] = Reagir .useState() return ( <div> {resetError ? <ErrModal message={resetError} /> : null} {resetSuccess ? <SuccessModal message={resetSuccess} /> : null} <form onSubmit={handleForgot} className="reset -Contraseña"> {/* Formularinhalt */} </form> </div> )}
Error or success messages received from the backend can be rendered in the UI to inform the user of the status of their actions.
You'll notice that we used custom modal components to render the message. These components receive the message as props and can be reused throughout the code base. See the component structure below.
export const SuccessModal = ({ mensaje }) => { return ( <div className="auth-success-msg"> <p>{mensaje}</p> </div> )}export const ErrModal = ({ mensaje } ) => { return ( <div className="auth-err-msg"> <p>{mensaje}</p> </div> )}
You can make these components unique so that you can differentiate failure mode from success mode. The general convention is to use red color for error messages and green color for success messages. How you design these components is entirely up to you.
In addition to all that has been said, we need a way to verify that the correct data type is passed in support of the modal component. This can be achieved with the "Prop-Type" module in React.
propTypes.ErrModal = { mensagem: propTypes.string.isRequired,} propTypes.SuccessModal = { mensagem: propTypes.string.isRequired,}
The type checking process in the code snippet above ensures that the data received by the component must be a string and is required. If the component doesn't get a property with a string value, React will throw an error.
Now that we have covered the important aspect of the first form and the basic components of which we are going to replicate in the password reset path. Let's start by looking at the form layout below.
importar axios desde axios; importar reacionar desde reacionar; import cabeza desde siguiente/cabeza; Importar { useRouter } de next/router; Importar { SuccessModal, ErrModal } desde "../components/Modals"; const ResetPassword = () => { const [nuevaContraseña, establecerNuevaContraseña] = React.useState(""); const [carregando, setLoading] = React.useState(false); const [resetPasswordSuccess, setResetPasswordSuccess] = React.useState(); const [resetPasswordError, setResetPasswordError] = React.useState(); const {consulta} = useRouter(); const token = consulta.token; const email = consulta.email; const resetPassword = () => { } // venga luego... return ( <React.Fragment> <Head> <title>Restablecer contraseña</title> </Head> <div> {email && token ? ( <div className="auth-wrapper"> {resetPasswordSuccess? ( <SuccessModal message={resetPasswordSuccess} /> ) : ( null )} {resetPasswordError ? ( <ErrModal message={resetPasswordError} /> ) : ( null )} <form onSubmit= {resetPassword} className="reset-password"> <h1>Restablecer contraseña</h1> <p>Por favor ingrese su nueva contraseña</p> <div> <label htmlFor="password">Contraseña*</label > < input name="contraseña" type="contraseña" id="contraseña" placeholder="ingresar nueva contraseña" value={nuevaContraseña} onChange={(e) => setNewPassword(e.target.value)} /> < / input > <button name="reset-pwd-button" className="reset-pwd" > {!cargando ? "Restablecer" : "Procesando..."} </button> </formulario> </div> ) : ( <p>A página que pretende acessar não está disponível</p> )} </div> </React.Fragment> );};
Since we looked at the fundamentals of the first form in the previous section, the above excerpt contains almost the same as the previous form.
You can see how we read the URL parameters and the password reset error and success variable declarations.
const [resetPasswordSuccess, setResetPasswordSuccess] = React.useState()const [resetPasswordError, setResetPasswordError] = React.useState()const { consulta } = useRouter()const token = consulta.tokenconst email = consulta.email
You will also notice how we conditionally process the password reset form, checking that theE-mail
yPlate
Variables are present in the URL; If these variables are false (i.e. not included in the URL), we render some text that the page you are looking for is not available.
{ email && token ? ( <div className="auth-wrapper"> <FormComponentt /> </div> ): ( <p>The page you are trying to access is not available</p> )}
Now let's take a look at the controller function that we would use to send the user's new password, along with the token and email for verification purposes, to the backend via the API endpoint.
import { authEndpoints } from '../endpoints'const resetPassword = async (e) => { e.preventDefault() try { setLoading(true) const respuesta = esperar axios({ método: 'POST', url: authEndpoints.resetPassword , Daten: {token, email, password: newPassword, }, headers: { 'Content-Type': 'application/json', }, }) setResetPasswordSuccess(response.data.msg) setLoading(false) setTimeout(() = > { router.push ('/') }, 4000) setResetPasswordError ('') } catch (Fehler) { setLoading (falso) setResetPasswordError (error.response.data.msg) setResetPasswordSuccess (nulo) }}
The snippet above is an async handler function. We use it to send a POST request with the user's new password, access token, and email address, which we get from the query parameters in the URL segment.
setTimeout(() => {enrutador.push('/')}, 4000)
If you look at the snippet above you can see how we use themset timeout
Method in JavaScript and Next.js'use routers
Hook to redirect the user to the homepage, in this case the login page, after four seconds (you can shorten this period if you like) so that the user can log in again.
This also makes for a good user experience metric, as it prevents the user from looking for a link or button that will take them back to the login page.
final thoughts
There's a lot of information on best practices and great design patterns for resetting passwords. This article is just a front-end implementation of a password reset flow that also considers the user experience issue. It's not enough to create a password reset feature without considering the user experience of the people who would use that feature.
Thanks for reading. Hope this article was helpful!
Further Reading in Smashing Magazine
- „How to implement authentication in Next.js using Auth0“, Facundo Giuliani
- „Dynamic recovery of data in an authenticated Next.js application", Caleb Olojo
- „Locate your Next.js application", Attila Fassina
- „The current state of authentication: We have a problem with the password“, Drew Thomas
(nl, er)
Learn more at