Verifying the User’s Email

In the last tutorial we created an instance function named generateAuthToken() that creates authorization tokens and a middleware function named auth() that verifies that an authorization token belongs to a user in the database.  We also made changes to POST /user to generate an auth token and passed it to sendVerificationEmail().

In this tutorial we’ll modify the email sent by sendVerificationEmail() and send an HTML email containing a button, that when pressed will open a browser to a verification page on the client.  Included in the URL to the verification page will be the token that was passed to sendVerificationEmail().  The verification page will parse the token from the URL, store it in the browser’s local storage, then pass the token in a request to GET /user/verification.  That endpoint will attempt to verify the token belongs to a user and if valid, flip the email_verified flag in the user’s document and return a 200 OK status code.  If the client receives a 200 status code, the client will redirect the user to the app’s Main page; otherwise the user will be redirected back to the home page.

There’s lots to do, so let’s get started.

Create a GET /user/verification Endpoint

Open VSC and load your API server project.  Load the auth middleware module at the beginning of src/routers/user.js.

const auth = require('../middleware/auth')

Then add the following code to establish the endpoint.

router.get('/user/verification', auth, async (req, res) => {
    const user = req.user
    const token = req.token

    console.log(user)
    console.log(token)

    user.email_verified = true
    user.save()
    
    res.send()
})

Notice that we are passing auth as the second argument to router.get().  This instructs express to run the auth middleware before running the endpoint handler.  Recall that auth() adds two properties to the request (req.user and req.token), if a user document is found in the database containing the token and the user id signed in the token.  Though we don’t need to, we print the user and token to the console for debugging purposes.  We can remove these lines later.

Test The Endpoint

Start up your API server on localhost.

Open Postman and create a new request in the users folder.  Set the name to Verify email, the method to GET, and the URL to {{url}}/user/verification.

In the menu found under the URL field, select Auth.  In the type drop-down, choose Bearer Token.  Cut a token string from a User document in the MongoDB database and paste it in the Token field in Postman.

Set the environment to Localhost and press Send to send the request.  You should receive a response with a 200 status code.

If you did not receive a 200 status code, resolve the error before going further.

If you did receive a 200 status code, check the document with Compass and verify that the email_verified property was set to true.

Try to induce a 401 status code by taking a character out of the Bearer Token field in Postman and resend the request.

Push Your GitHub and Test Azure

Push your code to GitHub and then when the deployment has finished, test your API server running on Azure with Postman using the process described above.

Create Verify and Main Pages for the Client

Open your web app project in VSC.

Create a Verification Web Page

Create a new file named verify.html in the root of your project and add the following code.  Add a CSS file (css/verify.css) to give the page some style.

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="css/verify.css">
</head>

<body>
    <div id="main">
        <h1>Thank you!</h1>
        <p>We're verifying your email now.</p>
    </div>


    <script type="module" src="js/verify.js"> </script>
</body>

</html>

Create a JS File to Verify the Token

Create a file named js/verify.js and add the following code to the file.

Be sure to replace <<YOUR-API-URL>> on line 23 with your API server’s URL.

async function checkVerification() {
    console.log('running checkVerification')

    const h1 = document.querySelector("h1")
    const p = document.querySelector("p")

    // get token from query string
    const params = (new URL(document.location)).searchParams;
    const token = params.get("token")

    if (!token) {
        h1.innerHTML = "Something went wrong."
        p.innerHTML = "Please click the link in the email that was sent to you."

        console.log("No token found")

        setTimeout(() => {
            location.href = "index.html"
        }, 4000)
        return
    }

    const url = "https://<<YOUR-API-URL>>/user/verification"

    const options = {
        method: "GET",
        headers: {
            Authorization: `Bearer ${token}`
        }
    }

    let response = await fetch(url, options)

    if (response.status == 200) {
        h1.innerHTML = 'Thank you! Your email has been verified.'
        p.innerHTML = 'You will be redirected to the app momentarily.'

        console.log("Verification successful")
        localStorage.setItem("token", token);

        setTimeout(() => {
            location.href = "main.html"
        }, 4000)
    }
    else {
        h1.innerHTML = "Something went wrong."
        p.innerHTML = "Please try verifying your account once more."

        console.log("Error verifying email address")

        setTimeout(() => {
            location.href = "index.html"
        }, 4000)
    }
}

checkVerification()
Explanation of the Code

Lines 8 and 9 retrieve the token from the current page’s URL and stores it in const token.

On lines 11 through 21, an error message is displayed to the user and the user is redirect to the app’s home page after 4 seconds, if the token was not found in the URL.

Lines 27-29 define an Authorization header containing the Bearer (aka authorization) token which will be passed to the API server.

Line 39 stores the authorization token in the browser’s local storage and lines 34-44 redirect the user to main.html if API server returned a successful status code.

Lines 45-54 display an error message in the web page and redirect the user back to the home page (index.html) if the API server could not validate the token.

Create a Main Hub Page for Your Web App

The Main hub page will be the page the user is redirected to when they successfully verify their email and login.

Create a new file named main.html in the root of your project and add the following code.  Add a CSS file (css/main.css) to give the page some style.

<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="css/main.css">
</head>

<body>
    <h1>Main Page</h1>

    <script src="js/main.js"></script>
</body>

</html>

Create a js/ directory if you don’t yet have one, and then add a file named js/main.js.  Include the following code in the file.  This code will help us determine if we are successful in storing the user’s auth token in the browser’s local storage.

const token = localStorage.getItem("token");

console.log("token: " + token)

Test the Client

Use VSC’s Live Server extension to open verify.html.  You should see the error message displayed and after 4 seconds the browser should redirect you to your home page (index.html).

Cut a token from a User’s document in Compass.  Then in the browser’s location box, enter “127.0.0.1:5500/verify.html?token=” and paste the token.  You should see the message ‘Thank you! Your email has been verified.’ and be redirected to your app’s main hub page.

Note: My Live Server extension runs on port 5500.  Yours may be different.

Push to GitHub and Test Azure

Push your client code to GitHub and when the deployment is finished, test your Azure hosted site using the process described above.

Upload an Image to SendGrid (Optional)

If you want to include an image in the verification email, you have to upload the image to Sendgrid and use the URL provided by Sendgrid.

To do so, navigate a browser to Sendgrid.com and log in.

Select the Digital Library link in the left-side menu.  Then select Your Images in the top-menu.

On the Your Images page, drag and drop an image into the sendgrid page to upload an image.

Once uploaded, click on the image.  You should see a button labelled Copy Image URL.  Keep this page open, as you’ll need this URL below.

Update sendVerificationEmail()

Inside your API server project you should have a file named src/emails/accounts.js.  In addition to sending simple text emails, Sendgrid provides a way to send HTML formatted emails.  In order to send an HTML formatted email we need to set the html property on the object that we pass to sgMail.send().  The html property should hold a complete HTML document including doctype, head, and body sections and should include CSS styling.

The code that I wrote to send an email containing HTMl is lengthy, so rather than paste it here, I’ll direct you to the file on GitHub.com.  Feel free to use as little or as much code as you’d like.

My code has a helper function named sendOneButtonEmail() which is called by sendVerificationEmail().  The helper function formats the HTML and calls the sendgrid API code.  It allows me to easily create multiple emails by simply adding new functions that call sendOneButtonEmail() and pass to it the missing information.

The most important part of sendOneButtonEmail(), is the anchor tag <a> used in the button.

<a href=${buttonURL} target="_blank">${buttonLabel}</a>

The buttonURL is passed into sendOneButtonEmail(). In sendVerificationEmail it is defined as a template literal.

buttonURL: `https://lively-glacier-0949d3f0f.4.azurestaticapps.net/verify.html?token=${token}`
As you can see, it contains the URL to the verify.html page in my web app along with the query string containing the token parameter.  Be sure to change the URL to match your web app’s URL.
If you uploaded an image to Sendgrid, the image URL is passed to sendOneButtonEmail()as imgURL.

Test the Email

When you’re finished editing accounts.js, save the file and start up your localhost server.

Use Postman to send a Create User request with the localhost environment.  Verify that an email was sent and that the button (or link) in the email causes the browser to open your web app’s verify.html page.

When satisfied that it is working properly, push your code to GitHub and test your API server by sending Create User requests with Postman when using your Azure environment.

© 2024, Eric McGregor. All rights reserved. Unauthorized use, distribution and/or duplication of this material without express and written permission from the author is strictly prohibited.