Create a passwordless authentication using Auth0, Cloudinary and Nextjs

Demola Malomo - Oct 17 '21 - - Dev Community

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Auth0 Dashboard

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

creating app

Click on the Settings tab and copy the Domain and Client ID. They will come in handy when configuring Auth0.

app configuration

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/ and http://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. The coupon 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/ URI

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.

auth method

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.

configure OTP Email

Next, connect the auth_pwdless app we created earlier and Save.

Connecting app

Finally, we can test the application by clicking on the Try button and then check our mail for the one-time-password(OTP).

Try Passwordless

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"
    }); 
Enter fullscreen mode Exit fullscreen mode

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>
      );
    }
Enter fullscreen mode Exit fullscreen mode

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 the coupon 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>
      );
    }
Enter fullscreen mode Exit fullscreen mode

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>
      );
    }
Enter fullscreen mode Exit fullscreen mode

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>
      );
    }
Enter fullscreen mode Exit fullscreen mode

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 the Coupon page and get the user’s information. Then, we use the user information to render the Coupon page when valid or redirect to the Home 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.

send OTP
Verify OTP

getting 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .