Securing an API involves two distinct concepts:

  • Authentication (AuthN): The process of verifying a user's identity. "Are you who you say you are?" This is typically done with a username and password.
  • Authorization (AuthZ): The process of verifying that an authenticated user has permission to access a specific resource. "Are you allowed to do this?"

For modern APIs, the most common way to handle this is with JSON Web Tokens (JWT).

The JWT Authentication Flow

JWT provides a stateless authentication mechanism. The server doesn't need to store session information in memory or a database.

  1. Login: The user sends their credentials (e.g., email/password) to a /login endpoint.
  2. Verification: The server validates the credentials against the database.
  3. Token Creation: If the credentials are valid, the server creates a JWT. A JWT consists of three parts: a header, a payload (containing user info like ID and role), and a signature. The signature is created using a secret key known only to the server.
  4. Token Issuance: The server sends the signed JWT back to the client.
  5. Token Storage: The client stores the JWT (usually in memory or localStorage).
  6. Authenticated Requests: For every subsequent request to a protected route, the client includes the JWT in the Authorization header, typically as Bearer <token>.
  7. Token Verification: The server receives the request, extracts the token, and verifies its signature using the secret key. If the signature is valid, the server trusts the information in the payload and processes the request. If not, it rejects the request with a 401 Unauthorized error.

Implementation Example

First, install the necessary libraries:

Bash


npm install jsonwebtoken bcryptjs
# bcryptjs is for securely hashing passwords

1. Login Route to Create a Token

JavaScript


const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();
app.use(express.json());

// In a real app, you get this from environment variables!
const JWT_SECRET = 'your-super-secret-key-that-is-very-long';

app.post('/login', (req, res) => {
  // --- In a real app, you'd do this: ---
  // 1. Get email/password from req.body
  // 2. Find the user in the database by email
  // 3. Compare the provided password with the stored hashed password using bcrypt.compare()

  // For this example, let's assume authentication is successful
  const user = { id: 123, username: 'testuser', role: 'user' };

  // Create the JWT payload
  const payload = {
    userId: user.id,
    role: user.role,
  };

  // Sign the token
  const token = jwt.sign(payload, JWT_SECRET, { expiresIn: '1h' }); // Token expires in 1 hour

  res.json({ success: true, token: token });
});

2. Middleware to Protect Routes

This middleware will check for a valid token on incoming requests.

JavaScript


function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

  if (token == null) {
    return res.sendStatus(401); // Unauthorized
  }

  jwt.verify(token, JWT_SECRET, (err, user) => {
    if (err) {
      return res.sendStatus(403); // Forbidden (invalid token)
    }
    // If token is valid, attach the user payload to the request object
    req.user = user;
    next();
  });
}

// Use the middleware to protect a route
app.get('/profile', authenticateToken, (req, res) => {
  // Thanks to the middleware, req.user is now available
  res.json({ message: `Welcome user ${req.user.userId}!` });
});

Now, the /profile endpoint can only be accessed by clients who provide a valid JWT.