Passport.js : Authentication - Local Strategy

Passport.js : Authentication - Local Strategy

1.Folder Structure

project-root/
│
├── config/                  # Configuration files
│   ├── db-connection.js     # Database connection setup
│   ├── passport-config.js   # Passport.js configuration
│   ├── session-config.js    # Session configuration
│   └── ...                  # Other configuration files
│
├── models/                  # Mongoose models
│   ├── user.schema.js       # User schema and model
│   └── ...                  # Other schemas and models
│
├── routes/                  # Express route handlers
│   ├── auth.route.js              # Authentication routes
│   ├── index.js             # Index routes
│   └── users.js             # User-related routes
│
├── views/                   # EJS templates
│   ├── index.ejs            # Main page template
│   ├── login.ejs            # Login page template
│   ├── register.ejs         # Registration page template
│   └── ...                  # Other view templates
│
├── .env                     # Environment variables
├── app.js                   # Main application file
└── package.json             # Project metadata and dependencies

2. Install all these packages

Session and Flash Messaging:

npm install express-session connect-flash

Authentication:

npm install passport passport-local passport-local-mongoose

Database:

npm install mongoose

You can run these commands separately, or combine them into a single command for convenience:

npm install express-session connect-flash passport passport-local mongoose passport-local-mongoose

3. Add these codes to respective file

3.1 app.js

// 📂 app.js
require('dotenv').config({ path: "./.env" });

const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');

// 🔷🔷🔷🔷🔷🔷🔷 Authentication Setup 🔷🔷🔷🔷🔷🔷🔷
const session = require('express-session');
const flash = require('connect-flash'); // Import connect-flash
const passport = require('./config/passport-config'); // Passport.js configuration
const authRouter = require('./routes/auth.route'); // Routes related to authentication

// Establish MongoDB connection and load user schema
require('./config/db-connection').connectDB();
require('./models/user.schema');
// 🔷🔷🔷🔷🔷🔷🔷 Authentication Setup End 🔷🔷🔷🔷🔷🔷🔷

const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');

const app = express();


// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// 🔶🔶🔶🔶🔶🔶🔶 Session and Passport Setup 🔶🔶🔶🔶🔶🔶🔶
// Initialize session middleware for persistent login sessions
app.use(session({
  secret: process.env.SESSION_SECRET || 'your-secret-key', // Replace with your secret
  resave: false,
  saveUninitialized: false,
  cookie: { secure: false } // Set to true if using HTTPS
}));

// Initialize connect-flash
app.use(flash());

// Initialize Passport.js and restore authentication state from session
app.use(passport.initialize());
app.use(passport.session());

// Use authentication routes
app.use('/', authRouter);
// 🔶🔶🔶🔶🔶🔶🔶 Session and Passport Setup End 🔶🔶🔶🔶🔶🔶🔶

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use((req, res, next) => {
  next(createError(404));
});

// error handler
app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

3.2 config/passport-config.js

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/user.schema');

const loginFieldName = "email" || "username";

// Configure Passport.js to use the LocalStrategy
passport.use(new LocalStrategy(
    { usernameField: loginFieldName }, // Field used for login
    async (loginFieldName, password, done) => {
        try {
            console.log("Testing -> 1");

            // Find user by loginFieldName
            const user = await User.findOne({ 
                $or: [
                    { username: loginFieldName },
                    { email: loginFieldName }
                ]
             });
            console.log("user: ", user);

            if (!user) {
                console.log("Testing -> 2");
                return done(null, false, { message: 'No user with that username' });
            }

            // Passport-local-mongoose handles password comparison
            user.authenticate(password, (err, user, msg) => {
                if (err) return done(err);
                if (!user) return done(null, false, { message: msg });
                return done(null, user);
            });
        } catch (err) {
            return done(err);
        }
    }
));

// Serialize user into session
passport.serializeUser((user, done) => {    
    done(null, user.id);
});

// Deserialize user from session
passport.deserializeUser(async (id, done) => {
    const user = await User.findById(id);
    done(null, user);
});


module.exports = passport;

3.3 config/session-config.js

const session = require('express-session');

const sessionConfig = session({
    secret: 'your-secret-key', // Secret key for session encryption
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false } // Set `secure: true` if using HTTPS

});

