Share
Explore

Basic Node.js Authentication with Passport.js

megaphone

We will be using the approach of a simple password authentication method without using JSON Web Tokens (JWT), you can implement a traditional username and password-based authentication system.

Here's a general outline of how you can implement it:

User Registration: Create a registration form where users can provide their desired username and password. When a user submits the form, securely store their username and password in your database. It's important to hash and salt the passwords to enhance security.
User Login: Create a login form where users can enter their username and password. When a user submits the form, retrieve the corresponding user record from the database based on the provided username. Then, compare the hashed and salted password stored in the database with the password entered by the user. If they match, authenticate the user and allow access to protected routes.
Session Management: After successful authentication, create a session for the user. You can store the session information in a server-side session store or use encrypted cookies to store session tokens on the client-side. The session should include relevant user information to identify the authenticated user in subsequent requests.
Protecting Routes: Implement middleware that checks for the presence of an active session or valid authentication token before allowing access to protected routes. If a user tries to access a protected route without proper authentication, redirect them to the login page or return an appropriate error message.
Password Reset: Provide a mechanism for users to reset their passwords if they forget them. This can be done by sending a password reset link to the user's registered email address or through other secure verification methods.


It's worth noting that while this approach can provide a basic level of authentication, it may not offer the same level of security and flexibility as using JWT. JWTs have become popular due to their ability to securely transmit information between parties and their support for stateless authentication. However, if you prefer a simpler approach, the traditional username and password authentication method can still be effective.
megaphone

This code is a basic Node.js application using Express, Passport.js, Mongoose, and MongoDB to create a simple web app with user authentication. Let's break down how the password authentication is happening in this program:

Dependencies: The script includes necessary modules like Express, Passport, Mongoose, etc., for web server functionality, authentication, and database operations.
Database Connection: It connects to a MongoDB database using Mongoose.
User Schema and Model: A Mongoose schema for a User is defined with username and password fields. This model is used to interact with the users' collection in the MongoDB database.
Passport Local Strategy: The script uses Passport's LocalStrategy for authentication. In this strategy, when a login request is made, Passport will use the provided username and password to find a matching user in the database.
The authentication process is as follows:
It checks for a user in the database with the matching username and password.
If no user is found, or if an error occurs, the strategy calls the done function with appropriate parameters (either an error object or false to indicate failed authentication).
If a user is found with the matching credentials, it considers the authentication successful and calls done with the user object.
Serialization and Deserialization: Passport requires methods to serialize the user to the session and deserialize it. When a user logs in, their user ID is saved to the session. For each subsequent request, the user ID is used to find the user, which is then attached to the request object.
Express Middleware: The application uses sessions and initializes Passport and its session handling middleware.
Routes:
A route for the root path (/) which just sends a welcome message.
A login route (/login) that uses Passport's authenticate method with the 'local' strategy. On successful authentication, it redirects to /data, otherwise to the root.
A route to /data that serves protected data. This route uses a custom isAuthenticated middleware. This middleware checks if the user is authenticated and if the body of the request contains the password 'joe'. If the authentication fails, it redirects to the root.
Server Initialization: Finally, the app starts an Express server on port 3001.

Important Notes and Concerns:

Password Storage: This implementation stores and checks passwords in plain text, which is a significant security risk. In a secure application, passwords should be hashed and salted before being stored in the database.
Custom Middleware: The isAuthenticated middleware contains a hardcoded check against the password 'joe'. This is unusual and not a standard practice. Usually, the authentication check would only rely on req.isAuthenticated(), which checks if the user is logged in.
Security Improvements: For better security, consider using bcrypt for hashing passwords, implement proper error handling, and avoid hardcoding sensitive information like the session secret. Also, ensure the use of HTTPS in production to secure data in transit.
Write and run this Code:
app.js
// Include necessary dependencies
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bodyParser = require('body-parser');

// Create an Express app
const app = express();

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/simple_db', { useNewUrlParser: true, useUnifiedTopology: true });

// Define the user schema and model
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: String,
password: String
});
const User = mongoose.model('User', UserSchema);

// Configure Passport
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username, password: password }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user);
});
}
));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});

