After 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 anunknown
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.