DEV Community

Hamza Khan
Hamza Khan

Posted on

🛡️ Securing Your Express.js App: JWT Authentication Step-by-Step 🚀

When building web applications, security is one of the most critical aspects you need to focus on. One common way to secure routes and data is by using JWT (JSON Web Token) for authentication. In this post, we’ll walk through implementing JWT authentication in an Express.js app!


⚙️ What is JWT?

JWT (JSON Web Token) is an open standard that defines a compact, self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it’s digitally signed.

  • Stateless: Authentication is stored on the client side.
  • Compact: JWTs are small and can be sent via URL, POST request, or HTTP headers.
  • Secure: Information is signed using a secret or public/private key pair.

🛠️ Step-by-Step Guide to Implement JWT Authentication

Let’s go through the process of securing an Express.js app using JWT. We’ll cover user login, generating tokens, and protecting routes.


1️⃣ Setting Up the Project

First, let’s create an Express.js project and install the necessary dependencies.

# Create a new folder for the project
mkdir express-jwt-auth && cd express-jwt-auth

# Initialize a Node.js project
npm init -y

# Install necessary packages
npm install express jsonwebtoken bcryptjs body-parser
Enter fullscreen mode Exit fullscreen mode
  • express: For creating the server.
  • jsonwebtoken: To create and verify JWTs.
  • bcryptjs: To hash and compare passwords securely.
  • body-parser: To parse incoming request bodies.

2️⃣ Creating the Express App

Now, let’s create a basic Express server.

// server.js
const express = require('express');
const bodyParser = require('body-parser');

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

// Middleware to parse incoming requests
app.use(bodyParser.json());

// Start the server
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Run the server:

node server.js
Enter fullscreen mode Exit fullscreen mode

3️⃣ User Registration and Password Hashing

For authentication, we need a way to store user credentials securely. Let’s create a simple in-memory user storage and register users with hashed passwords using bcryptjs.

// userController.js
const bcrypt = require('bcryptjs');

let users = []; // In-memory user storage

// Register a new user
exports.registerUser = async (req, res) => {
  const { username, password } = req.body;

  // Hash the password
  const hashedPassword = await bcrypt.hash(password, 10);

  // Store user with hashed password
  users.push({ username, password: hashedPassword });

  res.status(201).json({ message: 'User registered successfully!' });
};
Enter fullscreen mode Exit fullscreen mode

4️⃣ User Login and JWT Token Generation

When a user logs in, we validate the credentials and generate a JWT token for them. The token is used to authenticate subsequent requests.

// userController.js
const jwt = require('jsonwebtoken');
const secretKey = 'your-secret-key'; // Store securely in environment variables

// User login and JWT generation
exports.loginUser = async (req, res) => {
  const { username, password } = req.body;

  // Find user by username
  const user = users.find(u => u.username === username);
  if (!user) return res.status(400).json({ message: 'Invalid credentials' });

  // Compare password with the stored hash
  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) return res.status(400).json({ message: 'Invalid credentials' });

  // Generate JWT token
  const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });

  res.json({ token });
};
Enter fullscreen mode Exit fullscreen mode

5️⃣ Protecting Routes with JWT Middleware

Now that users can log in and receive JWT tokens, we need to protect certain routes by verifying the token before allowing access.

// authMiddleware.js
const jwt = require('jsonwebtoken');
const secretKey = 'your-secret-key';

// Middleware to protect routes
exports.verifyToken = (req, res, next) => {
  const token = req.header('Authorization');

  if (!token) return res.status(401).json({ message: 'Access Denied' });

  try {
    // Verify the token
    const verified = jwt.verify(token.split(' ')[1], secretKey);
    req.user = verified;
    next();
  } catch (err) {
    res.status(400).json({ message: 'Invalid Token' });
  }
};
Enter fullscreen mode Exit fullscreen mode

6️⃣ Securing Routes

Now, we can apply the verifyToken middleware to any route we want to protect.

// server.js
const { registerUser, loginUser } = require('./userController');
const { verifyToken } = require('./authMiddleware');

app.post('/register', registerUser);
app.post('/login', loginUser);

// Protected route
app.get('/dashboard', verifyToken, (req, res) => {
  res.json({ message: `Welcome to the dashboard, ${req.user.username}!` });
});
Enter fullscreen mode Exit fullscreen mode
  • /register: Allows users to register.
  • /login: Logs users in and generates JWT tokens.
  • /dashboard: A protected route, accessible only with a valid token.

🔒 Testing the Application

  1. Register a user:
curl -X POST http://localhost:5000/register -H "Content-Type: application/json" -d '{"username": "user1", "password": "password123"}'
Enter fullscreen mode Exit fullscreen mode
  1. Login and receive a JWT token:
curl -X POST http://localhost:5000/login -H "Content-Type: application/json" -d '{"username": "user1", "password": "password123"}'
Enter fullscreen mode Exit fullscreen mode

You’ll get a JWT token in response:

{
  "token": "your-jwt-token"
}
Enter fullscreen mode Exit fullscreen mode
  1. Access the protected route:

Use the token to access the /dashboard route:

curl -X GET http://localhost:5000/dashboard -H "Authorization: Bearer your-jwt-token"
Enter fullscreen mode Exit fullscreen mode

If the token is valid, you’ll receive a welcome message.


🔐 Why JWT?

  1. Stateless Authentication: JWTs are stored on the client-side, reducing the need for server-side sessions.
  2. Flexibility: They can be used across different services and technologies, as long as they share the same secret.
  3. Security: JWTs are signed, making them secure as long as you keep your secret key safe.

🏁 Conclusion

In this tutorial, we’ve implemented a simple JWT authentication system in an Express.js app, showing how to register users, log them in, and protect routes using middleware. JWT makes it easy to add stateless authentication to your Node.js applications, ensuring security and flexibility.

JWTs are a powerful tool for securing your API, but remember to always keep your secret key safe and implement additional security measures like HTTPS, token expiration, and refreshing tokens to protect your app.


📚 Additional Resources:


Happy coding! Let me know if you have any questions or improvements! 😄

Top comments (0)