// Configure Express app
app.use(session({ secret: 'secret', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.urlencoded({ extended: true }));

// Define routes
app.get('/', (req, res) => {
res.send('Welcome to the simple app');
});

app.get('/peter', (req, res) => {
res.send('Welcome Peter');
});
app.post('/login', passport.authenticate('local', {
successRedirect: '/data',
failureRedirect: '/'
}));

app.get('/data', isAuthenticated, (req, res) => {
res.send('Protected data: Data 1, Data 2');
});

function isAuthenticated(req, res, next) {
if (req.isAuthenticated() && req.body.password === 'joe') {
return next();
}
res.redirect('/');
}

// Start the server
app.listen(3001, () => {
console.log('Server started on http://localhost:3001');
});
megaphone



megaphone

Let's go through this code line by line, focusing on the JavaScript authentication functions, particularly those related to Passport.js:

Including Dependencies

javascriptCopy code
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const bodyParser = require('body-parser');

express: A web framework for Node.js.
express-session: Middleware for session management in Express.
passport: Authentication middleware for Node.js.
passport-local: Strategy for username and password authentication in Passport.
mongoose: ODM (Object Data Modeling) library for MongoDB and Node.js.
body-parser: Middleware for parsing incoming request bodies in Express.

Creating an Express App

javascriptCopy code
const app = express();

Initializes a new Express application.

Connecting to MongoDB

javascriptCopy code
mongoose.connect('mongodb://localhost:27017/simple_db', { useNewUrlParser: true, useUnifiedTopology: true });

Connects to a MongoDB database named simple_db.

Defining User Schema and Model

javascriptCopy code
const Schema = mongoose.Schema;
const UserSchema = new Schema({ username: String, password: String });
const User = mongoose.model('User', UserSchema);

Defines a schema for the User model with username and password fields.
This model represents users in the MongoDB database.

Configuring Passport

javascriptCopy code
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username, password: password }, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }
return done(null, user);
});
}
));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});

Local Strategy: Defines the strategy for authentication using username and password. It checks if a user exists with the given username and password in the database.
serializeUser: Defines how to store the user information in the session. Here, the user's ID is stored.
deserializeUser: Defines how to retrieve the user information from the session. The user's ID is used to find the user in the database.

Configuring Express App

