Cross Site Request Forgery in Gin with Firestore
Cross Site Request Forgery in Gin with Firestore — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) in a Gin application that uses Firestore arises when an authenticated Firestore-backed endpoint does not enforce anti-CSRF protections, allowing an attacker to trick a logged-in user’s browser into issuing unintended authenticated requests. Gin does not provide built-in CSRF middleware, so developers must explicitly add protections. When session or authentication state is managed via cookies (for example a session cookie or an ID token stored in a cookie) and Firestore APIs are called on the server using service account credentials, the server-side Firestore calls will execute with the server identity, not the end-user identity. This separation can mask missing user-action validation on sensitive Firestore write operations, such as updating a user document or creating a document in a shared collection.
Consider a Gin route that accepts a POST to update a user profile and writes directly to a Firestore collection without verifying the request origin or including a CSRF token. Because the route relies on cookies for authentication and does not validate a same-site token or a request header, an attacker can craft a malicious site with a form that submits to this endpoint. When the victim’s browser sends the request with the session cookie, Gin processes it, authenticates via cookie, and executes Firestore writes under the victim’s permissions. If the Firestore security rules rely only on authentication and not on verifying that the request was intentionally made by the user (for example by checking custom claims or requiring a one-time token), the operation succeeds, leading to unauthorized updates.
A concrete scenario: a user is authenticated to a Gin app with a session stored in a cookie. The app exposes /profile (POST) which calls Firestore to update user data. The route uses the standard Firestore Go SDK on the server, authorizing with Application Default Credentials. Because the route does not validate a CSRF token, an attacker can host a page containing:
<form action="https://api.example.com/profile" method="POST">
<input type="hidden" name="displayName" value="Hacked" />
<input type="submit" value="Click me" />
</form>
If the victim visits the attacker’s page while authenticated to the Gin app, the browser sends the session cookie and the Firestore update executes with the victim’s permissions. Because Gin handled authentication via cookies and Firestore was called server-side without validating the request source, there is no effective CSRF defense at either layer. This highlights the need for explicit CSRF mitigation in Gin-Firestore applications, especially for state-changing operations that modify Firestore documents.
Firestore-Specific Remediation in Gin — concrete code fixes
Remediation centers on ensuring that every state-changing request to a Gin endpoint that results in Firestore writes includes verifiable evidence that the request was intentionally made by the authenticated user. The most practical approach in a cookie-based authentication setup is synchronizer token pattern (CSRF tokens) combined with strict same-site cookie policies and, where appropriate, origin/referrer checks.
1. Use CSRF tokens with Gin sessions. Store a cryptographically random token in the user’s session (server-side store) and also set it in a separate, httpOnly cookie (or in a header if using SPA/fetch). For each state-changing request, require that the request contain the token either in a header (e.g., X-CSRF-Token) or in the form body, and compare it to the session token before calling Firestore.
Example token generation and validation in Gin:
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Generate and store token
func generateCSRFToken(c *gin.Context) {
token := uuid.NewString()
// Store in server-side session; for simplicity, using a map here
sessionStore[c.GetCookie("session_id")] = token
c.SetCookie("csrf_token", token, 3600, "/", "example.com", true, true)
}
// Validate token
func validateCSRFToken(c *gin.Context) bool {
sessionID, err := c.Cookie("session_id")
if err != nil {
return false
}
expected, ok := sessionStore[sessionID]
if !ok {
return false
}
provided := c.GetHeader("X-CSRF-Token")
return provided == expected
}
Then protect routes:
func updateProfile(c *gin.Context) {
if !validateCSRFToken(c) {
c.AbortWithStatusJSON(403, gin.H{"error": "invalid csrf token"})
return
}
var req struct {
DisplayName string `json:"displayName"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": "invalid payload"})
return
}
// Firestore write using the server's service account
ctx := context.Background()
client, err := firestore.NewClient(ctx, "your-project-id")
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "firestore init failed"})
return
}
defer client.Close()
_, err = client.Collection("users").Doc(req.UserID).Update(ctx, []firestore.Update{
{Path: "displayName", Value: req.DisplayName},
})
if err != nil {
c.AbortWithStatusJSON(500, gin.H{"error": "update failed"})
return
}
c.JSON(200, gin.H{"status": "ok"})
}
2. Set secure cookie attributes. Ensure session and CSRF cookies use SameSite=Strict or Lax where appropriate, Secure, and HttpOnly. This reduces the likelihood that cookies are included in cross-origin requests initiated by malicious sites.
3. Combine with CORS and origin checks. For endpoints that accept AJAX requests from known origins, configure CORS strictly and validate the Origin/Referer headers as an additional layer. Note that CORS is a browser-enforced mechanism and should not be relied upon as the sole CSRF defense for sensitive operations.
4. Firestore rules as a secondary signal. While Firestore security rules cannot stop CSRF (because they validate authentication, not request intent), structure rules to be least-privilege and consider including user identity (UID) in document paths so that server-side Firestore calls can double-check that the target document belongs to the authenticated UID, adding defense-in-depth if an attacker somehow bypasses the server-side CSRF check.
By implementing synchronizer token CSRF protection in Gin and carefully controlling Firestore writes, you prevent unauthorized commands from being executed on behalf of authenticated users, even when an attacker can induce the user’s browser to make requests.