Here’s a blog post on understanding JWT authentication using the provided backend code. You can adjust the tone and details to match your preferred s

Lokendra PandeyLokendra Pandey
5 min read

In modern web applications, security is crucial. One widely used method for managing authentication is JSON Web Tokens (JWT). In this blog, we’ll explore JWT authentication using a practical example—our blog application backend.

What is JWT?

JSON Web Token (JWT) is a compact, URL-safe token format that allows you to securely transmit information between parties. It’s often used for authentication and authorization in web applications. A JWT is composed of three parts:

  1. Header: Contains metadata about the token (e.g., type and signing algorithm).

  2. Payload: Contains the claims or information (e.g., user ID, roles).

  3. Signature: Ensures the token’s integrity and authenticity.

Setting Up the Backend

We’ll use Node.js with Express and MongoDB to build a backend that demonstrates JWT authentication. Here’s a step-by-step breakdown of the code.

1. Initial Setup

We start by setting up the required libraries and initializing our Express app:

javascriptCopy codeconst express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const multer = require('multer');
const path = require('path');
const cors = require('cors');

// Initialize Express App
const app = express();
const PORT = 8000;

app.use(express.json());
app.use(cors());
app.use('/uploads', express.static('uploads')); // Serve static files from uploads directory
  • Express: Framework for building the server.

  • Mongoose: For MongoDB object modeling.

  • Bcryptjs: For hashing passwords.

  • Jsonwebtoken: For generating and verifying JWTs.

  • Multer: For handling file uploads.

  • Cors: For handling cross-origin requests.

2. Connecting to MongoDB

javascriptCopy codemongoose.connect("mongodb://127.0.0.1/blogify", {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
  .then(() => console.log("mongodb is connected"))
  .catch((err) => console.log("error", err));

We connect to a MongoDB database named blogify. Mongoose handles our database operations.

3. Defining Models

User Model

javascriptCopy codeconst UserSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  profilePicture: { type: String }
});

UserSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

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

The User schema defines how user data is structured. The pre('save') hook ensures passwords are hashed before saving them to the database.

BlogPost Model

javascriptCopy codeconst BlogPostSchema = new mongoose.Schema({
  title: { type: String, required: true },
  content: { type: String, required: true },
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  createdAt: { type: Date, default: Date.now }
});

const BlogPost = mongoose.model('BlogPost', BlogPostSchema);

The BlogPost schema defines the structure of blog posts, linking each post to a user.

4. Authentication Middleware

javascriptCopy codeconst authMiddleware = (req, res, next) => {
  const token = req.header('Authorization').replace('Bearer ', '');
  if (!token) return res.status(401).json({ message: 'No token, authorization denied' });

  try {
    const decoded = jwt.verify(token, 'secretKey');
    req.user = decoded.id;
    next();
  } catch (err) {
    res.status(401).json({ message: 'Token is not valid' });
  }
};

authMiddleware extracts and verifies the JWT from the request header. If valid, it allows access to the protected route.

5. Routes

User Routes

javascriptCopy codeapp.post('/api/auth/register', async (req, res) => {
  const { username, email, password } = req.body;
  try {
    const user = new User({ username, email, password });
    await user.save();
    res.status(201).json(user);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;
  try {
    const user = await User.findOne({ email });
    if (!user) return res.status(404).json({ message: 'User not found' });

    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' });

    const token = jwt.sign({ id: user._id }, 'secretKey', { expiresIn: '1h' });
    res.json({ token });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});
  • /api/auth/register: Registers a new user.

  • /api/auth/login: Authenticates a user and returns a JWT token.

Blog Routes

javascriptCopy codeapp.post('/api/blog', authMiddleware, async (req, res) => {
  const { title, content } = req.body;
  try {
    const blogPost = new BlogPost({ title, content, author: req.user });
    await blogPost.save();
    res.status(201).json(blogPost);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.get('/api/blog', async (req, res) => {
  try {
    const blogPosts = await BlogPost.find().populate('author', 'username email');
    res.json(blogPosts);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.get('/api/blog/:id', async (req, res) => {
  try {
    const blogPost = await BlogPost.findById(req.params.id).populate('author', 'username email');
    if (!blogPost) return res.status(404).json({ message: 'Post not found' });
    res.json(blogPost);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.put('/api/blog/:id', authMiddleware, async (req, res) => {
  try {
    const blogPost = await BlogPost.findById(req.params.id);
    if (!blogPost) return res.status(404).json({ message: 'Post not found' });

    if (blogPost.author.toString() !== req.user) return res.status(401).json({ message: 'User not authorized' });

    blogPost.title = req.body.title || blogPost.title;
    blogPost.content = req.body.content || blogPost.content;
    await blogPost.save();

    res.json(blogPost);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.delete('/api/blog/:id', authMiddleware, async (req, res) => {
  try {
    const blogPost = await BlogPost.findById(req.params.id);
    if (!blogPost) return res.status(404).json({ message: 'Post not found' });

    if (blogPost.author.toString() !== req.user) return res.status(401).json({ message: 'User not authorized' });

    await blogPost.remove();
    res.json({ message: 'Post removed' });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});
  • /api/blog: Create, read, update, and delete blog posts (with authentication).

6. File Upload

javascriptCopy codeconst storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
  }
});

const upload = multer({ storage: storage });

app.post('/api/upload', upload.single('profilePicture'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ message: 'No file uploaded' });
  }
  res.json({ filePath: req.file.path });
});
  • Multer Setup: Manages file uploads and saves them in the uploads directory.

Conclusion

JWTs are a powerful tool for managing authentication in web applications. They offer a stateless, scalable method for verifying user identity. By understanding and implementing JWT authentication, you can build secure and robust web applications.

Feel free to dive into the code, tweak it, and explore how JWTs fit into your application’s authentication flow. Happy coding!

2
Subscribe to my newsletter

Read articles from Lokendra Pandey directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Lokendra Pandey
Lokendra Pandey