Customize Auth0 Universal login page using React

Auth0 provides several ways of changing login experience based on the level customization you need. You can use Auth0’s new universal login page which allows you to use Liquid template language to customize the login page. You can find out more about this approach in Auth0 documentation.

Auth0 has also another universal login page which is called Classic Universal Login page. Classic Universal Login page has a several templates for customization. You will get access to the html page as well as Auth0 SDKs (Auth0.js and Lock.js) and it allows you to apply advanced customizations.

In this post, I am going to use the classic universal login page and use a React SPA as login page. You may ask, why not use the Embedded Login approach? Well, embedded login comes with several risks which I am not going to explain here. However, you can find the reasons in this page.

Before start coding, go ahead and create a free Auth0 account if you don’t already have one.

Once you created an account, you need to integrate your application with Auth0. They have a great quick start page which covers all types of applications. You can easily integrate your application by following their guides.

So far I am assuming you already have an account and integrated your application with Auth0. You don’t need a fully functional app to follow this post. All we need is a link to redirect the page to the login screen where we are going to make some changes.

Let’s go ahead and take a look at Auth0 templates for customizing the login page. Login to your Auth0 account and then navigate to Branding -> Universal Login and then select the Login tab. There is a toggle switch in the login page that it enables and disables customize login page. Let’s switch it on so we can see what’s inside of the each template. Auth0 provides three base templates: Lock, Lock (Passwordless Mode) and Custom Login Form. We use the Custom Login Form template in this post, however, you can find more information about each template here.

Let’s select Custom Login Form template and see what we can do with this template. Custom Login Form uses Auth0.js with a simple login form which you can customize. This is great, but what if you want to use a React or an Angular application in this page? Writing a React app is so much simpler than the pure JavaScript. You can use many libraries to the validation and other cool things that you may need in your login page. Keep in mind that we talk about the login page in this post, however, you can use the same approach for other pages like MFA and Reset Password.

Now we have enough understanding of how the Auth0 custom login form template works. We can start creating a React app for our login page and then include it in this page. I use Create React App for this post, but feel free to use what you normally use to create your react apps.

You can create a react app simply by running the following command:

npx create-react-app my-login-page

It asks a couple of questions and then starts downloading node packages. Once you have the project ready, open it in your favourite code editor so we can make some changes.

Now we can install dependencies needed for our project. The main library that we need is auth0.js. I also use react-hook-form for my login form and yup for validating the form. You can choose any other libraries that you normally use. Also, if you are fan of typescript like me, don’t forget to install @types/auth0-js.

We have all we need, let’s start building our login form. We start by creating a provider call AuthProvider. This provider will have the functions that we need for sign in and sign up. The reason that we create a provider is so I can have all functions in the same place and reuse them for other functionalities like MFA and Password Reset. Below is the code of my provider:

import React, { useCallback, useMemo, createContext, useContext } from "react"; 
import auth0 from 'auth0-js';
 
type Props = { children: React.ReactNode } 
const DATABASE_CONNECTION = 'Username-Password-Authentication'; 

const AuthContext = createContext<{
    loginWithUsernamePassword: (username: string, password: string) => Promise, 
    loginWithGoogle: () => void, 
    signUp: (username: string, password: string, name:string) => Promise, 
}>(undefined as any); 

export function AuthProvider({children}: Props) { 
    const webAuth = useMemo(() => new auth0.WebAuth({ 
        domain: process.env.REACT_APP_AUTH0_DOMAIN, 
        clientID: process.env.REACT_APP_AUTH0_CLIENT_ID, 
        redirectUri: process.env.REACT_APP_AUTH0_REDIRECT_URI,
        responseType: 'code', 
    }), []); 

    const loginWithUsernamePassword = useCallback((username: string, password: string) => { 
        const urlParams = new URLSearchParams(window.location.search);
        const stateParam = urlParams.get('state') || ''; 
        return new Promise((resolve, reject) => { 
            webAuth.login({ 
                username, 
                password,
                realm: DATABASE_CONNECTION, 
                state: stateParam, 
            }, (error, result) => { 
                if (error) { 
                    reject(error); 
                    return; 
                } 
                resolve(result); 
            }) }) }, [webAuth]); 
    const loginWithGoogle = useCallback(() => { 
        webAuth.authorize({ connection: 'google-oauth2' }); 
    }, [webAuth]); 
    const signUp = useCallback((username: string, password: string, name:string) => { 
        return new Promise((resolve, reject) => { 
            webAuth.signup({ 
                connection: DATABASE_CONNECTION, 
                password: password, 
                email: username, 
                name }, 
           (error, result) => { 
               if (error) { 
                   reject(error); 
                   return; 
               } 
               resolve(result); }) }) 
           }, [webAuth]); 
    const value = useMemo(() => ({ 
        loginWithUsernamePassword, 
        loginWithGoogle, 
        signUp }), 
    [loginWithGoogle, loginWithUsernamePassword, signUp]); 

    return ( {children} ); } 

