A login system is a specific need when developing a web app to authenticate themselves before accessing protected views or resources. Fortunately, there's a middleware called Passport that can be inserted into any Express-based web application to provide authentication procedures in only a few keystrokes for people building Node apps.
This tutorial will show you how to utilize Passport with a MongoDB backend to perform local authentication (that is, signing in with a username and password). Please refer to this instruction if you're trying to install authentication using Facebook or GitHub.
What is Passport.js, and how does it work?
Passport is a Node.js authentication middleware. The Passport may be easily integrated into any Express-based online application because of its flexibility and modularity. A broad range of mechanisms supports authentication via username and password, Facebook, Twitter, and other methods.
While Node.js can also be used for building chatbots, it’s mostly popular for traditional websites and back-end API services.
JWT vs. Session
Let's speak about login options briefly before we get started.
Many of today's tutorials will use JSON Web Tokens for token-based authentication (JWTs). This method is arguably the most straightforward and widely used currently. However, to keep the user authenticated, it delegates some authentication responsibility to the client and requires them to sign a token with every request.
SaaS teams also need this authentication request for email to avoid the email being forged. When, for example, a company is sending SaaS account setup email templates, email authentication blocks harmful uses of emails such as phishing.
We'll use session-based authentication in this lesson, which is at the core of the passport-local strategy.
Both methods have merits and disadvantages. This Stack Overflow discussion may be an excellent place to start if you want to learn more about the differences between the two.
We can begin once all of the needed software has been installed.
We'll start by creating our app's folder and then accessing it from the terminal:
We'll use the following command to make the node app:
For Node's package, you'll be asked to give certain information.
To accept the default configuration, keep pressing Return (or use the -y flag).
Setting up Express
We must now install Express. Enter the following command at the terminal:
We'll also need to install the body-parser middleware, which is used to parse the request body and authenticate the user using Passport. Finally, we'll also have to set up the express-session middleware. This way, when a request is made from the same client again, let’s say for some feedback, we will have their session information stored properly.
Let's get started. Execute the command below:
Create an index.js file in the root folder of your app and fill it with the following content:
First, we'll require Express, and then we'll make an Express app by calling Express (). Then we specify the directory from which our static files will be served.
The body-parser middleware require in the following line, which will assist us in parsing the body of our requests. The express-session middleware is also being added to aid in the saving of the session cookie.
We're configuring express-session with a secret to sign the session ID cookie (you should select a specific unique value here), as well as two other fields, resave and saveUninitialized, as you can see. The saveUninitialized field forces an "uninitialized" session to be saved to the session store, whereas the resave field moves the session to be saved back to the session store. Check out their documentation to understand more about them, but for now, know that we want to keep them false in our instance.
Then, if the environment port variable exists, we use process.env.PORT to set the port to it. Otherwise, we'll use port 3000, which is what we'll use locally. This allows you to move from development to production, even if a service provider like Heroku configures the port. Finally, we called app.listen() with the port variable we put up and a simple log to let us know that everything was good and that the app was listening on the port.
That concludes the Express configuration. So, by using Node.js you can make different sample bot types for your website. It's now time to set up Passport.
To begin, use the following command to install Passport:
Then, at the bottom of the index.js file, add the following lines:
We need a passport, and we need to initialize it and its session authentication middleware inside the Express app.
Creating a Data Store in MongoDB
Install Mongo; you should be able to use the following command to launch the Mongo shell:
Run the following command from the shell:
This establishes a MyDatabase datastore.
Leave the terminal in its current location; we'll return to it later.
Mongoose is used to connect Mongo to Node.
Now that we have a database with records in it, we need a means for our application to interface with it. Mongoose will be used to accomplish this.
Mongoose will make our lives easier while also improving the elegance of our code.
Let's get started by running the following command:
We'll also use passport-local-mongoose for local authentication, which will make the interaction between Mongoose and Passport much easier. To save the hashed password and the salt value, it will add a hash and salt field to our Schema. This is fantastic because passwords should never be kept in a database as plain text.
Let's get started installing the package:
Now we need to set up Mongoose. By now, you should be familiar with the procedure: at the bottom of your index.js file, add the following code:
The previously installed packages are required here. Then we use Mongoose.connect to our database and provide it the path to our database. Following that, we'll use a Schema to define our data structure. We're going to make a UserDetail schema with username and password fields in this scenario.
Finally, we add passportLocalMongoose to our Schema as a plugin. This will help with the magic we discussed previously. Then, using that Schema, we design a model. The first parameter is the name of the database collection. The second is a reference to our Schema, and the third is the Mongoose collection's name.
That concludes the Mongoose configuration. Now we can start putting our Passport approach into action.
Implementing Local Authentication
Finally, this is why we came here in the first place! Let's get started with setting up local authentication. We'll write the code that will set it up for us, as seen below:
There's a lot of magic happening here. First, we tell Passport to utilize the local strategy by using passport-local-createStrategy() Mongoose's method on our UserDetails model, which takes care of everything, so we don't have to. It's pretty useful.
The serializeUser and deserializeUser callbacks are then used. The first will be called after authentication, and it must serialize the user instance with the data we supply it and save it in the session through a cookie. The second will be called on each subsequent request to deserialize the model and provide it with the unique cookie identification as a "credential." More information is available in the Passport documents.
Now we'll build some routes to connect everything. We'll start by adding a final bundle. Run the following command in the terminal:
The connect-ensure-login package is middleware that guarantees a user's login. If an unauthenticated request is received, the request will be forwarded to a login page. This will be used to protect our routes.
Now, at the bottom of index.js, add the following:
Connect-ensure-login is required at the top. We'll return to this topic later.
The next step was to create a route to handle a POST request to the /login URL. We utilize the passport.authenticate method inside the handler, which tries to authenticate using the strategy it gets as its first parameter — in this case, local. If authentication fails, it will send us to /login, but it will include a query parameter — info — that will contain an error message. Otherwise, it will send us to the '/' route if authentication is successful.
Then we created the /login route, which sends the login page to the user. We're doing this with res.sendFile() and the file path, as well as our root directory, which is the one we're working on — hence the __dirname.
The /login route will be open to everybody, but our subsequent routes will not. We'll send their respective HTML pages to the / and /private routes, and you'll notice something different here. We're going to use connectEnsureLogin.ensureLoggedIn() before the callback. Our route guard is this guy. Its job is to validate the session and ensure that you are permitted to view that route.
Finally, we'll need a /user route that returns an object containing our user data. This is only to demonstrate how you can obtain information from the server. We'll ask the client for this route and show the result.
The client should be straightforward. We're going to make some HTML pages as well as a CSS file. Let's start with the index or main page. Create a folder called Html in your project root and add a file called index.html to it.
We have an empty h1 element where we'll put our welcome message and a link to /private underneath it. The script tag at the bottom is vital because we'll get the username to generate the welcome message.
This is broken down into four sections:
- Using the new XMLHttpRequest, we create the request object ().
- The function that will be invoked after we get our answer is set as the onreadystatechange attribute. We check for a successful response in the callback. If it is, we parse the response, extract the user object (the one we sent in the /user route, remember?), and locate the welcome-message element, setting its innerText to our user.username.
- We open() the GET request to the user URL and set the last parameter to true to make it asynchronous.
- Finally, the request is sent().
We'll now make the login page. Create a file called login.html in the HTML folder and fill it with the following content:
A simple login form with username and password fields and a Submit button may be found on this page. Below that is a label where any error messages will be shown. Keep in mind that these are included in the query string.
This time, the script tag at the bottom is much simpler. The window.location.search property, which holds the parameters string in our URL, creates a URLSearchParams object. Then we utilize the URLSearchParams.get() method with the parameter name we're looking for as a parameter name.
Either we have an information message, or we don't. If that's the case, we'll fetch the error-message element, set its innerText to whatever the message is, and set its style.display attribute to block. Given that it has a display: "none" value by default, it will be visible.
Now is the time to set up the private page. Create a new file called private.html in the HTML folder and fill it with the following content:
It's that easy. All we have is an introductory statement and a link that sends us back to the homepage.
That's all there is to the HTML; however, as you can see, the head tags are referencing a CSS file. Let's go ahead and add that file now. At the root of our project, create a css folder and add a styles.css file with the following content:
This will give our pages a lovely appearance. Let's have a look!
Run the following command in a terminal referring to the project root:
Now open your browser and go to http://localhost:3000/. You should be taken to the login page directly. If you go to http://localhost:3000/private, it will take you back to the login screen. Our route guard is performing its duties.
To stop our server, use Ctrl + C in the terminal window. Return to the index.js file and add the following lines to the bottom of the file:
To salt the password, we utilize the passport-local-mongoose register method. All we have to do now is send it in plain text.
Now we're going to run node index.js. The users are going to be generated. Those last lines should be commented on right now.
Remember how we left the MongoDB shell terminal open? Return to it and type:
This should display your three users, and as you can see, the salt and hash have taken up a significant amount of the terminal's space.
All we need for the app to work is that. We've completed our task!
Please return to the browser and try to log in using one of the credentials we submitted. You'll see a login message with the username you entered.
We only included the modules required for the app to function - nothing more, nothing less. You'll need to add more middleware and break your code into modules for a production project. Please take this as a challenge to create a clean, scalable environment and build it into something useful as part of your business objectives in the digital sphere!
The simplest and most straightforward step is to use Passport's req.logout() method to add the logout.
Then you might want to consider implementing a register flow. You'll wish for a registration form as well as a contact method. As a template, you should utilize the UserDetails.register() function we implemented before. Nodemailer is a good option for email confirmation.
You might also try applying these principles to a single-page application and using Vue.js and its router, perhaps. That's the end of your weekend!
Finally, we've concluded. We covered how to use Passport to build local authentication in a Node.js application in this tutorial. We also learned how to use Mongoose to connect to MongoDB during the process.
Perhaps it wasn't as simple for you as we attempted to portray it, but at the very least, you saw how it gets easier with these tools that work their magic in the background, allowing us to focus solely on the project at hand.
Although "magic" tools aren't always optimal, trustworthy and actively maintained tools can help us write less code — and code that isn't written isn't kept, and code that isn't maintained isn't broken.
Also, keep in mind that if a core team regularly maintains a tool, it's likely that they know more about it than any of us. When at all possible, delegate.