DEV Community

Muhammad Saad Bin Nadeem
Muhammad Saad Bin Nadeem

Posted on

Beyond JWTs: Preventing API Replay Attacks in React Native & .NET (Financial-Grade Security)

When building mobile applications—especially in the fintech, maritime, or ERP sectors—most developers think security stops at JWT Authentication and HTTPS.

But what happens if a malicious user or a compromised network proxy intercepts a perfectly valid POST request? For example:
POST /api/wallet/transfer { "amount": 100, "to": "user_b" }

Even with a valid JWT, the attacker can capture this payload and "replay" (resend) it 100 times, draining the victim's wallet. Your backend will process it because the token is technically valid.

To prevent this, enterprise systems use HMAC (Hash-based Message Authentication Code) Signatures combined with strict Timestamps. Here is how to implement this end-to-end between React Native and .NET Core.

The Concept
The Client (React Native) takes the request body, adds the current timestamp, and hashes it using a secret key. It sends this hash in the headers.

The Server (.NET) receives the request. First, it checks if the timestamp is older than 60 seconds (if yes, it rejects it immediately to prevent old replays). Then, the server hashes the body and timestamp using the same secret key.

If the server's hash matches the client's hash, the request is untouched and fresh.

Step 1: The React Native Implementation (Axios)
First, install a crypto library: npm install crypto-js.
Then, we use an Axios Interceptor to automatically sign every outgoing POST, PUT, or PATCH request.

import axios from 'axios';
import CryptoJS from 'crypto-js';

const api = axios.create({ baseURL: 'https://api.yourdomain.com' });
const CLIENT_SECRET = process.env.EXPO_PUBLIC_HMAC_SECRET; // Must match the backend

api.interceptors.request.use((config) => {
  // Only sign requests that modify data
  if (['post', 'put', 'patch'].includes(config.method ?? '')) {
    const timestamp = Date.now().toString();
    const bodyString = config.data ? JSON.stringify(config.data) : '';

    // Create the signature payload: Timestamp + Body
    const payloadToSign = `${timestamp}:${bodyString}`;

    // Hash it using HMAC-SHA256
    const signature = CryptoJS.HmacSHA256(payloadToSign, CLIENT_SECRET).toString(CryptoJS.enc.Hex);

    // Attach to headers
    config.headers['X-Request-Timestamp'] = timestamp;
    config.headers['X-Request-Signature'] = signature;
  }
  return config;
});

export default api;
Enter fullscreen mode Exit fullscreen mode

Step 2: The .NET 9 Backend Implementation
Instead of polluting our controllers, we write a clean, reusable ActionFilterAttribute in C# to intercept requests before they even hit our endpoints.

Create a file called ValidateHmacSignatureAttribute.cs:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Security.Cryptography;
using System.Text;

public class ValidateHmacSignatureAttribute : ActionFilterAttribute
{
    private const int MaxAgeInSeconds = 60;

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var request = context.HttpContext.Request;

        // 1. Extract Headers
        if (!request.Headers.TryGetValue("X-Request-Timestamp", out var timestampValues) ||
            !request.Headers.TryGetValue("X-Request-Signature", out var signatureValues))
        {
            context.Result = new UnauthorizedObjectResult("Missing security headers.");
            return;
        }

        var timestampStr = timestampValues.ToString();
        var clientSignature = signatureValues.ToString();

        // 2. Prevent Replay Attacks via Timestamp Validation
        if (!long.TryParse(timestampStr, out long timestampMs))
        {
            context.Result = new BadRequestObjectResult("Invalid timestamp format.");
            return;
        }

        var requestTime = DateTimeOffset.FromUnixTimeMilliseconds(timestampMs);
        if ((DateTimeOffset.UtcNow - requestTime).TotalSeconds > MaxAgeInSeconds)
        {
            context.Result = new UnauthorizedObjectResult("Request expired. Replay attack detected.");
            return;
        }

        // 3. Rebuild the payload and validate signature
        request.EnableBuffering();
        using var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true);
        var body = await reader.ReadToEndAsync();
        request.Body.Position = 0; // Reset stream for the controller

        var payloadToSign = $"{timestampStr}:{body}";

        // In a real app, inject this secret via IConfiguration
        var serverSecret = "YOUR_SHARED_SECRET_KEY"; 

        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(serverSecret));
        var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(payloadToSign));
        var serverSignature = Convert.ToHexString(hashBytes).ToLower();

        // 4. Compare
        if (serverSignature != clientSignature.ToLower())
        {
            context.Result = new UnauthorizedObjectResult("Payload tampering detected. Invalid signature.");
            return;
        }

        await next();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Secure Your Endpoints
Now, to protect any financial transaction in your API, you simply add the attribute to the controller:

[HttpPost("transfer")]
[ValidateHmacSignature] // BOOM. Replay attacks are dead.
public IActionResult TransferFunds([FromBody] TransferDto request)
{
    // Process the transaction safely
    return Ok(new { Message = "Funds transferred securely." });
}
Enter fullscreen mode Exit fullscreen mode

Conclusion
By combining timestamps with HMAC signatures, we ensure two things:

The payload was not altered in transit.

The request is fresh and cannot be intercepted and replayed later.

If you are building fintech apps, ERP systems, or anything handling real money, this level of security is non-negotiable.

What other advanced security layers do you guys implement in your mobile architectures? Let's discuss in the comments!

(P.S. Make sure to follow my profile for more deep dives into enterprise-grade .NET and React Native architecture!)

Top comments (0)