DRF register new user with email verification bannerAfter registration, a user will get an email with a verification link. Why is there such a procedure? We would like to have users with valid emails only to be able to contact them. (If we don’t care about emails, we could use the username for login)

When a user clicks the verification link, then the website for email verification will be opened. The token from the link will be used to send a POST request to the server. The verification status will be displayed in the website.

This article is part of SaaSitive paid course.

Email Verification View

Below is the email with a verification link:

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [example.com] Please Confirm Your E-mail Address
From: webmaster@localhost
To: piotr@piotr.pl
Date: Thu, 25 Aug 2022 13:53:15 -0000
Message-ID: <166143559536.50748.16793504971950475831@komp2>

Hello from example.com!

You're receiving this e-mail because user pepek has given your e-mail address to register an account on example.com.

To confirm this is correct, go to http://127.0.0.1:8000/verify-email/MQ:1oRDIJ:EliIs1rQ4mAr_jch8LuC-T98gIoKBhirwSMlygiINkU/

Thank you for using example.com!
example.com
-------------------------------------------------------------------------------

The verification link:

http://127.0.0.1:8000/verify-email/MQ:1oRDIJ:EliIs1rQ4mAr_jch8LuC-T98gIoKBhirwSMlygiINkU/
/-----domain--------/--view-url--/--------------token-----------------------------------/

We will change the domain later, let’s take the link and paste it into the web browser with domain localhost:3000:

http://localhost:3000/verify-email/MQ:1oRDIJ:EliIs1rQ4mAr_jch8LuC-T98gIoKBhirwSMlygiINkU/

We should see an empty page.

Let’s add a new view VerifyEmailView.tsx in the frontend/src/views directory:

export default function VerifyEmailView() {
  return (
    <div>
      <div className="container">
        <div className="row">
          <div className="col-md-10 offset-md-1">
            <h1>Verify Email</h1>
          </div>
        </div>
      </div>
    </div>
  );
}

This view will be displayed for the verification link. Let’s add a new Route in frontend/src/AppRoutes.txs file:

import VerifyEmailView from "./views/VerifyEmailView";

// the rest of the code ...

<Route path="/" element={<WebLayout />}>
  
  <Route path="/verify-email/:key" element={<VerifyEmailView />} />
  
  {/* the rest of the code ... */}
  
</Route>

// the rest of the code ...

Please note that the path is "/verify-email/:key". The :key is for obtaining a token from the URL address.

When we enter the verification link we will see only Verify Email header. We need to perform a POST request with the token in the view. We will need a new variable in the auth store to keep information about email verification status.

Update authSlice

Let’s update the frontend/src/slices/authSlice.ts:


// the rest of the code ...

const initialState = {
    usernameRegisterError: "",
    emailRegisterError: "",
    password1RegisterError: "",
    verifyEmailStatus: "unknown", // new variable in the store
};

const authSlice = createSlice({
    name: 'auth',
    initialState,
    reducers: {
        setUsernameRegisterError(state, action: PayloadAction<string>) {
            state.usernameRegisterError = action.payload;
        },
        setEmailRegisterError(state, action: PayloadAction<string>) {
            state.emailRegisterError = action.payload;
        },
        setPassword1RegisterError(state, action: PayloadAction<string>) {
            state.password1RegisterError = action.payload;
        },
        //
        // new function to set variable value
        //
        setVerifyEmailStatus(state, action: PayloadAction<string>) {
            state.verifyEmailStatus = action.payload;
        },
    },
});

export default authSlice.reducer;

export const {
    setUsernameRegisterError,
    setEmailRegisterError,
    setPassword1RegisterError,
    //
    setVerifyEmailStatus, // export new function
    //
} = authSlice.actions;

// ...

// new getter function
export const getVerifyEmailStatus = (state: RootState) => state.auth.verifyEmailStatus;


// the rest of the code ...

Updates in the authSlice:

  • we added a new variable in the initialState with an unknown value,
  • we added a new function setVerifyEmailStatus to set status,
  • we added a new function getVerifyEmailStatus to get email verification status from the store.

