DEV Community

Cover image for Advanced CSRF Protection with RSA
Nelson Odo
Nelson Odo

Posted on

Advanced CSRF Protection with RSA

A Modern Take on Enhancing API Security Beyond Authentication πŸ”’πŸš€

In the evolving world of web security, authentication and authorization are the backbone of most secure systems. However, relying solely on these mechanisms without addressing CSRF (Cross-Site Request Forgery) or brute-force risks can leave your APIs exposed. A malicious actor can inspect network calls, mimic headers, and execute requests outside your browserβ€”using tools like Postman, CURL, or custom scripts.

Sounds alarming, right? Even top platforms like X (formerly Twitter), Facebook, TikTok, and Instagram are not immune to this issue. So, how do we fight back? Introducing Advanced CSRF Protection using RSA encryptionβ€”a lightweight yet effective way to prevent unauthorized API usage, brute-force attempts, and stale requests.

This technique adds an extra layer of security on top of your existing authentication system. By encrypting request metadata like the method, URL, and timestamp using RSA, we ensure each API request is uniquely validated and cannot be replayed or reused.


How Does It Work? πŸ› οΈ

We leverage RSA asymmetric encryption to validate the integrity of API requests. Here's the flow:

  1. Generate an RSA key pair (public and private keys).You can use tools like cryptotools.net/rsagen.
  2. The public key is stored on the client (ideally in environment variables).
  3. Before making an API request, we encrypt a payload containing:
    • The request method
    • The request URL
    • A current timestamp
  4. This encrypted payload is sent as a custom csrf header.
  5. On the backend:
    • The payload is decrypted using the private RSA key.
    • Validation ensures:
      • The method and URL match.
      • The timestamp isn’t stale (e.g., older than 15 seconds).
  6. If validation passes, the request proceeds; otherwise, it’s rejected.

This approach ensures that every API request is tightly bound to specific parameters, preventing replay attacks, stale requests, and brute-force abuse.


Client-Side Implementation (React + Axios) βš›οΈ

To streamline CSRF handling & ensure that it'll be available for all our request, we’ll use Axios interceptors. Here’s the setup in TypeScript:

import axios, { AxiosError } from "axios";
import appConfig from "config"; // Import your app config
import { rsaCrypt } from "Utils/rsaCrypt"; // RSA utility

const axiosClient = axios.create({
  baseURL: appConfig.BASE_URL,
  headers: {
    "Content-Type": "application/json",
  },
});

// Helper function to create the CSRF payload
const getCsrfPayload = (method: string, url: string) => {
  return {
    url,
    method,
    timestamp: Date.now(),
  };
};

// Axios request interceptor
axiosClient.interceptors.request.use(
  async (config) => {
    if (config && config.headers) {
      // Generate CSRF payload
      const csrfPayload = getCsrfPayload(config.method!, config.url!);
      // Encrypt the payload
      const csrfToken = await rsaCrypt.encrypt(JSON.stringify(csrfPayload));
      if (csrfToken) {
        config.headers["csrf"] = csrfToken; // Attach the encrypted token to headers
      }
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Axios response interceptor
axiosClient.interceptors.response.use(
  function (response) {
    return response;
  },
  async function (error: AxiosError<{ code: string; message: string }>) {
    return Promise.reject(error);
  }
);

export default axiosClient;

Enter fullscreen mode Exit fullscreen mode

Backend Implementation (Node.js + Express) πŸ›‘οΈ

On the server, we’ll validate the incoming CSRF token using the RSA private key and ensure the request integrity.

Middleware for CSRF Validation

import { NextFunction, Request, Response } from "express";
import { BadRequestError } from "../utils/custom.error";
import { rsaEncrypt } from "../utils/rsa.crypt";

type EncPayload = {
  url: string;
  method: string;
  timestamp: number;
};

export async function csrfValidation(req: Request, res: Response, next: NextFunction) {
  const urlPath = decodeURIComponent(req.originalUrl.split("?")[0]?.trim() || "");
  const method = req.method.toLowerCase();

  const csrf = req.headers["csrf"];

  if (!csrf || Array.isArray(csrf)) {
    throw new BadRequestError("CSRF token is missing or malformed.", "CSRF_TOKEN_MISSING");
  }

  const now = Date.now();
  const payload = decryptPayload(csrf);

  if (!payload) {
    throw new BadRequestError("Failed to decrypt or parse CSRF token.", "CSRF_TOKEN_INVALID");
  }

  const payloadUrlPath = decodeURIComponent(payload.url.split("?")[0]?.trim() || "");
  if (urlPath !== payloadUrlPath || payload.method.toLowerCase() !== method) {
    throw new BadRequestError(
      "CSRF token validation failed: URL or method mismatch.",
      "CSRF_VALIDATION_FAILED"
    );
  }

  const queryExpiry = 15 * 1000; // 15 seconds
  if (now - payload.timestamp > queryExpiry) {
    throw new BadRequestError("CSRF token has expired.", "CSRF_TOKEN_EXPIRED");
  }

  next();
}

function decryptPayload<T = EncPayload>(payload: string): T | undefined {
  try {
    return JSON.parse(rsaEncrypt.decrypt(payload)) as T;
  } catch (error) {
    console.error("Error decrypting payload:", error);
    return undefined;
  }
}

Enter fullscreen mode Exit fullscreen mode

Key Features 🎯

  • Asymmetric Encryption: Only the server knows the private key, ensuring tokens cannot be forged.
  • Request Freshness: Timestamps limit the token's validity to 15 seconds, preventing replay attacks.
  • Method & URL Validation: Ensures tokens are tied to specific actions, blocking brute-force scripts.

I implemented this approach in one of my projects, Winzy Social, to enhance the security of API interactions and prevent unauthorized or stale requests. Feel free to check it out!

Demo & Source Code πŸ“‚

GitHub Repository: https://github.com/Nedum84/csrf-with-rsa
Live Demo: https://nedum84.github.io/csrf-with-rsa

Conclusion πŸ“

This approach enhances your API security in addition to existing authentication and authorization mechanisms. By introducing encrypted, timestamped CSRF tokens, you can prevent malicious actors from abusing your APIs outside the browser.

Give this a shot and level up your application security! πŸš€


Let's Connect 🌐


What Do You Think? πŸ’­

Share your thoughts! You can improve the code to serve your use case. Did this post help you?
Have ideas or feedback to share? Let’s continue the conversation in the comments below! 😊

Top comments (1)

Collapse
 
eioluseyi profile image
EIO β€’ Emmanuel Imolorhe

Thanks for sharing.
And thanks for leaving a demo to work with