Introduction
Most applications require that an individual is verified and permitted to access resources. This ensures the security of the app.
Strapi provides you with authentication mechanisms which include email and password, AuthO, AWS Cognito, GitHub, Google, etc. through its Users and Permissions providers feature. Thus, authentication bottlenecks are solved using Strapi.
Next.js on the other hand is a popular frontend framework that allows users to build applications with the power of React components. It also supports authentication, session management and authorization through Middleware, Data Access Layer and Data Object Layer. Learn more here.
In this tutorial, you will learn how to implement Strapi email and password authentication with Next.js frontend.
Tutorial Series
We will divide this tutorial into two series:
- Part 1 - Email and Password registration, Email confirmation, and Login
- Part 2 - Session Management, Data Access, password reset, and changing password
GitHub Repository: Full Code for Strapi and Next.js Authentication Project
The complete code for this project can be found in this repo: strapi-email-and-password-authentication
Tutorial Goals
In this tutorial, here are the concepts we will cover:
- Strapi Email and Password registration (signup) flow.
- Strapi Email confirmation flow.
- Strapi Email and Password Login (sign-in) flow
- Strapi Forgot Password flow
- Strapi Reset password flow
- Changing Password in Strapi with Authorization
- Next.js server actions, authentication, session management, middleware, and data access layer.
Prerequisites
Before we begin, ensure you have the following:
- Node.js runtime: Only Active LTS or Maintenance LTS versions are supported (currently v18, v20, and v22).
- A basic knowledge of Next.js and React should suffice.
- A SendGrid API Key. Visit this page to learn how to get yours.
- Optionally, a HTTP client. Postman is recommended.
Introduction to Strapi Email and Password Authentication
Authentication refers to the process of verifying that an entity has the correct credentials or permission to access resources.
Strapi offers multiple authentication methods to secure your application:
- Users & Permissions Plugin: This is Strapi's built-in authentication system that provides email/password authentication, role-based access control, and third-party provider authentication.
- API Tokens: This allows authenticating REST and GraphQL API queries without user accounts, different token types such as read-only, full access, or custom.
- Third-Party Authentication Providers: Strapi supports numerous third-party authentication providers through the Users & Permissions plugin: Social providers: Facebook, Google, Twitter, GitHub, Discord, Twitch, Instagram, VK, LinkedIn, Reddit Enterprise providers: Auth0, AWS Cognito, etc.
- Single Sign-On (SSO): For enterprise users, Strapi offers SSO capabilities: Available with Enterprise plan. SSO allows administrators to authenticate through an identity provider (e.g. Microsoft Azure Active Directory).
The email and password authentication in Strapi uses the Users & Permissions plugin and follows RESTful API patterns.
If you are interested in knowing the concepts without digging into the technical aspect of this tutorial, please visit this documentation page.
Now, let's install Strapi!
Strapi Installation and Setup
Create a folder where both your Strapi and Next.js app will reside. Give it any name.
Strapi Installation
Let's begin by installing Strapi. To create a new Strapi project, run the command below in your terminal. Ensure you are in the directory where you want to keep both your Strapi backend and Next.js frontend.
npx create-strapi@latest
The command above will be followed with some questions from the terminal.
Depending on your preferred setup, you can select the answer you want. Here are the answers for this project.
? What is the name of your project? strapi-backend
...
? Please log in or sign up. Skip
? Do you want to use the default database (sqlite) ? Yes
? Start with an example structure & data? No
? Start with Typescript? Yes
? Install dependencies with npm? Yes
? Initialize a git repository? No
NOTE: The name of the project is
strapi-backend
.
Start Strapi Development Server
Once you have successfully installed Strapi, cd
into your strapi project and run the Strapi develop
command.
cd strapi-backend
npm run develop
This should start up your Strapi application in the URL: http://localhost:1337/admin
. Enter your new admin credentials to continue.
This is what your Strapi dashboard should look like after registering the new admin:
To learn more about creating a Strapi project, read the Strapi CLI documentation page.
In the next section, we will configure the Strapi email plugin to allow us to send emails.
Configuring Strapi Email Plugin with SendGrid (Step-by-Step)
In this project, you will need the Strapi Email plugin to send emails.
For example, a confirmation email will be sent to a user when they register or sign up. So, you need to configure your Strapi Email plugin.
When you head over to Settings > Email Plugin > Configuration as shown below:
You will notice that the default email provider is sendmail
, this is for local development. However, for production-ready applications, we will be using SendGrid as our email provider.
Install SendGrid provider
Providers can be installed using npm
or yarn
using the following format @strapi/provider-<plugin>-<provider> --save
.
Install the SendGrid email provider which is available in the Strapi marketplace in your Strapi backend project by running the command below:
npm i @strapi/provider-email-sendgrid
Upon success, configure your provider as shown below:
Configure SendGrid Provider
Navigate to your Strapi project and locate the configuration file for plugins, ./config/plugins.ts
. Add the following code:
1module.exports = ({ env }) => ({
2 email: {
3 config: {
4 provider: "sendgrid",
5 providerOptions: {
6 apiKey: env("SENDGRID_API_KEY"), // Required
7 },
8 settings: {
9 defaultFrom: env("SENDGRID_EMAIL"),
10 defaultReplyTo: env("SENDGRID_EMAIL"),
11 },
12 },
13 },
14});
Ensure you have your SendGrid API key and SendGrid email present in your Strapi project environment variable (strapi-backend/.env
):
1# Path: strapi-backend/.env
2# ... Other environment variables
3
4# SendGrid API Key
5SENDGRID_API_KEY=YOUR_API_KEY
6SENDGRID_EMAIL=YOUR_SENDGRID_EMAIL
Restart Strapi App
Restart your Strapi development server and visit the email configuration page. You will notice that the email provider is now SendGrid and the default email address is the one you provided in the configuration file.
🖐️ NOTE: The test feature on this page might not work in development mode, because it might not be using your SendGrid configuration.
Great! Let's dive into Next.js installation in the next section.
Next.js Installation
Install Next.js
Install Next.js using the command below. Ensure you are in the directory where you installed your Strapi project.
npx create-next-app@latest
During installation, ensure you follow the prompts. For this project, here are the answers to the prompts.
1✔ What is your project named? … nextjs-frontend
2✔ Would you like to use TypeScript? … Yes
3✔ Would you like to use ESLint? … No
4✔ Would you like to use Tailwind CSS? … Yes
5✔ Would you like your code inside a `src/` directory? … Yes
6✔ Would you like to use App Router? (recommended) … Yes
7✔ Would you like to use Turbopack for `next dev`? … Yes
8✔ Would you like to customize the import alias (`@/*` by default)? … No
NOTE: The project name is
nextjs-frontend
Start Next.js Development Server
Upon successful installation, cd
into your Next.js project and run the command below to start your Next.js development server:
cd nextjs-frontend
npm run dev
Visit http://localhost:3000
to view your application.
👋 NOTE: Want to learn more about how to integrate Strapi and Next.js? Visit the Strapi and Next.js integration guide.
In the next section, we will perform some essential setups.
Create Starter Files, Dependencies and Templates
Before we begin implementing Strapi email and password authentication, we need to first install some dependencies, create some starter files and code templates.
Install Dependencies
Install the following dependencies:
- axios: For HTTP requests.
- jose: JavaScript module for JSON Object Signing and Encryption, providing support for JSON Web Tokens (JWT) and so on.
- react-toastify: Allows us to add toast notifications.
npm i axios jose react-toastify
Create Pages
1nextjs-frontend/
2├── ...
3├── .env
4├── middleware.ts
5└── src/
6 └── app/
7 ├── actions/
8 │ └── auth.ts
9 ├── auth/
10 │ ├── signup/
11 │ │ └── page.tsx
12 │ ├── login/
13 │ │ └── page.tsx
14 │ ├── confirm-email/
15 │ │ └── page.tsx
16 │ ├── signup-success/
17 │ │ └── page.tsx
18 │ ├── forgot-password/
19 │ │ └── page.tsx
20 │ ├── change-password/
21 │ │ └── page.tsx
22 │ └── reset-password/
23 │ └── page.tsx
24 ├── components/
25 │ ├── NavBar.tsx
26 │ └── LogOutButton.tsx
27 ├── lib/
28 ├── layout.tsx
29 ├── page.tsx
30 └── globals.css
Create the following folders and files inside the nextjs-frontend/src/app
folder.
actions
: This is where server actions logic will reside. Create theauth.ts
file inside it.auth
: This will hold pages related to authentication. Inside this folder, create the following:
signup
>page.tsx
login
>page.tsx
confirm-email
>page.tsx
signup-success
>page.tsx
forgot-password
>page.tsx
change-password
>page.tsx
reset-password
>page.tsx
components
: Inside this folder, create the following:
NavBar.tsx
: Represents the navigation bar.LogOutButton.tsx
: Represents the button for logging out.
lib
: This will hold the logic for session management, API requests, type definitions, and Data Access Layer (DAL).
Create Environment Variable File
Inside the root of your frontend project folder nextjs-frontend
, create the .env
file. And add the environment variable for your Strapi API endpoint.
1# Path: nextjs-frontend/.env
2
3STRAPI_ENDPOINT="http://localhost:1337"
Create Next.js Middleware File
Middleware allows you to run code before a request is completed. We will use it to protect some pages.
Inside the nextjs-frontend/src
folder, create the middleware.ts
file.
Modify Pages and Files
Global CSS
Since we are using Tailwind CSS in this tutorial, head over to ./src/app/globals.css
and delete other codes. Leave only the Tailwind import.
1/* Path: nextjs-frontend/src/app/globals.css *./
2
3@import "tailwindcss";
Log-Out Button
Inside the nextjs-frontend/src/app/components/LogOutButton.tsx
, add the following code:
1// Path: nextjs-frontend/src/app/components/LogOutButton.tsx
2
3"use client";
4
5export default function LogOut() {
6 return (
7 <button className="cursor-pointer w-full sm:w-auto px-6 py-2 bg-red-500 text-white rounded-lg shadow-md hover:bg-red-600 transition">
8 Sign Out
9 </button>
10 );
11}
Navigation Bar Component
Inside the nextjs-frontend/src/app/components/NavBar.tsx
file, add the following code:
1// Path: nextjs-frontend/src/app/components/NavBar.tsx
2
3import Link from "next/link";
4
5export default async function NavBar() {
6 return (
7 <nav className="flex items-center justify-between px-6 py-4 bg-white shadow-md">
8 {/* Logo */}
9 <Link href="/" className="text-xl font-semibold cursor-pointer">
10 MyApp
11 </Link>
12 <div className="flex">
13 <Link
14 href="/auth/signin"
15 className="px-4 py-2 rounded-lg bg-blue-500 text-white font-medium shadow-md transition-transform transform hover:scale-105 hover:bg-blue-600 cursor-pointer"
16 >
17 Sign-in
18 </Link>
19 </div>
20 </nav>
21 );
22}
Home Page
Inside the nextjs-frontend/src/app/page.tsx
file, replace the code inside with the following code:
1// Path: nextjs-frontend/src/app/page.tsx
2
3import Link from "next/link";
4
5export default async function Home() {
6 return (
7 <div className="flex flex-col items-center justify-center min-h-screen bg-gray-100 text-gray-900 p-6">
8 {/* Title */}
9 <h1 className="text-3xl sm:text-4xl font-semibold mb-4">
10 Email & Password Authentication in Next.js & Strapi
11 </h1>
12
13 {/* Subtitle */}
14 <p className="text-center text-lg text-gray-600 max-w-xl">
15 Learn how to implement a secure authentication flow using Next.js and
16 Strapi. Start by signing in or creating an account.
17 </p>
18
19 {/* Buttons */}
20 <div className="mt-6 flex space-x-4">
21 <Link href="/auth/login">
22 <button className="px-6 py-3 text-white bg-blue-500 rounded-lg shadow-md hover:bg-blue-600 transition">
23 Sign In
24 </button>
25 </Link>
26 <Link href="/auth/signup">
27 <button className="cursor-pointer px-6 py-3 text-blue-500 border border-blue-500 rounded-lg shadow-md hover:bg-blue-50 transition">
28 Sign Up
29 </button>
30 </Link>
31 </div>
32 </div>
33 );
34}
Layout
Inside the layout file, we will import the following:
NavBar
,ToastContainer
from thereact-toastify
that you installed earlier.react-toastify/dist/ReactToastify.css
, which styles theToastContainer
above.
1// Path: nextjs-frontend/src/app/layout.tsx
2
3import type { Metadata } from "next";
4import { Geist, Geist_Mono } from "next/font/google";
5import "./globals.css";
6import NavBar from "@/app/components/NavBar";
7import { ToastContainer } from "react-toastify";
8import "react-toastify/dist/ReactToastify.css";
9
10const geistSans = Geist({
11 variable: "--font-geist-sans",
12 subsets: ["latin"],
13});
14
15const geistMono = Geist_Mono({
16 variable: "--font-geist-mono",
17 subsets: ["latin"],
18});
19
20export const metadata: Metadata = {
21 title: "Create Next App",
22 description: "Generated by create next app",
23};
24
25export default function RootLayout({
26 children,
27}: Readonly<{
28 children: React.ReactNode;
29}>) {
30 return (
31 <html lang="en">
32 <body
33 className={`${geistSans.variable} ${geistMono.variable} antialiased`}
34 >
35 <ToastContainer />
36 <NavBar />
37
38 {children}
39 </body>
40 </html>
41 );
42}
Now, visit http://localhost:3000
to view your changes.
Create Type Definitions for this Project
Inside the nextjs-frontend/src/app/lib
folder, create a file called definitions.ts
and add the following code.
1// Path : nextjs-frontend/src/app/lib/definitions.ts
2
3
4// This file contains type definitions for the application.
5
6// Credentials for authentication
7export type Credentials = {
8 username?: string;
9 email?: string;
10 identifier?: string;
11 currentPassword?: string;
12 password?: string;
13 confirmPassword?: string;
14 newPassword?: string;
15 code?: string;
16};
17
18// Form state for form handling and server actions
19export type FormState = {
20 errors: Credentials;
21 values: Credentials;
22 message?: string;
23 success?: boolean;
24};
25
26export type SessionPayload = {
27 user?: any;
28 expiresAt?: Date;
29 jwt?: string;
30};
The Credentials
type will serve for authentication purposes. The FormState
type will be for form handling and server actions variable types. And the SessionPayload
refers to the type of payload returned from Strapi after successful login.
Now, let's get started!
How to Implement User Signup with Email Confirmation in Strapi and Next.js
Strapi's Users & Permissions plugin provides built-in functionality for email/password registration.
Here is the request for registering a new user.
1const STRAPI_ENDPOINT = "http://localhost:1337/";
2
3await axios.post(
4 `${STRAPI_ENDPOINT}/api/auth/local/register`,
5 {
6 username: 'Strapi user',
7 email: 'user@strapi.io',
8 password: 'strapiPassword',
9 },
10);
If you want additional fields, you will need to add them to the list of allowed fields in your config file. See the registration configuration guide.
Ways of Registering a New User in Strapi
There are two ways to register a new user.
- Register a new user by confirming their email address.
- Register a new user without confirming their email address
You can decide to choose the latter.
However, you want to make sure the user's email is verified. Therefore, you will need to enable email confirmation in Strapi.
Here are reasons why you need email confirmation:
- User identity verification
- Email delivery success
- Boosted brand reputation, etc.
👋 NOTE: If you are not interested in implementing Email Confirmation, please continue from the "Strapi Email and Password Login" section.
With that being said, let's walk through the steps of registering a new user.
Step 1. Enable Email Confirmation in Strapi
To begin, head over to Settings > USERS AND PERMISSIONS PLUGIN > Advanced Settings and do the following:
- Toggle the
Enable email confirmation
totrue
. - Add the redirect URL where a user will be redirected after the user's email is confirmed. The redirect URL for this project should be
http://localhost:3000/auth/signup-success
. This is the page that will show the user that the signup process is successful. We will create the signup-success page soon.
🖐️ NOTE: After signup, Strapi sends you a confirmation link that will redirect you to the
http://localhost:3000/auth/signup-success
above. The link looks like this:http://localhost:1337/api/auth/email-confirmation?confirmation=56343d5821391b2b9c1569c5b69672f95bb0caf3
. When clicked, it will redirect the user to the signup success page which you specified above.
Since we are now sending emails using SendGrid as the default email provider, edit the email template to continue.
Step 2: Edit Email Confirmation Template
Navigate to Settings > USERS & PERMISSIONS PLUGIN > Email Templates > Email address confirmation.
Change the default shipper email from no-reply@strapi.io
to the email
address you used for your SendGrid configuration, otherwise, email delivery will fail.
Next, we will write a request function to create a new user.
Step 3: Create User Registration Request Function
Inside the nextjs-frontend/src/app/lib
folder, create a requests.ts
file and add the following code:
1// Path: nextjs-frontend/src/app/lib/requests.ts
2
3import { Credentials } from "./definitions";
4import axios from "axios";
5
6const STRAPI_ENDPOINT = process.env.STRAPI_ENDPOINT || "http://localhost:1337";
7
8export const signUpRequest = async (credentials: Credentials) => {
9 try {
10 const response = await axios.post(
11 `${STRAPI_ENDPOINT}/api/auth/local/register`,
12 {
13 username: credentials.username,
14 email: credentials.email,
15 password: credentials.password,
16 }
17 );
18
19 return response;
20 } catch (error: any) {
21 return error?.response?.data?.error?.message || "Error signing up";
22 }
23};
The signUpRequest()
request function takes in a credentials object containing username
, email
, and password
, and sends it to Strapi's /auth/local/register
endpoint to create a new user with new credentials.
Next, let's create a server action that will invoke this request.
Step 4: Create Server Action for User Registration
Inside the ./src/app/actions/auth.ts
file, create a server action that will do the following:
- Handle a signup form by validating user input (
username
,email
,password
,confirmPassword
) - Call the
signUpRequest()
request function you created above. - Returns a
FormState
object type with error messages and form values if there are validation or API errors. - Redirect the user to a confirmation page.
Here is the code below:
1// Path: nextjs-frontend/src/app/actions/auth.ts
2
3"use server";
4
5import { redirect } from "next/navigation";
6import { FormState, Credentials } from "../lib/definitions";
7import { signUpRequest } from "../lib/requests";
8
9export async function signupAction(
10 initialState: FormState,
11 formData: FormData
12): Promise<FormState> {
13 // Convert formData into an object to extract data
14 const username = formData.get("username");
15 const email = formData.get("email");
16 const password = formData.get("password");
17 const confirmPassword = formData.get("confirmPassword");
18
19 const errors: Credentials = {};
20
21 // Validate the form data
22 if (!username) errors.username = "Username is required";
23 if (!username) errors.email = "Email is required";
24 if (!password) errors.password = "Password is required";
25 if (!confirmPassword) errors.confirmPassword = "Confirm password is required";
26 if (password && confirmPassword && password !== confirmPassword) {
27 errors.confirmPassword = "Passwords do not match";
28 }
29
30 // Check if there are any errors
31 if (Object.keys(errors).length > 0) {
32 return {
33 errors,
34 values: { username, email, password, confirmPassword } as Credentials,
35 message: "Error submitting form",
36 success: false,
37 };
38 }
39
40 // Call backend API
41 const res: any = await signUpRequest({
42 username,
43 email,
44 password,
45 } as Credentials);
46
47 // Check for errors in the response
48 if (res.statusText !== "OK") {
49 return {
50 errors: {} as Credentials,
51 values: { username, email, password, confirmPassword } as Credentials,
52 message: res?.statusText || res,
53 success: false,
54 };
55 }
56
57 // redirect to confirm email
58 redirect("/auth/confirm-email");
59}
Next, set up the email confirmation page because we are redirecting the user to the email confirmation page.
Step 5. Set Up an Email Confirmation Page
Inside the nextjs-frontend/src/app/auth/confirm-email/page.tsx
, add the following code:
1// Path: nextjs-frontend/src/app/auth/confirm-email/page.tsx
2
3export default function ConfirmEmail() {
4 return (
5 <div className="flex min-h-screen items-center justify-center bg-gray-100 px-4">
6 <div className="w-full max-w-md p-6 space-y-6 bg-white rounded-lg shadow-md text-center">
7 <h2 className="text-2xl font-semibold">Confirm Your Email</h2>
8 <p className="text-gray-700 text-sm">
9 We’ve sent a confirmation link to your email address. Please check
10 your inbox and click the link to verify your account before logging
11 in.
12 </p>
13 </div>
14 </div>
15 );
16}
Step 6. Set Up the Signup Success Page.
Locate the nextjs-frontend/src/app/auth/signup-success/page.tsx
file and add the following code:
1// Path: nextjs-frontend/src/app/auth/signup-success/page.tsx
2
3import Link from "next/link";
4
5export default function EmailConfirmed() {
6 return (
7 <div className="flex min-h-screen items-center justify-center bg-gray-100 px-4">
8 <div className="w-full max-w-md p-6 space-y-6 bg-white rounded-lg shadow-md text-center">
9 <h2 className="text-2xl font-semibold text-green-600">
10 Email Confirmed ✅
11 </h2>
12
13 <p className="text-gray-700 text-sm">
14 Your email has been successfully verified. You can now log in to your
15 account.
16 </p>
17
18 <Link href="/auth/signin">
19 <button className="mt-6 w-full py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition">
20 Go to Login
21 </button>
22 </Link>
23 </div>
24 </div>
25 );
26}
The page above shows the user that the signup or registration is successful.
Next, create the signup form.
Step 7: Create a Signup Form
Create the signup form.
1// Path: ./src/app/auth/signup/page.tsx
2
3"use client";
4
5import { useActionState } from "react";
6import { signupAction } from "@/app/actions/auth";
7import { FormState } from "@/app/lib/definitions";
8
9export default function SignUp() {
10 // create a form state
11 const initialState: FormState = {
12 errors: {},
13 values: {},
14 message: "",
15 success: false,
16 };
17
18 // use the action state to handle the form submission
19 const [state, formAction, isPending] = useActionState(
20 signupAction,
21 initialState
22 );
23
24 return (
25 <div className="flex min-h-screen items-center justify-center bg-gray-100">
26 <div className="w-full max-w-md p-6 space-y-6 bg-white rounded-lg shadow-md">
27 <h2 className="text-2xl font-semibold text-center">Sign Up</h2>
28
29 <form action={formAction} className="space-y-4">
30 <p className="text-red-500 text-center text-sm">
31 {!state?.success && state?.message}
32 </p>
33
34 {/* Email */}
35 <div>
36 <label className="block text-gray-700">Username</label>
37 <input
38 type="text"
39 name="username"
40 defaultValue={state?.values?.username as string}
41 placeholder="Enter your username"
42 className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
43 />
44 {state?.errors.username && (
45 <p className="text-red-500 text-sm">{state?.errors.username}</p>
46 )}
47 </div>
48
49 <div>
50 <label className="block text-gray-700">Email</label>
51 <input
52 type="email"
53 name="email"
54 defaultValue={state?.values?.email as string}
55 placeholder="Enter your email"
56 className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
57 />
58
59 {state?.errors.email && (
60 <p className="text-red-500 text-sm">{state?.errors.email}</p>
61 )}
62 </div>
63
64 {/* Password */}
65 <div>
66 <label className="block text-gray-700">Password</label>
67 <input
68 type="password"
69 name="password"
70 defaultValue={state?.values?.password as string}
71 placeholder="Create a password"
72 className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
73 />
74
75 {state?.errors.password && (
76 <p className="text-red-500 text-sm">{state?.errors.password}</p>
77 )}
78 </div>
79
80 {/* Confirm Password */}
81 <div>
82 <label className="block text-gray-700">Confirm Password</label>
83 <input
84 type="password"
85 name="confirmPassword"
86 defaultValue={state?.values?.confirmPassword as string}
87 placeholder="Confirm your password"
88 className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
89 />
90
91 {state?.errors.confirmPassword && (
92 <p className="text-red-500 text-sm">
93 {state?.errors.confirmPassword}
94 </p>
95 )}
96 </div>
97
98 {/* Sign Up Button */}
99 <button
100 type="submit"
101 disabled={isPending}
102 className="w-full py-2 text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition"
103 >
104 {isPending ? "Submitting..." : "Sign Up"}
105 </button>
106 </form>
107
108 {/* Sign In Link */}
109 <div className="text-center text-gray-600">
110 Already have an account?{" "}
111 <a href="/auth/login" className="text-blue-500 hover:underline">
112 Sign In
113 </a>
114 </div>
115 </div>
116 </div>
117 );
118}
This is how the signup page should look like:
Now, register a new user!
As you can see above, the signup process works the way we expect.
When you check your Strapi backend, you should see the new user.
Congratulations! You have registered a new user.
What happens when the user of your app doesn't get an email confirmation and would like the confirmation link to be resent?
Or how do you know if an email is already confirmed?
Let's learn how to send an email confirmation after registration in Strapi in the next section.
How to Send Email Confirmation After User Registration in Strapi
By default, Strapi automatically sends a confirmation email upon registration provided that it is enabled in the Strapi backend as discussed previously.
However, in some cases, the user may miss or not receive the email and would love to have it resent to them.
Strapi provides us with the following flow for manually sending or resending a confirmation email:
1const STRAPI_ENDPOINT = "http://localhost:1337/";
2
3await axios.post(
4 `${STRAPI_ENDPOINT}/api/auth/send-email-confirmation`,
5 {
6 email: 'user@strapi.io'
7 },
8);
Send a POST
request with the correct email to the endpoint /api/auth/send-email-confirmation
to confirm an email address.
What if an Email is Already Confirmed?
Recall that you already confirmed the email of the user you created above. Let's see what happens when you try to confirm it once more.
When you try to confirm an already confirmed email, as shown above, you get the response "Already confirmed".
Resending Email Confirmation Logic
We want to be able to allow the user to request a resend of the confirmation email.
There are many ways to do this, but here is what you will do in this tutorial:
- In the signup or registration section above, once the user registers, Strapi sends a confirmation email, and then you redirect them to the page where they get the message to check their email for the confirmation link.
- You will modify the redirect of the
signUpAction()
server action function to include the email. This is so that they can request the confirmation link to be resent to them in case they didn't receive it:
1// redirect to confirm email
2redirect("/auth/confirm-email?email=" + email);
- The page where they get the message to check their email for the confirmation link will now programmatically allow a resend of the confirmation link by extracting the email from the URL using
useSearchParams
.
Let's implement this:
Step 1. Modify Signup Server Action to Include Email Address
Include the email address in the redirect so that we can be able to resend an email confirmation if the user requests such.
1// Path : nextjs-frontend/src/app/actions/auth.ts
2
3// ... imports
4
5export async function signupAction(
6 initialState: FormState,
7 formData: FormData
8): Promise<FormState> {
9
10 // ... other codes
11
12 // redirect to confirm email with user email
13 redirect("/auth/confirm-email?email=" + email);
14}
Step 2. Create Send Email Confirmation Request Function
Inside the nextjs-frontend/src/app/lib/requests.ts
file, create the confirmEmailRequest()
function to send email confirmation.
1// Path: nextjs-frontend/src/app/lib/requests.ts
2
3// ... other codes
4
5export const confirmEmailRequest = async (email: string) => {
6 try {
7 const response = await axios.post(
8 `${STRAPI_ENDPOINT}/api/auth/send-email-confirmation`,
9 {
10 email,
11 }
12 );
13
14 return response;
15 } catch (error: any) {
16 return (
17 error?.response?.data?.error?.message ||
18 "Error sending confirmation email"
19 );
20 }
21};
Next, create a server action that will call the confirmEmailRequest()
function above.
Step 3. Create Server Action for Email Confirmation
Create a server action that will handle the form submission and invoke the confirmRequest()
function above.
1// Path: nextjs-frontend/src/app/actions/auth.ts
2
3 // ... other imports
4
5import { signUpRequest, confirmEmailRequest } from "../lib/requests";
6
7// ... other actions (signupAction)
8
9export async function resendConfirmEmailAction(
10 initialState: FormState,
11 formData: FormData
12) {
13 // Extract email from formData
14 const email = formData.get("email");
15
16 // Validate the email
17 if (!email) {
18 return {
19 values: { email } as Credentials,
20 message: "Email not found",
21 success: false,
22 };
23 }
24
25 // invoke the resend email function
26 const res = await confirmEmailRequest(email as string);
27
28 // Check for errors in the response
29 if (res.statusText !== "OK") {
30 return {
31 errors: {} as Credentials,
32 values: { email } as Credentials,
33 message: res?.statusText || res,
34 success: false,
35 };
36 }
37
38 return {
39 values: { email } as Credentials,
40 message: "Confirmation email sent",
41 success: true,
42 };
43}
Step 4: Modify the Confirm Email Message Page
Now, modify the confirm email page, nextjs-frontend/src/app/auth/confirm-email/page.tsx
, that tells the user to check their email for the confirmation link. It should now be able to get the email from the URL using useSearchParams
.
1// Path: nextjs-frontend/src/app/auth/confirm-email/page.tsx
2
3"use client";
4
5import { resendConfirmEmailAction } from "@/app/actions/auth";
6import { FormState } from "@/app/lib/definitions";
7
8import { useSearchParams } from "next/navigation";
9import { useActionState, useEffect } from "react";
10
11import { toast } from "react-toastify";
12
13export default function PleaseConfirmEmail() {
14 // create initial state
15 const initialState: FormState = {
16 errors: {},
17 values: {},
18 message: "",
19 success: false,
20 };
21
22 // useActionState to manage the state of the action
23 const [state, formAction, isPending] = useActionState(
24 resendConfirmEmailAction,
25 initialState
26 );
27
28 // useSearchParams to get the email from the URL
29 const searchParams = useSearchParams();
30 const userEmail = searchParams.get("email") || "";
31
32 useEffect(() => {
33 if (state.success) {
34 toast.success(state.message, { position: "top-center" });
35 }
36 }, [state.success]);
37
38 return (
39 <div className="flex min-h-screen items-center justify-center bg-gray-100 px-4">
40 <div className="w-full max-w-md p-6 space-y-6 bg-white rounded-lg shadow-md text-center">
41 <h2 className="text-2xl font-semibold">Confirm Your Email</h2>
42 <p className="text-red-500">{!state.success && state.message}</p>
43 <p className="text-gray-700 text-sm">
44 We’ve sent a confirmation link to your email address. Please check
45 your inbox and click the link to verify your account before logging
46 in.
47 </p>
48
49 <p className="text-gray-500 text-sm">
50 Didn’t receive the email? Check your spam folder or try resending it
51 below.
52 </p>
53
54 {/* Resend Email Button */}
55 <form action={formAction}>
56 <input
57 type="email"
58 name="email"
59 defaultValue={userEmail}
60 className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
61 />
62 <button
63 disabled={isPending}
64 type="submit"
65 className="w-full my-4 py-2 px-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition"
66 >
67 Resend Confirmation Email
68 </button>
69 {/* <input type="hidden" name="email" value={userEmail as string} /> */}
70 </form>
71 </div>
72 </div>
73 );
74}
In the code above:
- The component uses
useActionState
to manage the form state and handle theresendConfirmEmailAction
server action. - The email is prefilled from the URL using
useSearchParams
, - And a toast notification appears if the resend is successful.
Enter the email address that you have confirmed and click the "Resend Confirmation Email" button.
You should get the error message "Already confirmed".
Now with the new logic we have created above, create a new user.
As shown above, when a user signs up, they get redirected to the page that tells them they have been sent a confirmation link. In this modified page, they can also request to get it resent to them.
The email address in the input from the image above comes prefilled from the URL which we extracted using the useSearchParams
.
You have implemented user registration or signup, and email confirmation in Strapi. Next, let's implement Strapi email and password login!
Strapi Email and Password Login
After registration, a user will have to log in.
To log in as a user, you make a POST
request to /api/auth/local
endpoint.
1const STRAPI_ENDPOINT = "http://localhost:1337/";
2
3const response = await axios.post(`${BASE_URL}/api/auth/local`, {
4 identifier: credentials.identifier,
5 password: credentials.password,
6});
🖐️ NOTE: The identifier here could be the user's email address or username.
Here is the response you get:
1{
2 "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsImlhdCI6MTc0NTM1MzMzMSwiZXhwIjoxNzQ3OTQ1MzMxfQ.zvx2Q2OexHIPkNA5aCqaOG3Axn0rlylLOpgiVPifi8c",
3 "user": {
4 "id": 15,
5 "documentId": "npbi8dusjdsdwu5a0zq6ticv",
6 "username": "Theodore",
7 "email": "strapiUser@gmail.com",
8 "provider": "local",
9 "confirmed": true,
10 "blocked": false,
11 "createdAt": "2025-04-22T18:18:01.170Z",
12 "updatedAt": "2025-04-22T19:04:51.091Z",
13 "publishedAt": "2025-04-22T18:18:01.172Z"
14 }
15}
The jwt
from the response above will be extracted and used to make authorized requests as we will see in the later part of this tutorial.
Let's implement email and password login in Strapi.
Step 1: Create Email and Password Request Function
1// Path: nextjs-frontend/src/app/lib/requests.ts
2
3// ... other codes
4
5export const signInRequest = async (credentials: Credentials) => {
6 try {
7 const response = await axios.post(`${STRAPI_ENDPOINT}/api/auth/local`, {
8 identifier: credentials.identifier,
9 password: credentials.password,
10 });
11
12 return response;
13 } catch (error: any) {
14 return error?.response?.data?.error?.message || "Error signing in";
15 }
16};
Step 2: Create Server Action to Handle Form Submission
Create a server action function that does the following:
- Handles form submission for user sign-in by validating
identifier
andpassword
from theformData
. - If either field is missing, it returns a
FormState
object with error messages and previous values. - If inputs are valid, it calls the
signInRequest
function above to authenticate the user. - On success, it redirects to the
/profile
page which we will set up soon. - On failure, it returns a generic error message in the state.
1// Path: nextjs-frontend/src/app/actions/auth.ts
2
3// ... Other codes
4
5export async function signinAction(
6 initialState: FormState,
7 formData: FormData
8): Promise<FormState> {
9 // Convert formData into an object to extract data
10 const identifier = formData.get("identifier");
11 const password = formData.get("password");
12
13 const errors: Credentials = {};
14
15 if (!identifier) errors.identifier = "Username or email is required";
16 if (!password) errors.password = "Password is required";
17
18 if (errors.password || errors.identifier) {
19 return {
20 errors,
21 values: { identifier, password } as Credentials,
22 message: "Error submitting form",
23 success: false,
24 };
25 }
26
27 // Call backend API
28 const res: any = await signInRequest({
29 identifier,
30 password,
31 } as Credentials);
32
33 if (res.statusText !== "OK") {
34 return {
35 errors: {} as Credentials,
36 values: { identifier, password } as Credentials,
37 message: res?.statusText || res,
38 success: false,
39 };
40 }
41
42 redirect("/profile");
43}
Next, set up the login page.
Set Up Login Page
Head over to the nextjs-frontend/src/app/auth/login/page.tsx
file and add the following code:
1// Path: nextjs-frontend/src/app/auth/login/page.tsx
2
3"use client";
4
5import { useActionState } from "react";
6
7import { signinAction } from "@/app/actions/auth";
8import { FormState } from "@/app/lib/definitions";
9import { toast } from "react-toastify";
10
11export default function SignIn() {
12 const initialState: FormState = {
13 errors: {},
14 values: {},
15 message: "",
16 success: false,
17 };
18
19 const [state, formAction, isPending] = useActionState(
20 signinAction,
21 initialState
22 );
23
24 if (state.success) {
25 toast.success(state.message, { position: "top-center" });
26 }
27
28 return (
29 <div className="flex min-h-screen items-center justify-center bg-gray-100">
30 <div className="w-full max-w-md p-6 space-y-6 bg-white rounded-lg shadow-md">
31 <h2 className="text-2xl font-semibold text-center">Sign In</h2>
32
33 <form action={formAction} className="space-y-4">
34 <p className="text-red-500 text-center text-sm">
35 {!state?.success && state?.message}
36 </p>
37
38 {/* Email */}
39 <div>
40 <label className="block text-gray-700">Email</label>
41 <input
42 type="email"
43 name="identifier"
44 defaultValue={state?.values?.identifier}
45 placeholder="Enter your email"
46 className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
47 />
48 {state?.errors.identifier && (
49 <p className="text-red-500 text-sm">{state?.errors.identifier}</p>
50 )}
51 </div>
52
53 {/* Password */}
54 <div>
55 <label className="block text-gray-700">Password</label>
56 <input
57 type="password"
58 name="password"
59 defaultValue={state?.values.password}
60 placeholder="Enter your password"
61 className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
62 />
63 {state?.errors.password && (
64 <p className="text-red-500 text-sm">{state?.errors.password}</p>
65 )}
66 <div className="text-right mt-1">
67 <a
68 href="/auth/forgot-password"
69 className="text-sm text-blue-500 hover:underline"
70 >
71 Forgot password?
72 </a>
73 </div>
74 </div>
75
76 {/* Sign In Button */}
77 <button
78 type="submit"
79 className="w-full py-2 text-white bg-blue-500 rounded-lg hover:bg-blue-600 transition"
80 disabled={isPending}
81 >
82 Sign In
83 </button>
84 </form>
85
86 {/* Sign Up Link */}
87 <p className="text-center text-gray-600 text-sm">
88 Don't have an account?{" "}
89 <a href="/auth/signup" className="text-blue-500 hover:underline">
90 Sign Up
91 </a>
92 </p>
93 </div>
94 </div>
95 );
96}
When you visit the login page at http://localhost:3000/auth/login
, you should see the page below:
After login, the user is redirected to the profile page. Set up the profile page below:
Create User Profile Page
Inside the nextjs-frontend/src/app
folder, create a folder called profile
. Inside the profile
folder, create a new file called page.tsx
and add the following code:
1// Path: nextjs-frontend/src/app/profile/page.tsx
2
3import Link from "next/link";
4import LogOutButton from "@/app/components/LogOutButton";
5
6export default async function Profile() {
7
8 return (
9 <div className="flex min-h-screen items-center justify-center bg-gray-100 px-4">
10 <div className="w-full max-w-md bg-white p-6 rounded-lg shadow-md text-center space-y-6">
11 {/* Username */}
12 <p className="text-xl font-semibold text-gray-800 capitalize">
13 Welcome, John Doe!
14 </p>
15
16 {/* Action Buttons */}
17 <div className="flex flex-col sm:flex-row justify-center gap-4">
18 <Link
19 href="/auth/change-password"
20 className="w-full sm:w-auto px-6 py-2 bg-blue-500 text-white rounded-lg shadow-md hover:bg-blue-600 transition"
21 >
22 Change Password
23 </Link>
24 <LogOutButton />
25 </div>
26 </div>
27 </div>
28 );
29}
When you visit the profile page at http://localhost:3000/profile
, this is what you should see:
Login Demo
Log in in the new user:
You are now able to log in as a user. Congratulations! See the complete code for this project below.
GitHub Repository: Full Code for Strapi and Next.js Authentication Project
The complete code for this project can be found in this repo: strapi-email-and-password-authentication
Coming Up Next: Session Management, Password Reset, and Authorization
Some authentication processes have been put in place already.
However, there is more.
- How do you log out a user in Strapi?
- How do you track the user's authentication state across requests in Next.js?
- How do you implement Strapi forgot password and request for password reset in Strapi?
- How do you decide what routes and data the user can access using Next.js?
- How do you log out an authenticated user in Strapi?
Conclusion
In Part 1 of this tutorial, you have learned about authentication using Strapi and Next.js. You implemented Strapi email and password registration and signup using the SendGrid email provider. You also learned how to perform Strapi email verification, and confirmation, and resend the email confirmation link.
And with Next.js you were able to handle form submissions using Server Actions.
In the next part of this tutorial, we will cover and implement the following concepts.
- Implementing forgot password request and reset password in Strapi.
- Changing password of authenticated users.
- Logging out a User in Next.js
- Session Management to help encrypt, decrypt, create a session, and delete the session of an authenticated user.
- Data Access Layer for authorization logic, in this case, the profile page and navigation bar.,
- Middleware in Next.js to protect some routes.
See you in the next part!
Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.