The store is ready. The next step is to write a function to do POST request with a verification token.

POST request with a verification token

Please add a new function at the end of the authSlice.ts file:

export const verifyEmail =
    (key: string) =>
        async (dispatch: TypedDispatch) => {
            try {
                // set status to started 
                dispatch(setVerifyEmailStatus("started"));

                // send POST request
                const url = "/api/auth/register/verify-email/";
                await axios.post(url, { key });

                // set verify email status to ok
                dispatch(setVerifyEmailStatus("ok"));
            } catch (error) {
                // set status to error
                dispatch(setVerifyEmailStatus("error"));
            }
        };

The verifyEmail function gets the key as the argument. The key value is a verification token. It is sent to the backend to the /api/auth/register/verify-email/ endpoint. The verifyEmailStatus is set to "ok" for successful response and "error" otherwise.

Update VerifyEmailView.tsx

We will use the verifyEmailStatus variable to display proper messages for our users.

The frontend/src/views/VerifyEmailView.tsx:

import { useEffect } from "react";
import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { getVerifyEmailStatus, verifyEmail } from "../slices/authSlice";
import { useAppDispatch } from "../store";

export default function VerifyEmailView() {
  let { key } = useParams(); // get key (token) from URL
  let navigate = useNavigate(); // create navigate variable 
  const dispatch = useAppDispatch();
  const emailVerifyStatus = useSelector(getVerifyEmailStatus); //get emailVerifyStatus

  // after view load send POST request
  useEffect(() => {
    if (key) {
      dispatch(verifyEmail(key));
    }
  }, [dispatch, key]);

  return (
    <div>
      <div className="container">
        <div className="row">
          <div className="col-md-10 offset-md-1">
            <h1>Verify Email</h1>

            {(emailVerifyStatus === "unknown" ||
              emailVerifyStatus === "error") && (
              <p>
                We can't verify your email. Please try to register again or
                contact us by email contact@monitor-uptime.com
              </p>
            )}
            {emailVerifyStatus === "started" && (
              <p>Email verification started, please wait a while ...</p>
            )}
            {emailVerifyStatus === "ok" && (
              <p>
                Successfull email verification🎉 Please login to start
                monitoring!
                <br />
                <button
                  className="btn btn-lg btn-primary my-2"
                  onClick={() => navigate("/login")}
                >
                  Login
                </button>
              </p>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

We will use useParams from react-router-dom to get the token value from the URL address.

let { key } = useParams(); // get key (token) from URL

We will get the emailVerifyStatus from the store:

const emailVerifyStatus = useSelector(getVerifyEmailStatus); //get emailVerifyStatus

The POST request to the server will be sent after the VerifyEmailView component is loaded. We will use the useEffect React’s hook for this:

useEffect(() => {
    if (key) {
      dispatch(verifyEmail(key));
    }
  }, [dispatch, key]);

The useEffect hook is called only once after loading the component. It dispatches the verifyEmail(key).

We use the emailVerifyStatus value to display proper information for the user.

{(emailVerifyStatus === "unknown" ||
    emailVerifyStatus === "error") && (
    <p>
    We can't verify your email. Please try to register again or
    contact us by email contact@monitor-uptime.com
    </p>
)}
{emailVerifyStatus === "started" && (
    <p>Email verification started, please wait a while ...</p>
)}
{emailVerifyStatus === "ok" && (
    <p>
    Successfull email verification🎉 Please login to start
    monitoring!
    <br />
    <button
        className="btn btn-lg btn-primary my-2"
        onClick={() => navigate("/login")}
    >
        Login
    </button>
    </p>
)}

The login button is displayed after successful verification. The user will be redirected to log in view after clicking it.

Summary

We created a view for email verification. It displays information about verification status for the user and makes a POST request with a token value from the URL. We will add toasts and BlockUI in the next post (in the course).

This article is part of SaaSitive paid course.


Let's stay in touch!

Would you like to be notified about new posts? Please fill this form.