javascriptCopy code
app.use(session({ secret: 'secret', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.urlencoded({ extended: true }));

Configures session handling, initializes Passport, and sets up body parsing for form submissions.

Defining Routes

javascriptCopy code
app.get('/', (req, res) => {
res.send('Welcome to the simple app');
});

app.post('/login', passport.authenticate('local', {
successRedirect: '/data',
failureRedirect: '/'
}));

app.get('/data', isAuthenticated, (req, res) => {
res.send('Protected data: Data 1, Data 2');
});

Root Route ('/'): Just sends a welcome message.
Login Route ('/login'): Uses Passport to authenticate users. If successful, it redirects to /data; if not, it redirects back to the root.
Data Route ('/data'): A protected route that displays data only if the user is authenticated.

Authentication Check Function


function isAuthenticated(req, res, next) {
if (req.isAuthenticated() && req.body.password === 'joe') {
return next();
}
res.redirect('/');
}

A middleware function that checks if the user is authenticated and if the password is 'joe'. If both conditions are met, it proceeds to the next middleware; otherwise, it redirects to the root.

Starting the Server

javascriptCopy code
app.listen(3001, () => {
console.log('Server started on http://localhost:3001');
});

Starts the server on port 3001.

Important Points

The authentication strategy is quite basic and not secure since it checks passwords directly against the database. In a real-world scenario, passwords should be hashed.
The isAuthenticated function checks the password in the request body, which is unusual and not secure for a protected route. Normally, authentication checks should rely on session data.
The code doesn't handle user registration or password hashing, which are important for a complete authentication system.

Using Postman or similar tools is a great way to demonstrate the inner workings of Passport.js and the authentication flow in a Node.js application. Here are five workflows that you can demonstrate:

1. Successful User Login

Objective: Show how a user can log in successfully.
Workflow:
Create a POST request to http://localhost:3000/login.
In the body of the request, select x-www-form-urlencoded and enter username and password fields with correct credentials (assuming you have a user 'Joe' with password 'Joe' already created in the database).
Send the request and observe a successful login, which should redirect to the /protected route or return a success message.

2. Failed User Login (Wrong Password)

Objective: Demonstrate how Passport.js handles incorrect passwords.
Workflow:
Create a POST request to http://localhost:3000/login.
Enter the correct username but an incorrect password.
Send the request and observe the failure response, typically a redirection to the login page or an error message.

3. Failed User Login (Non-existent User)

Objective: Illustrate handling of non-existent user logins.
Workflow:
Make a POST request to http://localhost:3000/login.
Enter a username that does not exist in the database.
Send the request and note the failure response, which should be similar to the wrong password scenario.

4. Accessing Protected Route without Authentication

Objective: Show how Passport.js protects certain routes.
Workflow:
Create a GET request to http://localhost:3000/protected.
Send the request without logging in.
Observe that the request is denied or redirected since the user is not authenticated.

5. Accessing Protected Route after Authentication

Objective: Demonstrate access to protected content after authentication.
Workflow:
First, follow the workflow of a successful user login.
Then, create a GET request to http://localhost:3000/protected.
Send the request. Now that the session is authenticated, the server should provide access to the protected content.

Additional Notes for Demonstration:

Session Handling: In Postman, ensure that cookies are enabled to handle sessions. This is important because Passport.js relies on session cookies to maintain authentication state.
Database Preparation: Before demonstrating, make sure the MongoDB database has at least one user (e.g., username: Joe, password: Joe) created and stored.
Security Considerations: Explain that in real-world scenarios, additional security measures (like HTTPS, CSRF protection) are necessary.
These workflows cover the basic functionalities of Passport.js in an authentication context and provide a practical way for students to understand how user authentication works in web applications.


megaphone

Part 2:

“When I connect to http://localhost:3001/ I do not get the form to login with user id and password”

The above code for the Node.js server using Express and Passport.js sets up the backend logic for handling user authentication, but it does not include any code to serve a login form to the client.
To display a login form when you connect to http://localhost:3001/, you will need to modify the root route ('/') to serve an HTML page containing a login form.
image.png
image.png

Serving a Static HTML File for Login

Create a Login HTML File:
First, you need to create an HTML file named login.html (or similar) in a directory. This file will contain the login form. Here's a simple example:
<!DOCTYPE html> <html> <head> <title>Login</title> </head> <body> <form action="/login" method="post"> <div> <label for="username">Username:</label> <input type="text" id="username" name="username" required> </div> <div> <label for="password">Password:</label> <input type="password" id="password" name="password" required> </div> <div> <button type="submit">Login</button> </div> </form> </body> </html>
Serve the HTML File in Express:
Modify the root route in your Express application to serve this HTML file. You'll need to use the express.static middleware to serve static files, such as your HTML form:
javascript Copy code
const express = require('express'); const path = require('path');
// ... [rest of your code]
// Serve static files from a specified directory (e.g., 'public') app.use(express.static('public'));
// Modify the root route app.get('/', (req, res) => { res.sendFile(path.join(__dirname, '/public/login.html')); });
// ... [rest of your code] In this example, it's assumed that your login.html file is located in a directory named public at the root of your project.
Restart Your Server: After making these changes, restart your Node.js server.
When you now navigate to http://localhost:3001/ in your browser, you should see the login form. When you submit this form, it will send a POST request to /login, which is handled by your existing Passport.js logic.

megaphone

Part 3:


To modify the code so that it authenticates using Passport.js against a hardcoded password of "BILL" and without making calls to the database, we will need to change the Passport local strategy.
Here's how you can do it:
1. Remove the mongoose and User model-related code as you won't be using the database.
2. Modify the local strategy to check the username and password against hardcoded values.
Here is the modified code:
```javascript const express = require('express'); const session = require('express-session'); const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bodyParser = require('body-parser');
const path = require('path');
// Create an Express app const app = express(); app.use(express.static('public'));
// Configure Passport passport.use(new LocalStrategy( function(username, password, done) { if (username === "user" && password === "BILL") {
const user = { id: 1, username: "user" }; return done(null, user); } else { return done(null, false); } } ));
passport.serializeUser(function(user, done) { done(null, user.id); });
passport.deserializeUser(function(id, done) { if (id === 1) { const user = { id: 1, username: "user" }; done(null, user); } else { done(new Error('User not found')); } });
// Configure Express app to work with Passport on the session layer of TCP IP
app.use(session({ secret: 'secret', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.urlencoded({ extended: true }));
// Define routes app.post('/', (req, res) => { res.sendFile(path.join(__dirname, '/public/login.html')); });
app.post('/login', passport.authenticate('local', { successRedirect: '/data', failureRedirect: '/' }));
app.get('/data', isAuthenticated, (req, res) => { res.send('Protected data: Data 1, Data 2'); });
function isAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next(); } res.redirect('/'); }
// Start the server app.listen(3001, () => { console.log('Server started on http://localhost:3001'); }); ```
In this updated code: - The mongoose connection and User schema are removed as they are no longer needed. - The Passport local strategy is modified to check if the username is "user" and the password is "BILL". If so, it returns a hardcoded user object. - The `isAuthenticated` function is simplified to only check if the request is authenticated, without comparing the password since Passport handles the authentication. - The `deserializeUser` function is also simplified to return a hardcoded user if the ID matches the one we used in `serializeUser`.
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.