A modern way to enhance API security beyond authentication🔒🚀
In the ever-evolving world of cybersecurity, authentication and authorization are the backbone of most security systems. However, relying solely on these mechanisms without addressing CSRF (cross-site request forgery) or brute force risks could leave your API exposed. Malicious actors can use tools such as Postman, CURL, or custom scripts to inspect web calls, impersonate headers, and perform requests outside the browser.
Sounds shocking, right? Even top platforms like X (formerly Twitter), Facebook, TikTok, and Instagram are not immune to this problem. So, how do we fight back? Introducing advanced CSRF protection using RSA encryption– A lightweight but effective way to prevent unauthorized API usage, brute force attempts and stale requests.
This technology adds an extra layer of security on top of existing authentication systems. By using request metadata such as RSA encryption method, URL and timestamp, we ensure that each API request is uniquely authenticated and cannot be replayed or reused.
How does it work? 🛠️
we use RSA asymmetric encryption Verify the integrity of API requests. This is the process:
- generate a RSA key pair (public and private keys).
- this public key Stored on the client (preferably in an environment variable).
- Before making an API request, we encrypt the payload containing:
- Request method
- Request URL
- current timestamp
- This encrypted payload will be sent as a custom
csrf
header. - On the backend:
- The payload is decrypted using RSA private key.
- Validation ensures:
- this method and URL match.
- this Timestamp Not stale (for example, more than 15 seconds).
- If the verification passes, continue the request; otherwise, it will be rejected.
This approach ensures that each API request is tightly bound to specific parameters, preventing replay attacks, stale requests, and brute force abuse.
Client implementation (React + Axios) ⚛️
To simplify CSRF handling and ensure it satisfies all our requests, we will use axios interceptor. This is the setting 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;
Backend implementation (Node.js + Express) 🛡️
On the server, we will use the RSA private key to verify the incoming CSRF token and ensure the integrity of the request.
CSRF verification middleware
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;
}
}
Main features 🎯
- Asymmetric encryption: Only the server knows the private key, ensuring that the token cannot be forged.
- Request freshness: The timestamp limits the validity of the token to 15 seconds, preventing replay attacks.
- Method and URL validation: Ensure tokens are bound to specific operations and prevent brute force scripts.
I implemented this approach in one of my projects, Wentz Socialenhance the security of API interactions and prevent unauthorized or stale requests. Please feel free to check it out!
Demo and source code 📂
GitHub repository: https://github.com/Nedum84/csrf-with-rsa
Live demonstration: https://nedum84.github.io/csrf-with-rsa
Conclusion 📝
This approach enhances the security of the API in addition to the existing authentication and authorization mechanisms. By introducing encrypted timestamped CSRF tokens, you can prevent malicious actors from abusing your API outside the browser.
Try it out and improve your app security! 🚀
Let’s get in touch 🌐
What do you think? 💭
Share your thoughts! You can improve the code to suit your use case. Was this article helpful?
Have thoughts or feedback you’d like to share? Let’s continue the conversation in the comments below! 😊