REST API Security: Don’t Let Your System Crash Due to Basic Mistakes

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Is your API wide open to hackers?

Many new web developers prioritize making the API functional and returning the correct JSON. But believe me, putting an insecure API on the internet is like hanging a “Welcome” sign for hackers. In fact, I once stayed up all night handling an SSH brute-force attack on a server just because of a leaked test endpoint—from that, I learned a lesson: Security must be set up from the very first line of code, regardless of the project’s size.

An API is not just a place to send and receive data; it is the only gateway for the outside world to touch your database. According to a report by Akamai, attacks on APIs account for up to 75% of all web attacks today. Without a defense layer, user data can be stolen, and the entire system could even be wiped out in an instant.

3 “Non-negotiable” Layers of Defense

To protect an API comprehensively, I always apply the 3-layer rule that any Senior developer would require:

  • Authentication: Identity verification. You must prove who you are before entering the system.
  • Authorization: Access control. A customer should not have the permission to delete a shop owner’s product.
  • Data Validation: Filtering malicious code. This step helps stop techniques like SQL Injection or XSS right at the gate.

1. Smart Authentication with JSON Web Token (JWT)

Traditional sessions often cause difficulties when scaling servers. JWT solves this with a stateless mechanism (no state stored on the server). Upon successful login, the server returns a token. In subsequent requests, the client only needs to attach this token to the header.

Here is how I implement a JWT check middleware in Node.js to ensure security:

const jwt = require('jsonwebtoken');

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

    if (!token) return res.status(401).json({ message: "You must be logged in to perform this action!" });

    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
        if (err) return res.status(403).json({ message: "Token has expired or is invalid!" });
        req.user = user;
        next();
    });
};

Pro tip: Keep the token expiration time (exp) short, around 15-30 minutes, and use Refresh Tokens to enhance security.

2. Authorization – Stopping IDOR Vulnerabilities

Authentication is only half the battle. An extremely common mistake is IDOR (Insecure Direct Object Reference). This occurs when user A can view user B’s order simply by changing the id in the URL.

The best solution is to use RBAC (Role-Based Access Control). You need a middleware to check the user’s role before allowing access to the controller.

const authorizeRoles = (...allowedRoles) => {
    return (req, res, next) => {
        if (!allowedRoles.includes(req.user.role)) {
            return res.status(403).json({ message: "You do not have permission to access this area!" });
        }
        next();
    };
};

// Only Admin has the right to delete products
router.delete('/product/:id', authenticateToken, authorizeRoles('admin'), deleteProduct);

3. Data Validation – Never Trust the Client

Always assume that all data sent from the client is “dirty.” Just one moment of negligence in checking data types, and a hacker could send a destructive script instead of a number.

Instead of writing manual if/else statements, use libraries like Joi or Zod to define strict schemas:

const Joi = require('joi');

const schema = Joi.object({
    username: Joi.string().alphanum().min(3).max(30).required(),
    email: Joi.string().email().required(),
});

// Validate input data
const { error } = schema.validate(req.body);
if (error) return res.status(400).send(error.details[0].message);

Other Practical “Checkpoints”

In addition to the three pillars above, I often add these extra layers of protection to make the API truly resilient against automated attacks:

Enforce HTTPS

Without HTTPS, every security layer behind it is almost meaningless. Hackers can use Man-in-the-middle techniques to eavesdrop and easily steal tokens. Use TLS 1.2 or 1.3 to encrypt the entire data transmission path.

Rate Limiting – Blocking Bots and Spam

If an IP sends hundreds of requests per second, it is definitely a bot trying to cause disruption or brute-force passwords. The express-rate-limit library is a lifesaver that helps you limit the number of requests within a specific timeframe.

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

const limiter = rateLimit({
    windowMs: 1 * 60 * 1000, // 1 minute
    max: 60, // Max 60 requests per minute from a single IP
    message: "Too many requests, please wait a moment!"
});

app.use('/api/', limiter);

Use Helmet to Hide Technology Info

By default, Express often attaches the X-Powered-By: Express header. This inadvertently points hackers toward vulnerabilities in the specific framework version you are using. Just install helmet, and this library will automatically clean up sensitive headers and configure basic security for you.

Conclusion

API security is not a destination but a continuous journey. Don’t wait until your server is attacked in the middle of the night to start scrambling for patches. Cultivate the habit of writing secure code, review logs regularly, and update security patches for libraries as soon as they are available. Good luck building robust systems!

Share: