When developing web applications, securing requests between the frontend and backend is one of the key tasks.
Ignoring this aspect can lead to serious consequences: data leaks, duplicated operations, incorrect financial transactions, and even service outages. In this article, we will examine the main methods of securing requests and take a detailed look at such an important mechanism as idempotency and how to implement an idempotency key in web applications.
Before diving into idempotency, let's review the basic methods of securing requests:
These measures create a baseline level of security, but they do not protect against request duplication.
For that, idempotency is used.
Let's take a closer look at what that means.
Idempotency is the property of an operation where performing it multiple times produces the same result as performing it once.
Example:
If a client sends two identical requests to withdraw funds, the funds should only be withdrawn once.
If a client accidentally creates two identical orders, the server should create only one order.
To implement idempotency, the client must send a special header Idempotency-Key
— a unique operation identifier — with the request. The server checks this key and decides whether to process the request or not.
To implement idempotency, the following principles must be followed:
let’s go deeper!
The idempotency key should be generated on the client side because only the client knows which requests might be duplicates.
The key should meet the following requirements:
const idempotencyKey = crypto.randomUUID();
const data = JSON.stringify({ userId: 123, amount: 500 });
const idempotencyKey = crypto.createHash('sha256').update(data).digest('hex');.
const idempotencyKey = `${Date.now()}-${Math.random().toString(36).substring(2)}`;
After the client sends a request with the Idempotency-Key
header, the server performs the following actions:
key := r.Header.Get("Idempotency-Key")
if key == "" {
http.Error(w, "Idempotency-Key required", http.StatusBadRequest)
return
}
If the key exists — returns the stored result.
If the key does not exist — processes the request.
var idempotencyCache = sync.Map{}
var cacheTTL = 24 * time.Hour
func WithIdempotency(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("Idempotency-Key")
if key == "" {
http.Error(w, "Idempotency-Key required", http.StatusBadRequest)
return
}
if cached, ok := idempotencyCache.Load(key); ok {
w.WriteHeader(http.StatusOK)
w.Write(cached.([]byte))
return
}
// your logic
if recorder.statusCode >= 200 && recorder.statusCode < 300 {
idempotencyCache.Store(key, recorder.body)
time.AfterFunc(cacheTTL, func() {
idempotencyCache.Delete(key)
})
}
})
}
Idempotency allows you to avoid request duplication and make the system reliable in the event of network failures and repeated calls. Implementing it via middleware in Go simplifies request processing and increases system resilience.
However, you need to account for possible mistakes during implementation:
Idempotency is a simple yet powerful tool for preventing duplicate operations.
Start using it in all unsafe HTTP methods (POST, PUT, PATCH) — and you will make your API more stable and predictable.
Take care!