🟢 Node.js Q107 / 118

How do you implement a simple login system using Node.js and Express?

AI-Powered Answer ✓ Answered

This guide demonstrates how to build a basic login system using Node.js and Express. It covers user registration, password hashing with `bcrypt`, session management with `express-session`, and route protection. For simplicity, user data will be stored in an in-memory array; for a production application, you would integrate a persistent database.

1. Project Setup

Begin by creating a new Node.js project and installing the necessary dependencies: express for the web framework, body-parser to parse incoming request bodies, express-session for session management, and bcrypt for password hashing.

bash
npm init -y
bash
npm install express body-parser express-session bcrypt

2. Basic Express Application (`app.js`)

Create an app.js file. This will serve as your main application entry point. Set up Express, configure body-parser to handle form submissions, and initialize express-session to manage user sessions. Remember to replace 'your_secret_key' with a strong, unique secret key for production.

javascript
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const bcrypt = require('bcrypt'); // Required for password hashing

const app = express();
const PORT = 3000;

// Middleware setup
app.use(bodyParser.urlencoded({ extended: true })); // For parsing application/x-www-form-urlencoded
app.use(session({
    secret: 'your_secret_key', // Replace with a strong, unique secret string
    resave: false,
    saveUninitialized: false,
    cookie: { secure: false } // Set to true if using HTTPS in production
}));

// Simple root route
app.get('/', (req, res) => {
    if (req.session.userId) {
        res.send(`Welcome back, user ID: ${req.session.userId}! <a href="/logout">Logout</a>`);
    } else {
        res.send('Welcome to the simple login system. <a href="/login">Login</a> | <a href="/register">Register</a>');
    }
});

app.listen(PORT, () => {
    console.log(`Server running on http://localhost:${PORT}`);
});

3. User Data Storage (In-Memory)

For demonstration purposes, we'll store users in a simple array in memory. In a real application, you would integrate a database (e.g., MongoDB, PostgreSQL, MySQL) to persist user data. Add this code to your app.js file, preferably near the top after imports.

javascript
// In-memory user store (replace with a database in production)
const users = []; // Format: { id: number, username: string, passwordHash: string }
let nextUserId = 1; // Simple ID counter for new users

// Helper to find a user by username
function findUserByUsername(username) {
    return users.find(u => u.username === username);
}

// Helper to find a user by ID
function findUserById(id) {
    return users.find(u => u.id === id);
}

4. Registration Routes

Implement the routes for user registration. A GET request will display a basic registration form, and a POST request will handle the form submission, hash the password using bcrypt, and save the new user to our in-memory store. Add these routes to your app.js.

javascript
// Registration form (GET /register)
app.get('/register', (req, res) => {
    res.send(`
        <h1>Register</h1>
        <form action="/register" method="POST">
            <input type="text" name="username" placeholder="Username" required><br>
            <input type="password" name="password" placeholder="Password" required><br>
            <button type="submit">Register</button>
        </form>
        <p><a href="/login">Already have an account? Login here.</a></p>
    `);
});

// Handle registration submission (POST /register)
app.post('/register', async (req, res) => {
    const { username, password } = req.body;

    if (findUserByUsername(username)) {
        return res.status(400).send('User already exists.');
    }

    try {
        const hashedPassword = await bcrypt.hash(password, 10); // Hash password with 10 salt rounds
        const newUser = {
            id: nextUserId++,
            username,
            passwordHash: hashedPassword
        };
        users.push(newUser);
        console.log('Registered user:', newUser.username);
        res.redirect('/login'); // Redirect to login page after successful registration
    } catch (error) {
        console.error('Registration error:', error);
        res.status(500).send('Error registering user.');
    }
});

5. Login Routes

Create the routes for user login. The GET route will display a login form, and the POST route will verify the submitted credentials against the stored hashed passwords using bcrypt.compare(). On successful login, the user's ID will be stored in the session (req.session.userId). Add these routes to app.js.

javascript
// Login form (GET /login)
app.get('/login', (req, res) => {
    res.send(`
        <h1>Login</h1>
        <form action="/login" method="POST">
            <input type="text" name="username" placeholder="Username" required><br>
            <input type="password" name="password" placeholder="Password" required><br>
            <button type="submit">Login</button>
        </form>
        <p><a href="/register">Don't have an account? Register here.</a></p>
    `);
});

// Handle login submission (POST /login)
app.post('/login', async (req, res) => {
    const { username, password } = req.body;
    const user = findUserByUsername(username);

    if (!user) {
        return res.status(400).send('Invalid username or password.');
    }

    try {
        const match = await bcrypt.compare(password, user.passwordHash);
        if (match) {
            req.session.userId = user.id; // Store user ID in session
            console.log('User logged in:', user.username);
            res.redirect('/protected'); // Redirect to a protected page
        } else {
            res.status(400).send('Invalid username or password.');
        }
    } catch (error) {
        console.error('Login error:', error);
        res.status(500).send('Error logging in.');
    }
});

6. Protecting Routes with Middleware

To restrict access to certain pages to logged-in users only, create a middleware function. This isAuthenticated middleware checks if req.session.userId exists. If not, it redirects the user to the login page. Add this middleware and a protected route example to app.js.

javascript
// Middleware to check if user is authenticated
function isAuthenticated(req, res, next) {
    if (req.session.userId) {
        next(); // User is authenticated, proceed to the next handler
    } else {
        res.redirect('/login'); // User is not authenticated, redirect to login
    }
}

// Example of a protected route
app.get('/protected', isAuthenticated, (req, res) => {
    const user = findUserById(req.session.userId);
    if (user) {
        res.send(`<h1>Welcome, ${user.username}!</h1><p>This is a protected page.</p><p><a href="/logout">Logout</a></p>`);
    } else {
        res.redirect('/login'); // Fallback, should not happen if session is valid
    }
});

7. Logout Route

Finally, implement a logout route that destroys the user's session, effectively logging them out and removing their session data. Add this route to app.js.

javascript
// Logout route
app.get('/logout', (req, res) => {
    req.session.destroy(err => {
        if (err) {
            console.error('Error destroying session:', err);
            return res.status(500).send('Could not log out.');
        }
        res.redirect('/'); // Redirect to the homepage or login page after logout
    });
});