Understanding of Session and CSRF Handling in Odoo


✅ Overview

When exposing Odoo to the web — especially for portal users, unauthenticated users, or public APIs — managing sessions and CSRF (Cross-Site Request Forgery) is critical for security and reliability.


📌 Key Concepts


Term

Meaning

Session

A user’s persistent identity across HTTP requests in Odoo

CSRF Token

A hidden token Odoo uses to prevent forged POST requests

Portal

Public-facing access for partners or customers with limited permissions

Public API

An endpoint exposed to external apps/systems without login



🔄 Session Management in Portal


Odoo Automatically Uses Sessions in Web Controllers

When a portal user logs in via /web/login, a session is created and stored in a session cookie.

Cookie: session_id=abcdef12345

This allows Odoo to:

  • Authenticate user requests
  • Preserve state (cart, quote, etc.)
  • Access request.env.user, request.session, etc.


🔧 Example: Accessing Portal Data via Session​


from odoo import http

from odoo.http import request


class PortalController(http.Controller):


    @http.route('/my/orders', auth='user', website=True)

    def portal_orders(self):

        partner = request.env.user.partner_id

        orders = request.env['sale.order'].search([('partner_id', '=', partner.id)])

        return request.render('my_module.portal_orders_template', {'orders': orders})


  • Works because the portal user is authenticated through a session.
  • auth='user' ensures only logged-in users can access it.


🛡 CSRF Protection in Odoo

By default, all routes with type='http' and auth='user' or auth='public' have CSRF protection enabled.


🔥 Problem Scenario (API Fails With 403 Error)

You call a public POST route like:

curl -X POST https://your-odoo.com/api/submit -d '{"data": "value"}'

If CSRF is enabled and you didn’t provide a CSRF token — Odoo will block the request.



🔧 Disabling CSRF for API Endpoints


For trusted external integrations, disable CSRF token requirement by adding:

@http.route('/api/submit', type='json', auth='public', 

methods=['POST'], csrf=False)

def api_submit(self, **kwargs):

    # Your logic here

  • csrf=False disables the CSRF check (⚠️ use cautiously)
  • Recommended only if:
    • Auth is handled via token or header
    • Endpoint is secured via HTTPS


🔐 Secure API Pattern (Without Session)


@http.route('/api/secure-data', type='json', auth='none', methods=['POST'], csrf=False)

def secure_api(self, **kwargs):

    token = kwargs.get('token')

    if token != 'expected-api-key':

        return {'error': 'Unauthorized'}

    

    # Your logic here

    return {'data': 'OK'}



✅ Summary Table


Feature

Session-Based Portal

Stateless API Endpoint

Auth Mechanism

session_id (cookie)

Token-based (in headers or body)

CSRF Enabled by Default

✅ Yes

✅ Yes (disable with csrf=False)

Use request.env.user

✅ Yes

❌ No (use sudo / token auth)

Good For

Web views, My Account pages

Mobile apps, 3rd-party integrations

auth='public'

Access without login

Useful for open endpoints

auth='user'

Requires login

Portal or internal usage


🧠 Best Practices


  • Use csrf=False only for stateless API integrations with token security.
  • Never expose session-specific logic over auth='public' without token-based checks.
  • Always use HTTPS to protect session and token data.
  • For portals, rely on session-based access to ensure login-required content is protected.