Skip to content

Authentication

Login flow and session management for the PWA.

Overview

The PWA uses session-based authentication against Odoo's authentication system. Users log in with their Odoo credentials, and the session is maintained in Flask with a 24-hour lifetime.

Login Flow

sequenceDiagram
    participant User
    participant PWA as PWA (Flask)
    participant Odoo

    User->>PWA: GET /login
    PWA-->>User: Login page

    User->>PWA: POST /login (username, password)
    PWA->>Odoo: POST /web/session/authenticate
    Note over Odoo: Validates credentials

    alt Valid credentials
        Odoo-->>PWA: {uid, username, name, session_id}
        PWA->>Odoo: GET user partner_id
        Odoo-->>PWA: {partner_id}
        PWA->>PWA: Store in session
        PWA-->>User: Redirect to /jobs/active
    else Invalid credentials
        Odoo-->>PWA: {uid: false}
        PWA-->>User: "Invalid username or password"
    end

Routes

Login

GET /login

Displays the login form. Redirects to /jobs/active if already logged in.

POST /login

Parameter Type Required Description
username string Yes Odoo username/email
password string Yes Odoo password
remember checkbox No Keep session for 24 hours

Logout

GET /logout

Clears the session and redirects to login page.

Session Data

After successful login, the following data is stored in the Flask session:

Key Type Description
user_id int Odoo user ID (res.users.id)
username string Login username
user_name string User's display name
partner_id int Associated partner ID for activity attribution

Session Configuration

class Config:
    # Session lifetime (24 hours with "Remember me")
    PERMANENT_SESSION_LIFETIME = timedelta(hours=24)

    # Security settings
    SESSION_COOKIE_SECURE = True    # HTTPS only in production
    SESSION_COOKIE_HTTPONLY = True  # Not accessible via JavaScript
    SESSION_COOKIE_SAMESITE = 'Lax' # CSRF protection

Login Required Decorator

Protected routes use the @login_required decorator:

from functools import wraps
from flask import session, redirect, url_for, flash

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('Please log in to access this page.', 'warning')
            return redirect(url_for('auth.login'))
        return f(*args, **kwargs)
    return decorated_function

Usage:

@bp.route('/jobs/active')
@login_required
def active():
    # Only accessible when logged in
    ...

Odoo Authentication API

The PWA authenticates against Odoo's session endpoint:

def authenticate_user(self, username: str, password: str) -> dict:
    url = f"{self.base_url}/web/session/authenticate"
    payload = {
        "jsonrpc": "2.0",
        "method": "call",
        "params": {
            "db": self.db,
            "login": username,
            "password": password
        },
        "id": 1
    }

    response = requests.post(url, json=payload, timeout=10)
    result = response.json()

    if result.get('error') or not result.get('result', {}).get('uid'):
        raise Exception('Invalid username or password')

    return {
        'uid': result['result']['uid'],
        'username': result['result'].get('username', username),
        'name': result['result'].get('name', username),
        'session_id': result['result'].get('session_id')
    }

Partner ID for Activity Attribution

After login, the PWA fetches the user's partner_id to properly attribute activities:

def get_user_partner_id(self, user_id: int) -> int:
    result = self._jsonrpc_call('res.users', 'search_read',
        [[('id', '=', user_id)]],
        {'fields': ['partner_id'], 'limit': 1}
    )
    if result and result[0].get('partner_id'):
        return result[0]['partner_id'][0]
    return None

This partner_id is used when:

  • Posting comments to activity logs
  • Changing stages (FSM, CRM)
  • Recording who performed actions

Security Considerations

CSRF Protection

All forms include CSRF tokens via Flask-WTF:

<form method="POST">
    {{ csrf_token() }}
    ...
</form>

Password Handling

  • Passwords are never stored in the PWA
  • Passwords are transmitted over HTTPS only
  • Session tokens are HTTP-only cookies

Session Security

  • Sessions expire after 24 hours (with "Remember me")
  • Sessions are cleared on logout
  • Cookie is marked Secure in production

Troubleshooting

"Please log in to access this page"

Session has expired or was cleared. Log in again.

"Invalid username or password"

  1. Verify credentials work in Odoo directly
  2. Check ODOO_BASE_URL and ODOO_DB in .env
  3. Ensure user account is active in Odoo

Session Not Persisting

  1. Check SECRET_KEY is set and stable
  2. Verify cookies are enabled in browser
  3. Check for HTTPS issues (Secure cookie flag)

Example Usage

Accessing User Info in Templates

{% if session.user_name %}
    <span>Welcome, {{ session.user_name }}</span>
{% endif %}

Accessing User Info in Routes

@bp.route('/jobs/active')
@login_required
def active():
    user_id = session.get('user_id')
    partner_id = session.get('partner_id')

    # Filter jobs by user or use partner_id for attribution
    jobs = odoo_api.get_active_jobs(user_id)
    ...