How to implement One-Time Passwords (OTPs) in Next.js
Learn how to implement a Next.js login page with email OTP and SMS OTP, in this tutorial.
Vincent
Created: December 17, 2024
Updated: December 17, 2024
Our mission is to make the Internet a safer place, and the new login standard passkeys provides a superior solution to achieve that. That's why we want to help you understand passkeys and its characteristics better.
1. Introduction#
Next.js is a powerful framework that allows developers to build fast and user-friendly web applications. One of the most critical aspects of any web application is user authentication.
In this guide, we'll walk you through the process of implementing a login page in Next.js, with One-Time Password (OTP) authentication via SMS or Email.
If you want to see the complete code and check out other authenciation methods, use our Next.js login page repository on GitHub. There you will find information regarding:
Passwordless authentication eliminates traditional passwords by using a unique, time-sensitive OTP sent to a user's email or phone. This enhances security by reducing the risk of breaches, improves user experience by removing the need to remember passwords, and cuts support costs by minimizing password-related issues.
OTP authentication is a widely used security mechanism for verifying user identity by generating a unique passcode valid for a one-time usage. In this section, we will guide you through implementing One-Time Passcode.
The following steps are required to implement OTP-based authentication:
Set up OTP-based authentication project
Implement API Route for Generating OTP via Email and SMS
Implement API Route for Verifying OTP
Create OTP Authentication component
Testing the OTP Authentication Flow
5.1 Testing OTP Authentication via Email
5.2 Testing OTP Authentication via SMS
Good to Know: Understanding the OTP Flow
Implementing OTP authentication in your application involves several key steps. To ensure you have a clear understanding of this process, let's break down each step:
Trigger: The OTP flow starts with a trigger event. This can be an action such as a user attempting to log in or register.
OTP Generation: Upon receiving the trigger, the server generates a unique OTP. This OTP is usually a random, time-sensitive code.
OTP Delivery: The generated OTP is delivered to the user through their chosen medium, such as email or SMS.
User Input: The user receives the OTP and enters it into the application.
OTP Verification: If the OTP verification is successful, the user is authenticated and allowed to proceed with their intended action.
3.1 Set Up OTP-Based Authentication Project#
In this section, we'll analyze the specific files and structure needed for OTP-based authentication via email and SMS. Here's an overview of the relevant directory structure and files:
Key Files and Their Roles:
src/app/otp/page.tsx: Contains the user interface for OTP authentication, including form for entering contact information and OTPs.
src/models/Otp.ts: Defines the MongoDB schema for storing OTPs, including fields for email, phone number, OTP, and creation date.
src/pages/api/auth/otp/generate.ts: API route to handle generating OTPs, including generating, hashing, storing OTPs, and sending them via email or SMS.
src/pages/api/auth/otp/verify.ts: API route to handle verifying OTPs, including retrieving, comparing, validating, and deleting OTPs from the database.
3.2 Installing the Relevant Dependencies#
To set up OTP authentication, you need to install the following dependencies:
Nodemailer: Nodemailer is a Node.js module used for sending emails. It simplifies the process of using SMTP and other transport methods. In our application, we will use Nodemailer to send OTP codes via email.
Twilio: Twilio is a cloud communications platform that enables sending SMS messages from your application. We will use Twilio to send OTP codes via SMS.
Please note that you will need to create a Twilio account and obtain the relevant credentials (Account SID, Auth Token, and Twilio phone number) and add them to the environment variables file.
You can install these dependencies using the following command:
npm install nodemailer twilio
3.3 Set Up MongoDB for OTP Storage#
To store OTPs, we need to set up a MongoDB schema. In this section, we will guide you through the creation of an OTP model in MongoDB.
File Location: Create a new file at models/Otp.ts in your project directory.
Purpose: The OTP model will define the structure of the OTP documents stored in the MongoDB collection. It includes fields for email or phone number, OTP, and the creation date, ensuring each OTP expires after a specified time.
Explanation
Interface Definition : IOtp is an interface extending Document, defining the structure of an OTP document.
Schema Definition : OtpSchema is a new schema defining the structure of OTP documents. It includes the following fields:
email : An optional string field to store the user's email.
phoneNumber : An optional string field to store the user's phone number.
otp : A required string field to store the OTP.
createdAt : A date field with a default value of the current date. This field has an index with an expiration time of 10 minutes, meaning the document will be automatically deleted after 10 minutes.
Model Creation : Otp is a model created from the OtpSchema. If the model already exists in mongoose.models, it uses the existing model; otherwise, it creates a new one.
Here is a high-level description of the otps collection relevant for the OTP-based authentication method:
Here's the complete code for the OTP model:
3.4 Implement Backend API Route for Generating OTPs via Email and SMS#
To generate OTPs and send them via email or SMS, we need to implement an API route. In this section, we will guide you through creating an API route that handles OTP generation and delivery.
File Location: Create a new file at pages/api/auth/otp/generate.ts in your project directory.
Purpose: The API route will generate an OTP, hash it for security, store it in MongoDB, and send it to the user via email or SMS based on the specified delivery method.
Explanation
Generate OTP Code: A function to generate a 6-digit OTP.
Hash OTP: Use bcrypt to hash the OTP for security.
Store OTP in MongoDB: Store the hashed OTP along with the email or phone number in MongoDB.
Send OTP via Email: Use Nodemailer to send the OTP via email.
For testing purposes, we use an Ethereal email account to preview the email link in the console (this is implemented in the Generate API route). After clicking the Generate OTP button, check the console for the Email Preview URL. Copy and paste this link into your browser to view your OTP code.
Copy and paste this link into your browser to view your OTP code.
Send OTP via SMS: After setting up your Twilio account and adding the necessary credentials (Account SID, Auth Token, and Twilio phone number), the OTP will be sent to the entered phone number using Twilio's API.
This API route handles generating, hashing, and storing OTPs, and sends them to users via email or SMS based on the specified delivery method. Combine all the steps into the Generate API route:
3.5 Implement Backend API Route for Verifying OTP#
To verify OTPs sent via email or SMS, we need to implement an API route. In this section, we will guide you through creating an API route that handles OTP verification.
File Location: Create a new file at pages/api/auth/otp/verify.ts in your project directory.
Purpose: The API route will verify the OTP by comparing it with the stored hashed OTP in MongoDB. If the OTP is valid, it will delete the OTP record from the database.
Explanation
Connect to MongoDB: Establish a connection to the MongoDB database.
Extract Data from Request: Extract email, phone number, and OTP from the request body.
Validate Input: Ensure either email or phone number, and OTP are provided.
Find OTP Record: Find the OTP record by email or phone number.
Check OTP Record Existence: Check if the OTP record exists.
Compare OTP: Compare the provided OTP with the hashed OTP in the database.
Return Error if OTP is Invalid: If the OTP does not match, return an error.
Respond with Success Message: If the OTP does match, respond with a success message
Delete OTP Record: Delete the OTP record after successful verification.
Combine all the steps into the Verify API route. Here’s the complete code:
3.6 Create Frontend OTP Authentication component#
To create the user interface for OTP authentication via email and SMS, we need to implement a component that handles OTP generation and verification. In this section, we will guide you through creating a user-friendly interface for this purpose.
File Location: Create a new file at src/app/otp/page.tsx in your project directory.
Purpose: The user interface allows users to select their preferred delivery method (email or SMS), enter their contact information (email or phone number), generate an OTP, and verify the OTP.
Explanation
State Variables: We use state variables to manage contact information, OTP, messages, delivery method, and the status of OTP generation and verification.
validateContactInfo: Is a function to validate the contact information based on the selected delivery method.
handleGenerateOTP: Is a function to handle OTP generation. It validates the email and phone number and sends a request to generate an OTP.
handleVerifyOtp: A function to handle OTP verification. It sends a request to verify the OTP.
Here's the complete code for the OTP auth component:
3.7 Testing the OTP Authentication Flow#
To ensure the OTP authentication works correctly via both email and SMS, we will conduct some testing. This section covers the steps and routes involved in testing OTP authentication, along with screenshots for better clarity.
3.7.1 Testing OTP Authentication via Email#
Route: http://localhost:3000/otpSteps:
Navigate to the OTP Authentication Page: Open your browser and navigate to the OTP authentication page http://localhost:3000/otp
Select Email as Delivery Method: Select "Email" from the dropdown menu for OTP delivery method.
Enter Email: Enter a valid email address in the input field.
Generate OTP: Click the Generate OTP button.
Check the console for the Ethereal email preview URL to view the OTP (since we're using Ethereal for testing).
Please copy and paste it into your browser to get the OTP code.
Enter OTP: Copy the OTP from the Ethereal email preview and enter it in the OTP input field.
Verify OTP: Click the Verify OTP button.
Check for a success message indicating that the OTP was verified successfully.
3.7.2 Testing OTP Authentication via SMS#
Route:http://localhost:3000/otpSteps:
Navigate to the OTP Authentication Page:
Open your browser and navigate to the OTP authentication page http://localhost:3000/otp
Select SMS as Delivery Method:
Select SMS from the dropdown menu for OTP delivery method.
Enter Phone Number:
Enter a valid phone number in the input field.
4. Generate OTP:
Click the Generate OTP button. Then, check your phone for the SMS containing the OTP fron the Twilio trial account.
After the OTP is generated, an OTP record is saved to the otps collection in the MongoDB database.
Enter OTP:
Enter the OTP received via SMS in the OTP input field.
Verify OTP:
Click the Verify OTP button. Then, check for a success message indicating that the OTP was verified successfully.
You've successfully implemented an OTP-based authentication system with Next.js and Tailwind CSS.
4. Recommendation for Next.js Authentication & Login Pages#
Event though we just looked at how to roll out OTP authencication, which is not the safest auth method, building a secure and user-friendly authentication system is crucial to protect user data and provide a great first impression of your app. Authentication is often the first interaction a user has with your product and the first thing they will complain about if something goes wrong. Besides the detailed steps laid out above, we also have some additional best practices for Next.js authentication and login pages:
Don’t Roll out Your Own Auth: Use libraries and packages for tested security solutions and to save implementation time. Libraries like NextAuth.js or Auth0 are excellent choices as they offer robust and secure authentication solutions out of the box.
Implement Multi-Factor Authentication (MFA): Combine authentication methods to achieve MFA, e.g., password plus TOTP to add an extra layer of security.
Validate Email Addresses: Use APIs to check if email addresses are valid and belong to real users. Avoid allowing one-time or disposable email addresses to prevent spam and ensure genuine user engagement.
Prevent SMS Pumping Attacks: Implement rate limits on SMS verifications and restrict access to certain areas to prevent SMS pumping attacks. Use services that can help identify and block fraudulent requests.
Limit Login Attempts: To prevent brute force attacks, implement rate limiting on authentication routes. Temporarily block IP addresses after a set number of failed login attempts to protect against automated attacks.
Never Store Passwords in Plain Text: Ensure that passwords are stored securely using hashing algorithms like bcrypt. Never store passwords in plain text.
Implement Proper Session Management: Authentication is only the first step. After successfully authenticating your users, ensure you have proper session management in place to prevent session hijacking.
Use HTTPS Everywhere: Always use HTTPS to encrypt data in transit. This protects user credentials and other sensitive data from being intercepted by attackers.
Monitor and Log Authentication Events: Keep track of authentication events and monitor them for suspicious activities. Implement logging and alerting to quickly detect and respond to potential security threats.
By following these practices, you'll keep your application robust against common threats and ensure a safe environment for your users. Secure authentication not only protects your users but also builds trust and credibility for your application.
7. Conclusion#
In this Next.js login page guide, we explored various authentication methods to secure your Next.js applications. Here’s a recap of what we covered:
OTP via email or SMS: We implemented OTP-based authentication by generating and verifying OTPs via email and SMS, setting up MongoDB to store OTPs, and creating front-end components to handle user interactions.
Choosing the right authentication method for your application depends on various factors, including security requirements, user convenience, and the nature of your application. Each method we covered has its strengths and can be used alone or in combination to provide a robust authentication system. Experiment with different methods, gather user feedback, and iterate on your implementation to achieve the best balance for your specific needs.
Share this article
Table of Contents
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.