How to Prevent CSRF Attacks in SaaS Apps: Complete Guide
OverMCP Team
TL;DR: How to Prevent CSRF Attacks in a SaaS App
To prevent CSRF attacks in your SaaS app, implement CSRF tokens for all state-changing requests, set cookies with SameSite=Strict or Lax, and validate the Origin or Referer header on your server. For APIs, require a custom header like X-Requested-By. These three layers stop attackers from tricking authenticated users into performing unintended actions.
What Is a CSRF Attack and Why Should SaaS Devs Care?
Cross-Site Request Forgery (CSRF) is an attack where a malicious website, email, or blog causes a user's browser to make an unwanted request to your SaaS app. If the user is authenticated, the attacker can change settings, delete data, or initiate financial transactions without consent.
For vibe-coded SaaS apps built with AI tools, CSRF is especially dangerous because:
Let's walk through exactly how to prevent CSRF attacks in your app.
How CSRF Attacks Work: The Core Mechanism
A CSRF attack exploits the fact that browsers automatically include cookies (including session cookies) in requests to the domain they belong to. Here's a typical flow:
Step 1: Implement CSRF Tokens (The Gold Standard)
A CSRF token is a unique, unpredictable value generated by the server and included in every state-changing request (POST, PUT, DELETE). The server validates the token before processing the request.
Server-Side Token Generation (Node.js/Express Example)
// Using csurf middleware (or implement your own)
import csrf from 'csurf';
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/process', (req, res) => {
// CSRF token automatically validated
res.send('Data processed');
});Including the Token in HTML Forms
<form action="/change-email" method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input type="email" name="newEmail">
<button type="submit">Change Email</button>
</form>For SPAs or APIs: Custom Header Token
// Frontend (React/Vue)
const response = await fetch('/api/change-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ newEmail })
});
// Backend validation
app.use((req, res, next) => {
const token = req.headers['x-csrf-token'];
if (!isValidToken(token)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
next();
});Step 2: Set SameSite Cookie Attribute
The SameSite cookie attribute tells the browser when to send cookies. This is a powerful defense that works even if CSRF tokens are forgotten.
// Setting session cookie with SameSite=Strict
res.cookie('sessionId', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'Strict' // or 'Lax'
});Secure flag). Only use if you need cross-site requests.Step 3: Validate Origin/Referer Headers
Check the Origin or Referer header to ensure the request comes from your own domain. This is a simple fallback.
app.use('/api', (req, res, next) => {
const origin = req.headers['origin'];
const referer = req.headers['referer'];
const allowedOrigins = ['https://yourapp.com'];
if (origin && !allowedOrigins.includes(origin)) {
return res.status(403).json({ error: 'Invalid origin' });
}
// Or check referer
if (referer && !referer.startsWith('https://yourapp.com')) {
return res.status(403).json({ error: 'Invalid referer' });
}
next();
});Step 4: Disable CORS for Untrusted Origins
CORS settings can inadvertently allow CSRF. Be explicit about which origins can access your API.
app.use(cors({
origin: 'https://yourapp.com', // Only allow your frontend
credentials: true
}));Never use a wildcard * with credentials.
Common Pitfalls to Avoid
Framework-Specific Defenses
Next.js (Pages Router)
Next.js has built-in CSRF protection via getServerSideProps and middleware. For API routes, use the csrf utility from next-csrf.
// pages/api/change-email.js
import { csrf } from 'next-csrf';
const csrfMiddleware = csrf();
export default async function handler(req, res) {
await csrfMiddleware(req, res);
// Your handler
}Django
Django includes CSRF middleware by default. Ensure it's enabled in MIDDLEWARE:
# settings.py
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
# ...
]And include {% csrf_token %} in all forms.
Express.js
Use the csurf middleware (or its successor csrf-csrf). Always pass the token to views.
Why Vibe-Coded Apps Are Vulnerable
AI coding tools often generate code that:
SameSite=None for third-party integrationsAccess-Control-Allow-Origin: *If you built your SaaS app with AI, you need to audit these areas. Tools like OverMCP can scan your codebase for missing CSRF protections and other security gaps, giving you a clear fix list.
FAQ
What is the most effective way to prevent CSRF attacks?
The most effective way is to use CSRF tokens combined with the SameSite cookie attribute. Tokens provide server-side validation, while SameSite prevents browsers from sending cookies cross-origin. Together, they block nearly all CSRF vectors.
Can CSRF happen over HTTPS?
Yes. HTTPS only encrypts data in transit; it doesn't prevent CSRF. The attack exploits browser cookie behavior, not network eavesdropping. Always use CSRF protection regardless of HTTPS.
Do I need CSRF protection for a REST API used by a mobile app?
No, if your API is only consumed by a mobile app, CSRF tokens aren't needed because mobile apps don't automatically include cookies. However, if a web frontend also uses the same API, you need CSRF protection for that web client.