Enable Dark Mode!
how-to-add-a-field-to-the-user-login-page-in-odoo-18.jpg
By: Sruthi C Nair

How to Add a Field to the User Login Page in Odoo 18

Technical Odoo 18 Odoo Enterprises Odoo Community

Odoo, a popular open-source ERP and business management platform, offers extensive customization options to meet diverse business requirements. In this blog post, we'll walk through the process of adding a custom field to the Odoo user login screen. Enhancing the default login functionality allows businesses to collect additional user information during login, improve user management, and strengthen security. By following the steps outlined in this guide, you'll gain the knowledge and skills needed to effectively modify the login page. Let’s dive into how you can enhance your user login experience by introducing a custom field.

Default Odoo Login Page

The default Odoo 18 login page requires only a username (or email) and password.

How to Add a Field to the User Login Page in Odoo 18-cybrosys

An OTP (One-Time Password) field can now be added to the user login page, significantly enhancing security and user authentication. This feature allows users to log in by entering a unique, time-sensitive code received via email or their registered mobile number. Incorporating an OTP field helps businesses strengthen login procedures, minimize unauthorized access attempts, and add an extra layer of protection to user accounts.

To implement an OTP field on the Odoo login page, you’ll need to update the login template, modify the login controller, and include the necessary backend logic. In this guide, we’ll walk you through each step required to integrate OTP functionality, enabling more secure login experiences for your users. By following this process, you can safeguard your Odoo application from unwanted access.

Implementation Steps

Extend the res.users Model to Store OTP

First, we need to add a field to the User model to store the generated OTP.

File: models/res_users.py

from odoo import models, fields
class ResUsers(models.Model):
    _inherit = 'res.users'
    otp = fields.Char(string='OTP Code', help="One-Time Password for login authentication")
#Start by creating a `controllers` directory and defining a function for OTP generation.
def generate_otp():
   """Generate a 6-digit OTP (One-Time Password)."""
   digits = "0123456789"
   otp = ""
   for i in range(6):
       otp += digits[int(math.floor(random.random() * 10))]
   return otp

Create a Controller for OTP Handling

We create a controller to handle OTP generation, email sending, and validation. This controller inherits from Odoo's web controller.

File: controllers/main.py

import math
import random
from odoo import http
from odoo.http import request, route
from odoo.exceptions import AccessDenied
from odoo.addons.web.controllers.home import Home
class OTPLoginController(Home):
    def _generate_otp(self):
        """Generate a secure 6-digit numeric OTP."""
        digits = "0123456789"
        return ''.join(random.choice(digits) for _ in range(6))
    @http.route('/web/login/send_otp', type='json', auth='public')
    def send_otp(self, login, password, **kwargs):
        """Authenticate user credentials and send an OTP via email."""
        try:
            # Authenticate the user
            db = request.session.db
            uid = request.session.authenticate(db, login, password)
            if not uid:
                return {'success': False, 'error': 'Authentication Failed'}
            user = request.env['res.users'].browse(uid).sudo()
            # Generate and assign a new OTP
            otp_code = self._generate_otp()
            user.write({'otp': otp_code})
            # Prepare and send the OTP email
            template = request.env.ref('your_module.otp_email_template')
            context = {
                'user': user,
                'otp': otp_code,
                'company': user.company_id
            }
            email_body = request.env['ir.qweb']._render(template.id, context)
            mail_values = {
                'subject': f'{user.company_id.name}: Your Login OTP',
                'email_from': user.company_id.email,
                'email_to': user.email,
                'body_html': email_body,
            }
            request.env['mail.mail'].sudo().create(mail_values).send()
            # Log out the authenticated session to force OTP login
            request.session.logout()
            return {'success': True}
        except AccessDenied:
            # Log failed attempt if user exists
            user = request.env['res.users'].sudo().search([('login', '=', login)])
            if user:
                # ... (Code to log login attempt, e.g., IP, user agent) ...
                pass
            return {'success': False, 'error': 'Invalid login credentials'}
    @http.route('/web/login/verify_otp', type='json', auth='public')
    def verify_otp(self, login, otp, **kwargs):
        """Verify the submitted OTP against the one stored for the user."""
        user = request.env['res.users'].sudo().search([('login', '=', login)])
        if user and user.otp == otp:
            # Clear the OTP after successful use
            user.sudo().write({'otp': False})
            return {'success': True}
        return {'success': False, 'error': 'Invalid OTP'}

Inherit the Login QWeb Template

We modify the frontend login form to include our OTP field. This field is initially hidden and will be shown after the user successfully enters their password.

File: views/login_templates.xml

<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
    <template id="login_page_inherit" inherit_id="web.login">
            <xpath expr="//div[hasclass('oe_login_buttons')]" position="inside">
            <div class="form-group">
                <label for="otp">otp</label>
                <input type="text" name="otp" class="form-control" placeholder="Otp"/>
            </div>
            </xpath>
    </template>
</odoo>

Add JavaScript for Dynamic Login Flow

This JavaScript module controls the dynamic behavior: intercepting the login form, validating credentials, showing the OTP field, and finally verifying the OTP.

File: static/src/js/otp_login.js

/** @odoo-module **/
import publicWidget from '@web/legacy/js/public/public_widget';
import { jsonrpc } from '@web/core/network/rpc_service';
publicWidget.registry.OTPLogin = publicWidget.Widget.extend({
    selector: '.oe_login_form',
    events: {
        'click button[type="submit"]': '_onLoginSubmit',
    },
    _onLoginSubmit: function (ev) {
        ev.preventDefault();
        ev.stopPropagation();
        const $form = this.$el;
        const login = $form.find('#login').val();
        const password = $form.find('#password').val();
        const otp = $form.find('#otp').val();
        // If OTP field is visible, validate the OTP
        if (!$form.find('#otp_field_container').hasClass('d-none')) {
            this._verifyOTP(login, otp, $form);
            return;
        }
        // First, validate username/password to send OTP
        this._sendOTP(login, password, $form);
    },
    _sendOTP: function (login, password, $form) {
        const self = this;
        jsonrpc('/web/login/send_otp', {
            login: login,
            password: password,
        }).then(function (result) {
            if (result.success) {
                // Show the OTP field and focus on it
                $form.find('#otp_field_container').removeClass('d-none');
                $form.find('#otp').focus();
                self._clearError(); // Clear any previous errors
            } else {
                self._showError(result.error || 'Authentication failed. Please try again.');
            }
        }).catch(() => {
            self._showError('An error occurred while sending the OTP.');
        });
    },
    _verifyOTP: function (login, otp, $form) {
        const self = this;
        jsonrpc('/web/login/verify_otp', {
            login: login,
            otp: otp,
        }).then(function (result) {
            if (result.success) {
                // If OTP is correct, submit the form to complete the login
                $form.submit();
            } else {
                self._showError(result.error || 'Invalid OTP. Please try again.');
                $form.find('#otp').val('').focus();
            }
        }).catch(() => {
            self._showError('An error occurred during OTP verification.');
        });
    },
    _showError: function (message) {
        this._clearError();
        this.$el.prepend(
            `<div class="alert alert-danger" role="alert">
                ${message}
            </div>`
        );
    },
    _clearError: function () {
        this.$el.find('.alert-danger').remove();
    },
});
export default publicWidget.registry.OTPLogin;

How to Add a Field to the User Login Page in Odoo 18-cybrosys

Create an Email Template for the OTP

Finally, design an email template (via Odoo's Settings > Technical > Email > Templates) to send the OTP to the user. Use the QWeb context variables user and otp to personalize the message.

By following these steps, you can successfully integrate a robust OTP-based two-factor authentication system into your Odoo 18 login process. This significantly enhances security by adding a layer of protection beyond just a password.

To read more about How to Add a Field to the User Login Page in Odoo 17, refer to our blog How to Add a Field to the User Login Page in Odoo 17


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message