module.exports = sessionConfig;

3.4 routes/auth.route.js

const express = require('express');
const passport = require('../config/passport-config');
const User = require('../models/user.schema');
const router = express.Router();
const flash = require('connect-flash'); // Import connect-flash
const { isLoggedIn } = require('../middleware/isLoggedIn');

// Route to handle login with Passport.js authentication
router.post('/login', passport.authenticate('local', {
    successRedirect: '/profile', // Redirect on successful login
    failureRedirect: '/login',    // Redirect on failed login
    failureFlash: true            // Enable flash messages for failures
}));

// Route to handle user logout
router.get('/logout', (req, res) => {
    if (!req.isAuthenticated()) {
        // If the user is not authenticated, respond with an error message
        return res.status(400).send('You are not logged in');
    }

    // Proceed with logging out
    req.logout((err) => {
        if (err) {
            return next(err);
        }
        res.send('Logout Successfully');
        // res.redirect('/');
    });
});

router.post('/register', async (req, res, next) => {
    console.log(req.body)
    try {
        const { username, email, password } = req.body;
        const encryptedDetail = password;

        await User.register(new User({ username, email }), encryptedDetail);
        res.send("Registered Success")
        // res.redirect("/login");
    } catch (err) {
        console.error("Error: ", err);
        res.send(err.message);
    }
})

module.exports = router;

3.5 /middleware/isLoggedIn.js

// const isLoggedIn = (req, res, next) => {
exports.isLoggedIn = (req, res, next) => {
    if (req.isAuthenticated()) {
        return next();
    } else {
        res.redirect("/login");
    }
}

// module.exports = isLoggedIn; 
// isko dusre page mein fetch krne ke liye - destructuring krne ki jarrort nhi hai

3.6 /models/user.schema.js

// Import the mongoose library
const mongoose = require('mongoose');

// Import passport-local-mongoose plugin
const plm = require('passport-local-mongoose');

// Define a new schema for the user collection
const user_schema = mongoose.Schema({
    email: {
        type: String,
        required: true
    },
    username: {
        type: String,
        required: true // Field is required
    },
    password: String, // Password field as a string
}, { timestamps: true } // Automatically adds createdAt and updatedAt timestamps
);

// Apply the passport-local-mongoose plugin to the schema
// Configured to use 'email' as the field for login instead of the default 'username'
// user_schema.plugin(plm); 
user_schema.plugin(plm, { usernameField: 'email' });    // ⭐⭐⭐

// Create a model named "social-media" using the user schema
const User = mongoose.model("User", user_schema);

// Log a message indicating the schema has been created
console.log("Schema Created");

// Export the user collection model
module.exports = User;

3.7 /routes/index.route.js (Optional - Create yourself)

var express = require('express');
const { isLoggedIn } = require('../middleware/isLoggedIn');
var router = express.Router();

/* GET home page. */
router.get('/', function (req, res, next) {
  res.render('index', { title: 'Express' });
});

router.get('/register', (req, res, next) => {
  res.render('register', { error_messages: req.flash('error') });
})

router.get('/login', function (req, res, next) {
  res.send("Login Page");;
});

// Profile route
router.get('/profile', isLoggedIn, (req, res) => {
  // res.render('profile', { user: req.user });
  res.send('profile Page');
});

module.exports = router;

4. API Documentation

Endpoints

Login

  • POST /login

  • Body: { "email": "user@example.com", "password": "yourpassword" }

  • Success: { "token": "jwt-token" }

  • Error: { "message": "Invalid credentials" }

Register

  • POST /register

  • Body: { "email": "user@example.com", "username": "username", "password": "yourpassword" }

  • Success: { "message": "User registered" }

  • Error: { "message": "Validation error" }

Profile

  • GET /profile

  • Header: Authorization: Bearer <token>

  • Success: { "email": "user@example.com", "username": "username" }

  • Error: { "message": "Not authenticated" }

Logout

  • GET /logout

  • Header: Authorization: Bearer <token>

  • Success: { "message": "Logged out" }

  • Error: { "message": "Not authenticated" }

Errors

  • 400: Bad Request

  • 401: Unauthorized

  • 403: Forbidden

  • 404: Not Found

  • 500: Server Error