Introduction
When building web applications in Odoo, security is not optional—it’s foundational. One of the most critical protections you need to understand is CSRF (Cross-Site Request Forgery). If you’re working with controllers, forms, or APIs in Odoo 18, this is especially important.
What is CSRF?
Cross-Site Request Forgery (CSRF) is an attack where a malicious website tricks a user’s browser into sending unauthorized requests to another site where the user is authenticated.
Example:
- User logs into your Odoo system.
- Without logging out, they visit a malicious site.
- That site silently triggers a request like:
POST /odoo/create_invoice
- Since the browser still has session cookies, Odoo thinks it’s a valid request.
Result: Unauthorized actions performed without user consent.
How Odoo Handles CSRF
Odoo has built-in CSRF protection in its HTTP layer.
Enabled by default for:
- type=’http’ routes
- POST requests
Works using:
- A CSRF token stored in session
- Token must be sent with every unsafe request (POST/PUT/DELETE)
CSRF in Odoo Controllers
1. Default Behavior
from odoo import http
from odoo.http import request
class MyController(http.Controller):
@http.route(‘/my/form/submit’, type=’http’, auth=’user’, methods=[‘POST’])
def submit_form(self, **post):
return “Success”
This route is CSRF protected by default
2. Including CSRF Token in Forms
In QWeb templates:
<form action=”/my/form/submit” method=”post”>
<input type=”hidden” name=”csrf_token” t-att-value=”request.csrf_token()”/>
<input type=”text” name=”name”/>
<button type=”submit”>Submit</button>
</form>
This is mandatory for POST forms.
3. What Happens If Token is Missing?
Odoo will raise:
403 Forbidden: CSRF validation failed
Disabling CSRF (Use Carefully!)
Sometimes you must disable CSRF, for example:
- External API/webhook endpoints
- Third-party integrations
@http.route(‘/api/webhook’, type=’json’, auth=’public’, csrf=False)
def webhook(self, **data):
return {“status”: “ok”}
⚠️ Warning: Disabling CSRF opens security risks.
Best Practices When csrf=False
If you disable CSRF, you MUST secure the endpoint manually:
1. Use Authentication Tokens
if request.httprequest.headers.get(‘Authorization’) != ‘Bearer SECRET_KEY’:
return “Unauthorized”
2. Restrict IPs (if possible)
3. Validate Payload Strictly
4. Use type=’json’ instead of http for APIs
CSRF in JSON Routes
@http.route(‘/api/data’, type=’json’, auth=’user’)
def get_data(self):
return {“data”: “secure”}
JSON routes:
- Usually used with JS (OWL, RPC)
- CSRF handled automatically via session
Internal Working (Advanced Insight)
- Token generated per session
- Stored server-side
- Compared with incoming request
- Checked only for unsafe methods (POST, PUT, DELETE)
Debugging CSRF Issues
Common Problems: