Build secure social media REST APIs using JWT for token-based access in Node.js and Express. Ready to dive in?

Imagine you're building a social media app. You want users to log in and interact securely with the system, without asking for their passwords every time they access a protected resource. Here's where token-based authentication comes in handy! ๐Ÿ›ก๏ธ

Today, I'll walk you through creating a basic REST API in Node.js that uses JWT (JSON Web Tokens) to control access to specific routes. By the end of this guide, you'll have a working example of secure API access using token authentication. ๐Ÿš€

Let's get started!

Step 1: Setting up your project

To start, we need to create a simple Node.js project. Open your terminal and follow these steps:

Initialize a Node.js project:

mkdir social-api
cd social-api
npm init -y

Install the necessary dependencies: We'll need express for our server, jsonwebtoken for generating and verifying tokens, and bcrypt for hashing passwords.

npm install express jsonwebtoken bcryptjs

Install nodemon for auto-reloading: It's a handy tool to restart your server when code changes.

npm install --save-dev nodemon

Update the package.json to include a start script for nodemon:

"scripts": {
  "start": "nodemon index.js"
}

Step 2: Setting up the server with Express

Create a file named index.js in your project folder. Let's set up a basic Express server:

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

app.use(express.json());

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

Explanation: Here, we initialize an Express application and tell it to listen on port 3000. The express.json() middleware allows us to parse JSON request bodies.

Step 3: Creating routes for user registration and login

Now, let's add routes for user registration and login. First, we need a mock user database (just an in-memory array for simplicity):

const users = [];

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

  // Hash the password
  const hashedPassword = await bcrypt.hash(password, 10);
  
  users.push({ username, password: hashedPassword });

  res.status(201).send('User registered');
});

Explanation:

  • The /register route allows a user to register by providing a username and password.
  • We use bcrypt.hash() to hash the password before saving it to our users array. Hashing ensures the password is stored securely.

Step 4: Implementing login and generating a JWT

Next, let's create the /login route where users can authenticate. If the login is successful, we generate a JWT and send it back.

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

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

  if (!user) {
    return res.status(400).send('User not found');
  }

  // Compare the entered password with the stored hash
  const validPassword = await bcrypt.compare(password, user.password);

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

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

Explanation:

  • We check if the user exists, then compare the entered password with the hashed password using bcrypt.compare().
  • If valid, we use jwt.sign() to create a token, passing the username as the payload and using a secret key ('secretkey' in this example). The token expires in 1 hour (expiresIn: '1h').
  • Finally, we return the token as a JSON response.

Step 5: Protecting routes using JWT middleware

Now that we have a way to generate tokens, let's protect certain routes using a middleware that verifies the token. For example, a user's profile page should only be accessible if they are logged in.

function authenticateToken(req, res, next) {
  const token = req.headers['authorization'];
  
  if (!token) return res.sendStatus(401);

  jwt.verify(token, 'secretkey', (err, user) => {
    if (err) return res.sendStatus(403);
    
    req.user = user;
    next();
  });
}

app.get('/profile', authenticateToken, (req, res) => {
  res.json({ message: `Welcome, ${req.user.username}` });
});

Explanation:

  • The authenticateToken middleware extracts the token from the Authorization header.
  • jwt.verify() checks if the token is valid. If it's expired or invalid, we send a 403 status code.
  • If valid, the decoded user is attached to the request object, allowing the user's data to be accessed in the /profile route.

Step 6: Testing the API

Here's how the API works:

  1. Register a new user: Send a POST request to /register with a username and password.
  2. Login and get the token: Send a POST request to /login with the same credentials. The API responds with a JWT token.
  3. Access the profile route: Send a GET request to /profile, passing the token in the Authorization header.
Authorization: Bearer <token>

If successful, you'll see a message like:

{ "message": "Welcome, johndoe" }

Step 7: Wrapping it up

You now have a fully functioning token-based authentication system using JWT in Node.js. You can expand this further by adding more features, like token refresh mechanisms, and role-based access, or integrating it with databases like MongoDB for real-world applications. ๐ŸŒŸ

Conclusion

In this blog, we covered the basics of building a Node.js API that uses JWT to authenticate users and protect routes. By following this simple approach, you ensure that sensitive operations are locked behind a secure authentication layer, keeping your users' data safe. ๐Ÿ”’

This is just the beginning! Once you've mastered token-based authentication, you can easily scale and enhance it for more complex apps. Happy coding! ๐Ÿ˜Š