export const useAuth = () => useContext(AuthContext);

Node that REACT_APP_AUTH0_DOMAIN, REACT_APP_AUTH0_CLIENT_ID and REACT_APP_AUTH0_REDIRECT_URI are the values that you get when you create an application in Auth0.

Now let’s create a login page:

import React, { useState } from 'react';
import Layout from "../layout";
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import styles from "./login.module.scss"
import { useAuth } from "../auth";
import { useNavigation } from "../navigation";

interface ILoginFormInput {
    username: string,
    password: string,
}

const LoginSchema = yup.object().shape({
    username: yup.string().required('Please enter your email address.'),
    password: yup.string().required('Please enter your password.'),
});

export default function Login() {
    const {goToResetPassword, goToSignUp} = useNavigation();
    const [authError, setAuthError] = useState(null);
    const validationOpt = {resolver: yupResolver(LoginSchema)};
    const {
        register,
        handleSubmit,
        formState: {errors},
    } = useForm<ILoginFormInput>(validationOpt);
    const {
        loginWithUsernamePassword,
        loginWithGoogle,
        loginWithFacebook,
        existingUserError,
        setExistingError} = useAuth();

    async function onSubmit(data: ILoginFormInput) {
        try {
            await loginWithUsernamePassword(data.username, data.password)
        } catch (err) {
            console.log(err)
        }
    }

    return (<Layout>
        <main id={styles.container}>
            <div>
                <h1 id={styles.header}>
                    Login
                </h1>
            </div>
            <div id={styles.socialWrapper}>
                <button
                    type="button"
                    id={styles.google}
                    onClick={async (e) => {
                        e.preventDefault();
                        loginWithGoogle();
                    }}
                >
                    Login with Google
                </button>

                <button
                    type="button"
                    id={styles.facebook}
                    onClick={async (e) => {
                        e.preventDefault();
                        loginWithFacebook();
                    }}
                >
                    Login with Facebook
                </button>
                {existingUserError && (
                    <span className={styles.errorMessage}>
                This email address may be linked to another login method, try again using your email and password below.
              </span>
                )}
            </div>

            <form onSubmit={handleSubmit(onSubmit)} id={styles.loginForm}>
                <label htmlFor="username">
                    <input
                        type="email"
                        {...register('username', {
                            onChange: () => {
                                setExistingError(false);
                            },
                        })}
                        placeholder="Email"
                        name="username"
                        id="username"
                        autoComplete="username"
                        className={authError || errors.username ? styles.inputWithError : ''}
                    />
                </label>
                <label htmlFor="password">
                    <input
                        type="password"
                        {...register('password', {
                            onChange: () => {
                                setExistingError(false);
                            },
                        })}
                        placeholder="Password"
                        name="password"
                        id="password"
                        autoComplete="current-password"
                        className={authError || errors.password ? styles.inputWithError : ''}
                    />
                </label>

                {authError && <p id={styles.authError}>{authError}</p>}
                {errors.username && <p id={styles.authError}>{errors.username.message}</p>}
                {errors.password && <p id={styles.authError}>{errors.password.message}</p>}
                <button type="submit" className={authError ? styles.submitWithError : ''}>Log in</button>
            </form>
            <div id={styles.forgotPasswordWrapper}>
                <button type="button" className="link-button" onClick={(e) => {
                    e.preventDefault();
                    goToResetPassword();
                }}>
                    Forgot your password?
                </button>
            </div>
            <div id={styles.registrationWrapper}>
                <p>
                    Don't have an account? 
                    <button type="button" className="link-button" onClick={(e) => {
                        e.preventDefault();
                        goToSignUp();
                    }}>
                        Register an account
                    </button>
                </p>
            </div>
        </main>
    </Layout>)
}

I use react-hook-form for creating my login form and yup to validate it. As I mentioned in the beginning of this post, the goal is to deploy our own react app as the auth0 login page so, feel free to use any other libraries.

So far you should have an auth0 account, a new application as well as a react app for your login screen. Let’s see how we can use this app in auth0. Before that, you will need to build your react app and host it somewhere. Keep in mind that we only need JS and CSS files, auth0 gives us the html page. You can have your files where you normally host your static files for example, AWS S3 or firebase storage. Once you host your JS and CSS files, note their urls so you can add them to auth0 html page.

Go back to your auth0 tenant and open the login page template and replace the content of the body tag with:

<body>
  <div id="root"></div>
</body>

Basically, all we need is a container for the react app to render. The last step in to include your JS and CSS file to page. Your html template should look something like:

And that’s it! Go ahead save the template and try to access the login page and you will see your react app!

Leave a Reply