Course:Node.js & Express/
Lesson

Security isn't something you add to an APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. after it works, it's something you build in from the start. The good news is that most API security comes down to a handful of well-understood patterns. Get these right and you've covered the vast majority of real-world attack surface.

Transport security

Everything starts with HTTPSWhat is https?HTTP with encryption added, so data traveling between your browser and a server can't be read or tampered with by anyone in between.. HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. sends your data in plaintext, anyone on the same network can read it. HTTPS encrypts the connection. In production, there is no reason to serve an APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. over HTTP.

// Never in production
http://api.example.com/users

// Always
https://api.example.com/users

Beyond HTTPS, add security headers to every response. These headers tell browsers and clients how to treat your responses:

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'

Strict-Transport-Security tells browsers to only ever connect to your domain over HTTPS, even if someone types http://. It's enforced by the browser for up to a year after it first sees the header.

02

AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token.

Authentication answers: "Who are you?" Three common approaches:

APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. keys

Simple key passed in a header. Good for server-to-server communication and internal APIs:

GET /api/data
X-API-Key: your-api-key-here

The downside: if the key leaks, you have to rotate it everywhere. No expiry, no claims, no scoping without extra infrastructure.

JWTWhat is jwt?JSON Web Token - a self-contained, signed token that carries user data (like user ID and role). The server can verify it without a database lookup. (JSON Web Tokens)

The industry standard for statelessWhat is stateless?A design where each request contains all the information the server needs, so any server can handle any request without remembering previous ones. APIs. The client authenticates once, receives a tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use., and sends it with every subsequent request:

GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

The token itself contains claims, the user's ID, roles, expiry time, encoded and signed. The server verifies the signature without hitting the database. Tokens expire, which limits the damage from leaks.

OAuth 2.0What is oauth?An authorization protocol that lets users grant a third-party app limited access to their account on another service without sharing their password.

When your API needs to act on behalf of a user across third-party systems. The flows depend on the client type:

FlowClient typeUse case
Authorization CodeWeb app"Login with Google"
PKCEMobile / SPASame, without client secret
Client CredentialsServer-to-serverService accounts, background jobs
03

AuthorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages.

AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. proves identity. Authorization decides what that identity is allowed to do. Confusing these is a common source of bugs.

Role-based access controlWhat is rbac?Role-Based Access Control - assigning permissions to roles (like admin or editor), then giving users roles instead of individual permissions.

The simplest model: users have roles, roles have permissions. A middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. check before sensitive routes:

function requireRole(role) {
  return (req, res, next) => {
    if (!req.user.roles.includes(role)) {
      return res.status(403).json({
        error: 'Forbidden: insufficient permissions'
      });
    }
    next();
  };
}

app.delete('/users/:id', requireRole('admin'), deleteUser);

Resource-level authorization

Beyond roles, check that the authenticated user is allowed to access this specific resource. A common mistake is forgetting this check, a logged-in user can view another user's private data just by guessing their ID:

app.get('/users/:id', async (req, res) => {
  if (req.user.id !== req.params.id && !req.user.isAdmin) {
    return res.status(403).json({
      error: 'Cannot access another user\'s data'
    });
  }
  // ... fetch user
});
This class of bug has a name
Broken Object Level Authorization (BOLA), and it's consistently the #1 API security vulnerability according to the OWASP API Security Top 10. Always check that the requesting user owns the resource they're asking for.
04

Input validation

Never trust client input. Validate the shape, type, and value of everything the server receives, even from your own frontend:

const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(2).max(100).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(0).max(150),
  role: Joi.string().valid('user', 'admin').default('user')
});

app.post('/users', (req, res) => {
  const { error, value } = userSchema.validate(req.body);
  if (error) {
    return res.status(422).json({
      error: 'Validation failed',
      details: error.details
    });
  }
  // use `value`, not `req.body` - it's been sanitized
});

Beyond shape validation, use parameterized queries to prevent SQL injectionWhat is sql injection?An attack where user input is inserted directly into a database query, letting the attacker read, modify, or delete data. Parameterized queries prevent it.. Never concatenate user input into a query stringWhat is query string?The part of a URL after the ? that carries optional key-value pairs (like ?page=2&limit=10) used for filtering, sorting, or pagination.:

// SQL injection waiting to happen
const query = `SELECT * FROM users WHERE id = ${req.params.id}`;

// Parameterized - safe
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [req.params.id]);
05

Rate limitingWhat is rate limiting?Restricting how many requests a client can make within a time window. Prevents brute-force attacks and protects your API from being overwhelmed.

Rate limiting caps how many requests a client can make in a time window. It protects against brute force attacks on auth endpoints, scrapers, and accidental infinite loops in client code:

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100
});

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // 5 login attempts per 15 minutes
  skipSuccessfulRequests: true
});

app.use('/api/', apiLimiter);
app.use('/auth/login', authLimiter);

Set much stricter limits on login and password-reset endpoints than on general APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. endpoints. When a limit is hit, return 429 Too Many Requests with a Retry-After header.

06

CORSWhat is cors?Cross-Origin Resource Sharing - a browser security rule that blocks web pages from making requests to a different domain unless that domain explicitly allows it. configuration

CORS (Cross-Origin Resource Sharing) controls which websites can make requests to your APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. from a browser. The temptation is to set Access-Control-Allow-Origin: * to make everything work, resist it for authenticated endpoints:

const corsOptions = {
  origin: [
    'https://app.example.com',
    'https://admin.example.com'
  ],
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
};

app.use(cors(corsOptions));

credentials: true is required for cookies or AuthorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages. headers to work cross-originWhat is origin?The combination of protocol, domain, and port that defines a security boundary in the browser, like https://example.com:443.. When you use this, you cannot use * as the origin, you must specify exact domains.

07

Quick reference

LayerToolWhat it prevents
TransportHTTPS + HSTSEavesdropping, MITM
AuthenticationJWT / OAuthUnauthorized access
AuthorizationRBAC + resource checksPrivilege escalation, BOLA
Input validationSchema validationInjection attacks, bad data
InjectionParameterized queriesSQL/NoSQL injection
Rate limitingRequest quotasBrute force, DDoS
CORSOrigin allowlistCross-site request forgery