Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool
Preventing JSON CSRF Attacks
Cross-Site Request Forgery (CSRF) is a type of malicious exploit where unauthorized commands are transmitted from a user that the web application trusts. While often associated with traditional HTML form submissions that trigger state-changing actions via GET or POST requests with URL-encoded or multipart bodies, APIs that accept JSON payloads are also vulnerable, especially if not properly designed and secured. This page explores the nature of JSON CSRF and how to defend against it.
Understanding JSON CSRF
A JSON CSRF attack occurs when a malicious website tricks a user's browser into making an unintended request to a web application where the user is currently authenticated. If the application's API endpoint accepts a JSON payload and relies solely on session cookies (which browsers automatically attach to requests for the target domain), the malicious request can be indistinguishable from a legitimate one from the server's perspective, leading to unauthorized actions.
Traditionally, CSRF attacks leveraged HTML <form>
elements or image tags (<img>
) to trigger simple GET/POST requests with limited content types (application/x-www-form-urlencoded
or multipart/form-data
). However, modern APIs frequently use JSON (application/json
), which cannot be sent via simple GET/POST HTML forms without JavaScript. This requires the attacker to use JavaScript on the malicious page.
How a JSON CSRF Attack Might Work:
- A user is logged into an application (e.g.,
bank.com
). Their browser has a session cookie forbank.com
. - The user visits a malicious website (e.g.,
attacker.com
). - The malicious website contains JavaScript that creates and submits a request to a sensitive endpoint on
bank.com
(e.g.,POST /api/transfer-funds
) usingXMLHttpRequest
orFetch API
. - The JavaScript crafts a request with a JSON body, for example:
fetch('https://bank.com/api/transfer-funds', { method: 'POST', headers: { 'Content-Type': 'application/json', // Browser automatically attaches cookies for bank.com }, body: JSON.stringify({ recipient: 'attacker_account', amount: 1000 }) });
- The browser automatically includes the session cookie for
bank.com
with this request. - If
bank.com
relies only on the cookie for authentication and doesn't validate the request source or origin, it will process the request as if it came from the legitimate user.
Prevention Strategies
Defending against JSON CSRF requires measures beyond just relying on cookies. Here are several strategies, often used in combination, to protect your endpoints:
1. Check the Content-Type
Header
While older CSRF attacks were limited to simple content types, JavaScript-based requests can set arbitrary headers, including Content-Type: application/json
. However, many web frameworks and server-side libraries by default only parse the request body as JSON if this specific header is present.
Requests triggered by simple HTML forms (without JavaScript) typically sendapplication/x-www-form-urlencoded
or multipart/form-data
. A server configured to only accept JSON payloads when the Content-Type
is explicitly application/json
can reject form-based CSRF attempts. This doesn't stop JavaScript-based JSON CSRF, but it's a basic step.
Note: Some older or non-standard browser behaviors might still allow sendingapplication/json
with simpler methods, but this is less common now.
2. Check the Origin
or Referer
Header
Modern browsers include the Origin
header in cross-origin requests (including those made via Fetch or XMLHttpRequest). This header indicates the origin (scheme, hostname, port) of the page that initiated the request. Similarly, the Referer
header (though can be controlled or suppressed by the user/browser settings) might also contain the origin.
Your server can check these headers to ensure the request originated from your legitimate domain(s). If the Origin
or Referer
does not match your expected origin, the request should be rejected.
Conceptual Server-Side Check:
// Example in a Node.js/Express-like framework function checkOrigin(req, res, next) { const origin = req.headers['origin']; // Or req.headers['referer'] const allowedOrigins = ['https://your-app.com', 'https://subdomain.your-app.com']; // Configure your allowed origins if (!origin || allowedOrigins.includes(origin)) { // Request is from a trusted origin or a same-origin request (origin might be null/undefined) next(); } else { // Request is from an untrusted origin res.status(403).send('CSRF detected: Invalid Origin header'); } }
Caution: The Referer
header is not always reliable due to privacy settings or proxies. The Origin
header is generally preferred for this purpose for modern browsers.
3. Use CSRF Tokens (Synchronizer Token Pattern)
This is a widely used and effective defense. It involves:
- When a user requests a page (e.g., a form or a page making API calls), the server embeds a unique, randomly generated token within the page content.
- This same token is also stored server-side, typically in the user's session.
- When the user's browser makes a state-changing request (e.g., a POST, PUT, DELETE request), the token is sent back to the server, either in a hidden form field (for HTML forms), a custom HTTP header (common for AJAX/JSON requests), or as part of the request body/query parameters.
- The server verifies that the token received in the request matches the token stored in the user's session. If they don't match, the request is rejected.
For JSON APIs consumed by frontends using Fetch or XMLHttpRequest, sending the token in a custom HTTP header (like X-CSRF-Token
or X-Requested-With
) is a standard approach. Browsers enforce the Same-Origin Policy for custom headers, meaning malicious cross-origin JavaScript cannot add arbitrary custom headers to a request without a preflight CORS request (which the server can then deny).
Conceptual Frontend (JavaScript) and Server-Side Snippets:
Frontend (after obtaining the token, e.g., from a meta tag or initial API call):
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); fetch('/api/sensitive-action', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken, // Send token in custom header }, body: JSON.stringify({ data: '...' }) });
Server-Side (Verification):
// Example in a Node.js/Express-like framework function verifyCsrfToken(req, res, next) { const clientToken = req.headers['x-csrf-token']; // Get token from header const serverToken = req.session.csrfToken; // Get token from session if (clientToken && serverToken && clientToken === serverToken) { // Token is valid next(); } else { // Invalid or missing token res.status(403).send('CSRF token verification failed'); } } // In your route definition: // app.post('/api/sensitive-action', verifyCsrfToken, handleSensitiveAction);
4. Use SameSite Cookies
The SameSite
attribute on cookies is a powerful defense. It tells the browser whether to send cookies with cross-site requests.
SameSite=Lax
(default in modern browsers): Cookies are sent with top-level navigations (like clicking a link) but not with other cross-site requests (like AJAX calls, images, or iframes) unless it's a GET request for top-level navigation. This significantly mitigates CSRF for state-changing methods (POST, PUT, DELETE).SameSite=Strict
: Cookies are only sent with same-site requests (when the site in the address bar matches the site of the cookie). This provides the strongest protection but can break legitimate cross-site links (e.g., linking from an external site).SameSite=None; Secure
: Cookies are sent with all requests, including cross-site ones. Requires theSecure
attribute (HTTPS). Use this only for specific cross-site needs (e.g., third-party cookies, embedded content that needs authentication) and ensure *other* CSRF defenses are in place.
Setting your session cookies with at least SameSite=Lax
dramatically reduces the risk of CSRF, as the session cookie needed for authentication won't be sent with typical cross-site AJAX/Fetch requests initiated by an attacker's script.
5. Defend Against JSON Array/Object Literal Execution (Older Vulnerability)
In older browsers or specific contexts, if a JSON API endpoint returned sensitive data as a top-level array ([...]
) or object ({...}
), a malicious page could potentially include the API endpoint URL as a <script>
tag'ssrc
. The browser might execute the response as JavaScript.
If the sensitive data was returned as a simple JSON array (e.g., [{...}, {...}]
), this response was also a valid JavaScript array literal. In some scenarios (especially pre-ES5 browsers or specific execution contexts like overriding Array
constructors), the malicious page could potentially read the values of this array. Similarly, if it was a simple object literal ({...}
), it could potentially be assigned to a variable if the response was wrapped in parentheses.
To mitigate this, sensitive JSON API responses should *always* be wrapped in a top-level object that is not a valid JavaScript literal (e.g., { "data": [...] }
or prefixing the response with an unexecutable string like while(1);{ ... }
). Modern browsers are less susceptible to this specific vector, but it's a good practice for defense in depth, especially if supporting older clients.
6. Use Appropriate HTTP Methods
Ensure that state-changing operations (like creating, updating, deleting resources) are handled by HTTP methods other than GET (e.g., POST, PUT, DELETE). GET requests should ideally be idempotent and not have side effects. While this doesn't *prevent* JavaScript-based CSRF using other methods, it prevents CSRF via simple GET requests triggered by <img>
or link clicks.
Defense in Depth
No single CSRF prevention technique is foolproof in isolation across all possible scenarios and browser behaviors (especially historical ones). The most robust defense involves combining multiple layers:
- Use
SameSite=Lax
(or Strict) for session cookies. - Implement CSRF tokens for all state-changing API requests.
- Validate the
Origin
header for sensitive endpoints. - Ensure sensitive data responses are not parseable as executable JavaScript if fetched via
<script>
tag (wrap in object, prefix). - Use appropriate HTTP methods (POST/PUT/DELETE for state changes).
By implementing a combination of these measures, you significantly raise the bar for attackers attempting JSON CSRF, making your application much more secure.
Conclusion
JSON APIs are a common target for CSRF attacks when authentication relies solely on cookies and no other defenses are in place. Developers must be proactive in designing their APIs to include CSRF protection. Leveraging features like SameSite
cookies, validating request origins, and implementing CSRF tokens are essential steps to safeguard user data and application integrity against this prevalent web vulnerability.
Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool