Create a Picture Competition Website in Express JS, MEVN

Login with JWT

Login and Registration
Login and Registration

Now we need to allow users to log in to their accounts. So create a GET route in your server.js:

// route for login requests
app.route("/login")

    // get request accessed from browser
    .get(function (request, result) {
        // render login.ejs file inside "views" folder
        result.render("login");
    });

Then create a file named login.ejs inside your views folder and write the following code in it:

<%- include ("includes/header") %>

<div class="container margin-container" id="loginApp">
	<div class="row">
		<div class="offset-md-3 col-md-6">

			<h2 class="text-center">Login</h2>

			<form method="POST" v-bind:action="baseUrl + '/login'" v-on:submit="doLogin">

				<div class="form-group">
					<label>Email</label>
					<input type="email" name="email" class="form-control" required />
				</div>

				<div class="form-group">
					<label>Password</label>
					<input type="password" name="password" class="form-control" required />
				</div>

				<input type="submit" name="submit" value="Login" class="btn btn-primary" />
			</form>
		</div>
	</div>
</div>

<script>

	var loginApp = new Vue({
		el: "#loginApp",
		data: {
			baseUrl: mainURL
		},
		methods: {
			doLogin: async function () {
				event.preventDefault();
				const form = event.target;

				// disable the submit button and show "Loading..." text
		        form.submit.setAttribute("disabled", "disabled");
		        form.submit.value = "Loading...";

		        var formData = new FormData(form);
				myApp.callAjax(form.getAttribute("action"), formData, function (response) {
                    // convert the JSON string into Javascript object
                    var response = JSON.parse(response);

                    // enable the submit button
                    form.submit.removeAttribute("disabled");
                    form.submit.value = "Login";
 
                    // if the user is created, then redirect to login
                    if (response.status == "success") {
                    	// get access token from server
                        var accessToken = response.accessToken;
 
                        // save in local storage
                        localStorage.setItem(accessTokenKey, accessToken);
 
                        // redirect to home page
                        window.location.href = "/";
                    } else {
                    	swal("Error", response.message, "error");
                    }
				});
			}
		}
	});
</script>

<%- include ("includes/footer") %>

This will display a simple login form. When hit submit it will call an AJAX to authenticate the user. On the server-side, we need to install the jsonwebtoken module to create a unique token for the user each time he logs in. So run the following command in your terminal:

npm install jsonwebtoken

In your server.js write the following lines at the top before starting the server:

var jwt = require("jsonwebtoken");
var accessTokenSecret = "accessTokenSecret1234567890";

Then create a POST route for login by chaining it with GET route we created earlier.

// route for login requests
app.route("/login")

    // get request accessed from browser
    .get(function (request, result) {
        // render login.ejs file inside "views" folder
        result.render("login");
    })
    // post request called from AJAX
    .post(async function (request, result) {
 
        // get values from login form
        var email = request.fields.email;
        var password = request.fields.password;
 
        // check if email exists
        var user = await db.collection("users").findOne({
            "email": email
        });
 
        if (user == null) {
            result.json({
                "status": "error",
                "message": "Email does not exists."
            });
            return false;
        }
 
        // check if password is correct
        bcrypt.compare(password, user.password, async function (error, isVerify) {
            if (isVerify) {

            	if (user.isVerified) {
            		// generate JWT of user
	                var accessToken = jwt.sign({
	                    "email": email
	                }, accessTokenSecret);
	 
	                // update JWT of user in database
	                await db.collection("users").findOneAndUpdate({
	                    "email": email
	                }, {
	                    $set: {
	                        "accessToken": accessToken
	                    }
	                });
	 
	                result.json({
	                    "status": "success",
	                    "message": "Login successfully.",
	                    "accessToken": accessToken
	                });
            	} else {
            		result.json({
						"status": "error",
						"message": "Kindly verify your email."
					});
            	}
 
                return false;
            }
 
            result.json({
                "status": "error",
                "message": "Password is not correct."
            });
        });
    });

Refresh the page and try to log in. You will see an error if the entered email address does not exist, or the password is incorrect or if the account is not verified. Otherwise, you will see a success message. You can also see in Mongo DB compass that your authentication token has been generated.

Show Authenticated User

Now the user is logged-in, we must show his information in the navigation bar and also show him a button to log himself out. Update your Vue JS navApp in footer.ejs to the following:

var navApp = new Vue({
	el: "#navApp",
	data: {
		baseUrl: mainURL,
		login: false,
		user: null
	},
	methods: {
        doLogout: function () {
            //
        },

		getUser: function () {
			var self = this;
			// check if user is logged in
		    if (localStorage.getItem(accessTokenKey)) {

		        // call AJAX to get user data
		        var ajax = new XMLHttpRequest();
		        ajax.open("POST", mainURL + "/getUser", true);

		        ajax.onreadystatechange = function () {
		            if (this.readyState == 4) {
		                if (this.status == 200) {
		                    // console.log(this.responseText);

		                    var response = JSON.parse(this.responseText);
		                    if (response.status == "success") {
		                        // user is logged in
		                        self.user = response.user;

                                // [notifications are set here]

                                // [logged in functions will be called here]
		                    } else {
		                        // user is logged out
		                        localStorage.removeItem(accessTokenKey);
		                    }

		                    self.login = localStorage.getItem(accessTokenKey);
		                }

		                if (this.status == 500) {
		                    console.log(this.responseText);
		                }
		            }
		        };

		        var formData = new FormData();
		        formData.append("accessToken", localStorage.getItem(accessTokenKey));
		        ajax.send(formData);
		    } else {
		    	self.login = localStorage.getItem(accessTokenKey);
		    }
		}
	},
	mounted: function () {
		this.getUser();
	}
});

It will call an AJAX to the server to see if the user is logged in or not. If the variable login is true then we will show a logout button otherwise we will display login and signup buttons. Now create an API in your server.js that will return the authenticated user:

// return user data using access token
app.post("/getUser", async function (request, result) {
    var accessToken = request.fields.accessToken;
 
    var user = await db.collection("users").findOne({
        "accessToken": accessToken
    });
 
    if (user == null) {
        result.json({
            "status": "error",
            "message": "User has been logged out. Please login again."
        });
        return false;
    }
 
    result.json({
        "status": "success",
        "message": "Data has been fetched.",
        "user": user
    });
});

We have already applied the “login” variable condition on buttons in “header.ejs”. We can show the user name as well with a dropdown for more options:

[notifications layout goes here]

<li v-if="login" class="nav-item dropdown">
	<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-text="user.name"></a>
	
	<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
		<a v-bind:href="baseUrl + '/profile'" class="dropdown-item">Profile</a>
		<a v-bind:href="baseUrl + '/myCompetitions'" class="dropdown-item">My Competitions</a>
		<div class="dropdown-divider"></div>
    	<a v-bind:href="baseUrl + '/login'" class="dropdown-item" v-on:click="doLogout">Logout</a>
	</div>
</li>

If you refresh the page now, you will see the user’s name on the top right with an arrow down. When clicking will show a dropdown with more options. We will create those pages later in this tutorial.