Cross Site Request Forgery in Grape with Firestore
Cross Site Request Forgery in Grape with Firestore — how this specific combination creates or exposes the vulnerability
Cross Site Request Forgery (CSRF) occurs when an attacker tricks a victim’s browser into making an unwanted authenticated request to a vulnerable application. When using Grape with a Firestore backend, CSRF risk arises because client-side sessions or tokens (such as ID tokens from Firebase Authentication) can be automatically included by the browser, while server-side Grape endpoints may rely on those credentials to perform Firestore operations without additional anti-CSRF controls.
In a typical setup, a frontend authenticates via Firebase Authentication, receives an ID token, and includes it in requests (e.g., via Authorization: Bearer or custom headers). If a Grape API endpoint performs sensitive Firestore writes—such as updating user settings or creating records—and relies solely on the presence of a valid token without verifying the request origin, an attacker can craft a malicious site that causes the victim’s browser to issue those writes unintentionally. Because Firestore security rules are enforced per-request and do not inherently distinguish between same-site user-initiated requests and forged ones, the server-side Grape logic must enforce CSRF defenses to protect state-changing operations.
Grape does not include built-in CSRF middleware like some server frameworks; therefore developers must explicitly add protections. For endpoints that mutate Firestore data, missing or incomplete CSRF mitigations can lead to unauthorized updates, record creation, or deletions under the authenticated user’s identity. Attack vectors include embedding images or forms on attacker-controlled domains that submit POST requests to your Grape endpoints, or using JavaScript to trigger authenticated requests via XMLHttpRequest or fetch if credentials are permitted cross-origin.
To secure Grape + Firestore integrations, combine same-site cookie attributes, anti-CSRF tokens or custom request verification, and strict CORS policies. Firestore security rules should be designed to be least-privilege and not solely relied upon for CSRF prevention, because rules validate data but do not block unauthorized requests initiated from the browser. Server-side checks in Grape should verify the request’s origin and, where applicable, validate anti-CSRF tokens before committing writes to Firestore.
Concrete Firestore operations in Grape might include updating a user profile document or creating a transaction record. If these endpoints are exposed without CSRF considerations, an attacker could cause the victim’s browser to submit crafted payloads that modify Firestore data. Using the official Google Cloud Firestore client for Ruby, a vulnerable Grape route might look like a write that only checks authentication but not request origin, thereby exposing the integration to CSRF.
Firestore-Specific Remediation in Grape — concrete code fixes
Remediation focuses on ensuring that only intended, same-site or authorized requests can trigger Firestore writes from Grape. Below are concrete patterns and code examples for Grape endpoints that interact with Firestore.
First, configure your frontend and backend to use same-site cookies and CORS restrictions. For Firebase, ensure that authentication state is not automatically sent cross-origin unless intended. Then, implement anti-CSRF tokens or origin checks in your Grape API.
Example 1: Verify origin and required custom header on a Grape resource handling Firestore writes.
class API::V1::Profiles < Grape::API
format :json
before do
# Require a custom header to indicate same-site origin or token-based request
header_origin = env['HTTP_ORIGIN']
allowed_origin = 'https://your-app.example.com'
unless header_origin == allowed_origin
error!('Forbidden', 403)
end
# Require a custom anti-CSRF token in headers (e.g., X-CSRF-Token)
provided_token = env['HTTP_X_CSRF_TOKEN']
expected_token = env['rack.session']&.[]('csrf_token') # store this at login
unless provided_token&.casecmp(expected_token).to_i == 0
error!('Invalid CSRF token', 403)
end
end
desc 'Update user profile in Firestore'
params do
requires :display_name, type: String, desc: 'Profile display name'
end
put '/profile' do
# Assume authenticated user ID is available from Firebase token validation
user_id = current_user_id_from_token(env)
firestore = Google::Cloud::Firestore.new
doc_ref = firestore.doc("users/#{user_id}")
doc_ref.update({ display_name: params[:display_name] })
{ status: 'updated' }
end
end
Example 2: Use anti-CSRF token generation and validation with session storage in a Rack-compatible setup used by Grape.
# On login, set a CSRF token in the session
post '/login_with_firebase' do
# After verifying Firebase ID token and establishing session
session = Rack::Request.new(env).session
session['csrf_token'] = SecureRandom.urlsafe_base64(32)
{ csrf_token: session['csrf_token'] }
end
# In a before block, validate the token for state-changing methods
class API::V1::Transactions < Grape::API
format :json
before do
session = Rack::Request.new(env).session
unless session&.[]('csrf_token')&.casecmp(env['HTTP_X_CSRF_TOKEN']) == 0
error!('Forbidden', 403)
end
end
desc 'Create a transaction in Firestore'
params do
requires :amount, type: Integer
requires :currency, type: String
end
post '/transaction' do
user_id = current_user_id_from_token(env)
firestore = Google::Cloud::Firestore.new
transaction_ref = firestore.doc("users/#{user_id}/transactions/#{SecureRandom.uuid}")
transaction_ref.set({
amount: params[:amount],
currency: params[:currency],
created_at: Time.now.utc
})
{ status: 'created' }
end
end
Example 3: Tighten Firestore security rules to complement server-side checks, ensuring that each document write is scoped to the authenticated user and does not rely on client-supplied document IDs for authorization alone.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
// For subcollections, enforce ownership explicitly
match /transactions/{transactionId} {
allow create: if request.auth != null && request.auth.uid == userId;
allow update, delete: if request.auth != null && request.auth.uid == userId && request.resource.data.userId == userId;
}
}
}
}
Additional measures include setting SameSite=Lax or Strict on cookies, using CSRF tokens for any non-GET requests, and validating the Origin header where appropriate. These steps reduce the risk that a forged request from a malicious site will successfully execute privileged Firestore operations via your Grape API.