Passwordless authentication is an authentication method that allows users to log in without remembering a password. Instead, it requires users to enter public identifiers like username, phone number, email e.t.c and receive a one-time code or link, which they can then use to log in.
In this post, we will discuss how to build a passwordless coupon generation app using Cloudinary, Auth0, and Next.js. At the end of this tutorial, we would have learnt how to build a secure application using Auth0, Cloudinary and Next.js.
Source code
You can find the source code of this project here.
Prerequisites
The next steps in this post require JavaScript and React.js experience. Experience with Next.js isn’t a requirement, but it’s nice to have.
We also need the following:
- A Cloudinary account for file hosting. Signup is completely free.
- An Auth0 account for authentication. Signup is completely free.
- A CodeSandbox account. Signup is completely free.
Getting Started
We need to create a Next.js starter project by navigating to the desired directory and running the command below in our terminal
npx create-next-app auth_pwdless && cd auth_pwdless
This command creates a Next.js project called auth_pwdless
and navigates into the project directory.
We proceed to install the required dependencies with:
npm install cloudinary-react auth0-js
cloudinary-react
is a library that handles rendering of media files.
auth0-js
is a library for managing client-side authentication.
Setting up Passwordless functionality on Auth0
Creating an Application
To get started, we need to log into our Auth0 dashboard. Click on Applications
In the application page, click on the Create Application button, input application name auth_pwdless
in our case, select Single Page Web Application as the application type and Create
Click on the Settings tab and copy the Domain and Client ID. They will come in handy when configuring Auth0.
Then scroll down to the Applications URIs section and fill in the details below for Allowed Callback URLs, Allowed Logout URLs, Allowed Web Origins, and Allowed Origins (CORS) respectively.
PS: For security reasons, Auth0 does not allow the use of URLs like
http://localhost:3000/
andhttp://127.0.0.1:3000/
. We will be using the custom URL CodeSandbox provides. Auth0 also supports custom domains.
- Allowed Callback URLs
-
https://ueofp.sse.codesandbox.io/coupon
. Thecoupon
at the end of this URL represents the page in our application.
-
- Allowed Logout URLs, Allowed Web Origins, and Allowed Origins (CORS)
-
https://ueofp.sse.codesandbox.io/
-
Then scroll down to the bottom of the page and click on the Save Changes button.
Setting up Passwordless
With the application configured, click on the Authentication and select Passwordless.
Then select Email authentication type.
Next, edit the From Field, Subject Field and Save. Auth0 also support custom settings like the email template format, OTP validity period, e.t.c.
Next, connect the auth_pwdless
app we created earlier and Save.
Finally, we can test the application by clicking on the Try button and then check our mail for the one-time-password(OTP).
Integrating Passwordless with Auth0
First, we can leverage Next.js CSS Module support to style our page by replacing the content in Home.module.css
in the styles
folder with the gist below:
With that done, we need to create an helper function to help us instantiate Auth0. Create a a config
folder in the project root directory and, in the folder, create an auth.js
file and add the code snippet below.
import { WebAuth } from "auth0-js";
export const webAuth = new WebAuth({
clientID: <YOUR_CLIEND_ID HERE>,
domain: <YOUR DOMAIN HERE>,
redirectUri: 'https://ueofp.sse.codesandbox.io/coupon',//replace with your codesandbox url
responseType: "token"
});
The webAuth
variable creates an instance of the WebAuth
class and configures it with clientID
, domain
, redirectUri
and responseType
.
Setting up Login
Next, we need to modify index.js
file in the pages
folder by importing the webAuth
variable to create handleAuth
and handleVerifyToken
functions to send OTP to specified email and verify the OTP.
import styles from "../styles/Home.module.css";
import { useState } from "react";
import { useRouter } from "next/dist/client/router";
import { webAuth } from "../config/auth";
export default function Home() {
const router = useRouter();
const [email, setEmail] = useState("");
const [otp, setOtp] = useState("");
const [error, setError] = useState({
emailError: false,
otpError: false
});
const [success, setSuccess] = useState(false);
const handleAuth = (e) => {
e.preventDefault();
webAuth.passwordlessStart(
{
connection: "email",
send: "code",
email: email
},
function (err, res) {
if (res.Id) {
setSuccess(true);
} else {
setError({ ...error, emailError: true });
}
}
);
};
const handleVerifyToken = (e) => {
e.preventDefault();
webAuth.passwordlessLogin(
{
connection: "email",
email: email,
verificationCode: otp
},
function (err, res) {
if (err) {
setError({ ...error, otpError: true });
} else {
router.push("/coupon");
}
}
);
};
return (
<div className={styles.container}>
{/* remaining JSX comes here */}
</div>
);
}
The snippet above does the following:
- Import required dependencies
- Create states to manage email, OTP, errors and success.
-
handleAuth
configures how the OTP will be sent(email), the type of OTP to be sent(code) and the email to which the OTP will be sent. -
handleVerifyToken
configures how the OTP will be sent(email), the user's email to which the OTP is delivered, and the code sent to the user. The function also routes the user to thecoupon
page if the OTP is valid. We will create this page shortly.
Then we need to include markups to conditionally display forms to request for OTP and verify OTP as shown below:
//imports here
export default function Home() {
//states here
const handleAuth = (e) => {
//send token code here
};
const handleVerifyToken = (e) => {
//verify token code here
};
return (
<div className={styles.container}>
{!success && (
<form onSubmit={handleAuth}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className={styles.input}
placeholder="enter email"
required
/>
<button className={styles.button}>Get Coupon</button>
{error.emailError && (
<p className={styles.error}>Error sending mail</p>
)}
</form>
)}
{success && (
<form onSubmit={handleVerifyToken}>
<input
type="number"
value={otp}
onChange={(e) => setOtp(e.target.value)}
className={styles.input}
placeholder="input token"
required
/>
<button className={styles.button}>Verify Token</button>
{error.otpError && (
<p className={styles.error}>Error validating OTP</p>
)}
</form>
)}
</div>
);
}
Complete index.js
Setting up the coupon page
With the authentication setup, we need to create a coupon.js
file inside the pages
folder, and in this file, add the code snippet below:
import React from "react";
import { CloudinaryContext, Transformation, Image } from "cloudinary-react";
import styles from "../styles/Home.module.css";
export default function Coupon() {
return (
<div className={styles.container}>
<p>
Congratulations {user && user.email}
<span role="img" aria-label="emoji">
🎉
</span>
</p>
<h1>
Coupon: {Math.floor(Math.random() * (67646676 - 6746 + 1)) + 74664}
</h1>
<CloudinaryContext cloudName="dtgbzmpca">
<Image publicId="v1633726853/tamanna-rumee-rOBRka7Q12U-unsplash.jpg">
<Transformation
crop="scale"
width="600"
height="400"
dpr="auto"
responsive_placeholder="blank"
/>
</Image>
</CloudinaryContext>
</div>
);
}
The Coupon
page uses the Math
object to generate random numbers as coupons and cloudinary-react
to optimize and transform a congratulatory sample image.
Finally, we need to protect our page using Auth0 as we want only users with OTP to access the coupon.
//remaining import comes here
import { useState, useEffect } from "react"; //add
import { webAuth } from "../config/auth"; //add
import { useRouter } from "next/dist/client/router"; //add
export default function Coupon() {
const [user, setUser] = useState(null);
const router = useRouter();
useEffect(() => {
if (window.location.hash) {
webAuth.parseHash({ hash: window.location.hash }, function (
err,
authResult
) {
if (err) {
return console.log(err);
}
webAuth.client.userInfo(authResult.accessToken, function (err, user) {
// get user info
setUser(user);
});
});
} else {
if (user === null) {
router.push("/");
}
}
}, []);
return (
<div className={styles.container}>
<p>
Congratulations {user && user.email} {/* modify this to show email*/}
<span role="img" aria-label="emoji">
🎉
</span>
</p>
{/* remaining JSX comes here */}
</div>
);
}
The updated snippet above does the following:
- Import required dependencies.
- Creates state to manage authenticated users.
- Use
webAuth
variable created earlier to parse URL hash fragment sent by Auth0 when redirecting to theCoupon
page and get theuser
’s information. Then, we use theuser
information to render theCoupon
page when valid or redirect to theHome
page when it is not. - Display user’s email.
Complete coupon.js
Finally, we can test our application by requesting OTP, checking specified email, verifying OTP and getting a coupon.
Conclusion
This post discussed how to build a secure passwordless coupon generation app using Cloudinary, Auth0, and Next.js.
You may find these resources useful:
Content created for the Hackmamba Jamstack Content Hackathon.