Create a Picture Competition Website in Express JS, MEVN
Login with JWT
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.