Introduction

JSON Web Tokens (JWT) are a popular method for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs are commonly used for authentication and authorization in web applications.

Key Concepts

  1. JWT Structure: A JWT is composed of three parts:

    • Header: Contains metadata about the token, such as the type of token (JWT) and the signing algorithm (e.g., HMAC SHA256).
    • Payload: Contains the claims, which are statements about an entity (typically, the user) and additional data.
    • Signature: Used to verify the token's integrity and authenticity.
  2. Claims: Claims are statements about an entity (typically, the user) and additional data. There are three types of claims:

    • Registered claims: Predefined claims that are not mandatory but recommended, such as iss (issuer), exp (expiration time), sub (subject), and aud (audience).
    • Public claims: Custom claims that can be defined by the user.
    • Private claims: Custom claims created to share information between parties that agree on using them.
  3. Signing Algorithms: JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair (with RSA or ECDSA).

Practical Example

Step 1: Setting Up the Project

First, create a new Node.js project and install the necessary dependencies.

mkdir jwt-authentication
cd jwt-authentication
npm init -y
npm install express jsonwebtoken body-parser

Step 2: Creating the Server

Create a file named server.js and set up a basic Express server.

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const app = express();
const PORT = 3000;
const SECRET_KEY = 'your_secret_key';

app.use(bodyParser.json());

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

Step 3: Creating JWTs

Add a route to generate a JWT when a user logs in.

app.post('/login', (req, res) => {
  const { username, password } = req.body;

  // In a real application, you would verify the username and password
  if (username === 'user' && password === 'password') {
    const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' });
    return res.json({ token });
  }

  res.status(401).json({ message: 'Invalid credentials' });
});

Step 4: Verifying JWTs

Add a middleware function to verify the JWT for protected routes.

const authenticateJWT = (req, res, next) => {
  const token = req.header('Authorization');

  if (token) {
    jwt.verify(token, SECRET_KEY, (err, user) => {
      if (err) {
        return res.sendStatus(403);
      }

      req.user = user;
      next();
    });
  } else {
    res.sendStatus(401);
  }
};

app.get('/protected', authenticateJWT, (req, res) => {
  res.json({ message: 'This is a protected route', user: req.user });
});

Full Code Example

Here is the complete server.js file:

const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const app = express();
const PORT = 3000;
const SECRET_KEY = 'your_secret_key';

app.use(bodyParser.json());

app.post('/login', (req, res) => {
  const { username, password } = req.body;

  // In a real application, you would verify the username and password
  if (username === 'user' && password === 'password') {
    const token = jwt.sign({ username }, SECRET_KEY, { expiresIn: '1h' });
    return res.json({ token });
  }

  res.status(401).json({ message: 'Invalid credentials' });
});

const authenticateJWT = (req, res, next) => {
  const token = req.header('Authorization');

  if (token) {
    jwt.verify(token, SECRET_KEY, (err, user) => {
      if (err) {
        return res.sendStatus(403);
      }

      req.user = user;
      next();
    });
  } else {
    res.sendStatus(401);
  }
};

app.get('/protected', authenticateJWT, (req, res) => {
  res.json({ message: 'This is a protected route', user: req.user });
});

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

Practical Exercises

Exercise 1: Implementing Logout

Task: Implement a logout route that invalidates the JWT.

Solution: In a stateless JWT implementation, you can't directly invalidate a token. However, you can implement token blacklisting or change the secret key periodically.

Exercise 2: Adding More Claims

Task: Add more claims to the JWT payload, such as role and email.

Solution: Modify the login route to include additional claims.

app.post('/login', (req, res) => {
  const { username, password } = req.body;

  if (username === 'user' && password === 'password') {
    const token = jwt.sign({ username, role: 'admin', email: '[email protected]' }, SECRET_KEY, { expiresIn: '1h' });
    return res.json({ token });
  }

  res.status(401).json({ message: 'Invalid credentials' });
});

Common Mistakes and Tips

  • Not using HTTPS: Always use HTTPS to protect the token during transmission.
  • Storing JWT in local storage: It's safer to store JWTs in HTTP-only cookies to prevent XSS attacks.
  • Not handling token expiration: Ensure your application handles token expiration gracefully and prompts the user to re-authenticate.

Conclusion

In this section, you learned about JWT authentication, how to create and verify JWTs, and how to protect routes using JWTs. This knowledge is crucial for building secure web applications. In the next section, you will learn about role-based access control to further enhance your application's security.

Node.js Course

Module 1: Introduction to Node.js

Module 2: Core Concepts

Module 3: File System and I/O

Module 4: HTTP and Web Servers

Module 5: NPM and Package Management

Module 6: Express.js Framework

Module 7: Databases and ORMs

Module 8: Authentication and Authorization

Module 9: Testing and Debugging

Module 10: Advanced Topics

Module 11: Deployment and DevOps

Module 12: Real-World Projects

© Copyright 2024. All rights reserved