MERN Stack for Session and Cookies Handling

MERN Stack for Session and Cookies Handling

In a MERN (MongoDB, Express.js, React.js, Node.js) app, managing sessions and cookies usually needs a library like express-session for session control and cookie-parser for understanding cookies. I'll show you how to add session and cookies handling to a simple MERN authentication example below.

Backend (Node.js/Express):

  1. Install necessary packages:
cd mern-auth-backend
npm install express express-session mongoose cors bcrypt jsonwebtoken cookie-parser
  1. Update server.js:
const express = require('express');
const session = require('express-session');
const mongoose = require('mongoose');
const cors = require('cors');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');

const app = express();
const PORT = process.env.PORT || 5000;
const JWT_SECRET = 'your-secret-key';
const SESSION_SECRET = 'your-session-secret'; // Change this to a long, random string in production

app.use(cors({ credentials: true, origin: 'http://localhost:3000' }));
app.use(express.json());
app.use(cookieParser());
app.use(session({
  secret: SESSION_SECRET,
  resave: true,
  saveUninitialized: true,
  cookie: {
    maxAge: 1000 * 60 * 60 * 24, // 1 day
    httpOnly: true,
    secure: false, // Set to true in production if using HTTPS
  },
}));

mongoose.connect('mongodb://localhost:27017/mern-auth', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});

const userSchema = new mongoose.Schema({
  username: String,
  password: String,
});

const User = mongoose.model('User', userSchema);

app.post('/api/register', async (req, res) => {
  try {
    const { username, password } = req.body;
    const hashedPassword = await bcrypt.hash(password, 10);
    const user = new User({ username, password: hashedPassword });
    await user.save();
    res.status(201).json({ message: 'User registered successfully' });
  } catch (error) {
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.post('/api/login', async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const passwordMatch = await bcrypt.compare(password, user.password);

    if (!passwordMatch) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const token = jwt.sign({ username: user.username }, JWT_SECRET);

    // Set the token in a cookie
    res.cookie('token', token, {
      httpOnly: true,
      maxAge: 1000 * 60 * 60 * 24, // 1 day
      secure: false, // Set to true in production if using HTTPS
    });

    res.json({ token });
  } catch (error) {
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

In this updated backend code:

  • The express-session middleware is used for session handling, and cookie-parser is used to parse cookies.

  • The session is configured to use a session secret and set cookies with specific properties.

  • When a user logs in, the server generates a JWT token and sets it in a cookie.

Frontend (React):

  1. Install the axios library for making HTTP requests:
cd mern-auth-client
npm install axios
  1. Update src/App.js:
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import axios from 'axios';

const App = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [token, setToken] = useState('');

  const handleRegister = () => {
     fetch('/api/register', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ username, password }),
    })
      .then((res) => res.json())
      .then((data) => alert(data.message))
      .catch((error) => console.error('Error registering:', error));
  };

  const handleLogin = async () => {
    try {
      const response = await axios.post('http://localhost:5000/api/login', {
        username,
        password,
      });

      const { token } = response.data;
      setToken(token);
      alert('Login successful');
    } catch (error) {
      alert('Invalid credentials');
    }
  };

  const handleLogout = () => {
    // Clear the token in the cookie and state
    document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    setToken('');
  };

  useEffect(() => {
    // You can use the token for authenticated requests here
    // For simplicity, let's just log it to the console
    console.log('Token:', token);
  }, [token]);

  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/register">Register</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
          </ul>
        </nav>

        <hr />

        <Route path="/" exact>
          <h2>Home</h2>
          {token ? (
            <button onClick={handleLogout}>Logout</button>
          ) : (
            <p>Please register or log in.</p>
          )}
        </Route>

        <Route path="/register">
          <h2>Register</h2>
          <input
            type="text"
            placeholder="Username"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
          <input
            type="password"
            placeholder="Password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
          <button onClick={handleRegister}>Register</button>
        </Route>

        <Route path="/login">
          <h2>Login</h2>
          <input
            type="text"
            placeholder="Username"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
          <input
            type="password"
            placeholder="Password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
          <button onClick={handleLogin}>Login</button>
        </Route>
      </div>
    </Router>
  );
};

export default App;

In this updated frontend code:

    • The axios library helps make HTTP requests.

      • When a user logs in, the frontend sends a POST request to the /api/login endpoint. The server sets the token in a cookie and returns it in the response.

Now, when a user logs in, a session starts, and a token is saved in a cookie for later authenticated requests. This is a simple setup. In a real-world situation, you'd want to add more security steps, like using HTTPS and protecting the JWT secret.

Did you find this article valuable?

Support LingarajTechhub All About Programming by becoming a sponsor. Any amount is appreciated!