Kaizen #191: Implementing "Login with Zoho" using Python SDK

Kaizen #191: Implementing "Login with Zoho" using Python SDK

      
Welcome back to another week of Kaizen!!

This week, we are diving into how to implement secure user authentication using Login with Zoho and integrate it with Zoho CRM through our Python SDK.

To ground this in a real-world scenario, we will look at how Zylker Academy, a training institute offering web design and development courses, uses an internal portal that connects directly to Zoho CRM. This setup allows course coordinators to manage student data without maintaining a separate backend database.

Zylker receives frequent student enquiries and uses Zoho CRM to manage all related information. Every course coordinator, academic advisor, and support staff member who needs access to student information is added as a user in Zoho CRM, with access permissions aligned to their role. Instead of using Zoho’s interface directly, Zylker’s team works through a custom internal web portal, tailored to their workflow. This portal connects directly to Zoho CRM, reading from and writing to it, but does not have its own database.

But before this portal can access any CRM data, it must authenticate itself securely. Every time a user opens the portal, they must log in with their Zoho account. Once authenticated, they will be granted access to the CRM modules and records they are authorized to work with. That is where Login with Zoho comes in.

What is "Login with Zoho"?

Login with Zoho is Zoho’s implementation of the OAuth 2.0 Authorization Code flow. It allows applications to authenticate users and access their Zoho CRM data without ever handling their passwords.
Instead of asking users for their Zoho credentials directly, the app redirects them to Zoho’s login screen. Here is how it works:
  1. The app redirects the user to Zoho’s login page.
  2. The user logs in and approves the requested permissions (scopes).
  3. Zoho sends back an authorization code.
  4. The backend exchanges this code for access and refresh tokens.
  5. These tokens are used to make authenticated API calls.
This flow ensures that users maintain full control over their data. They can revoke access at any time, and your application never handles or stores passwords. 
In Zylker’s case, every time a coordinator opens the portal, they are prompted to log in with their Zoho account. Once authenticated, they can immediately begin working with student records—all backed by Zoho CRM.

Use Case Implementation: Zylker’s Student Management Portal

To demonstrate how this login flow works, we have built a stripped-down version of Zylker's portal:
  • A front-end form to enter and view student data
  • A backend server that interacts with Zoho CRM via the Zoho CRM Python SDK
The application includes a simple form for capturing student details—name, college, course, email, and phone number. Submitted data is treated as a Lead in Zoho CRM.
The app allows users to:
  • Add new leads
  • View a list of all registered leads
  • Edit an existing lead’s information
  • Delete records if necessary
All actions go straight to Zoho CRM using its Python SDK. But before any of this can happen, the user must complete the login flow.

Sample Project Structure

Before going into the implementation details, let us briefly define the components of the project.

Frontend
The frontend is a simple static web interface built with HTML, CSS, and JavaScript. It runs in the browser and handles user interactions and triggers backend API calls. These are the main files:
  • index.html : Main UI for login, data entry, and record viewing.
  • script.js : Contains the client-side logic to trigger login, submit data, and render records.
  • redirect.html :  A minimal page used to capture the authorization code returned by Zoho after login.
The frontend is served using any static server (e.g., Live Server in VS Code) and runs on http://localhost:5501/ in our example. 
Download the files from here.
Configuration Notes:
  • In script.js, update the redirect_url value in the login request to match your actual domain or port if you’re not using localhost:5501.
  • Ensure the URL in the Zoho API Console matches this redirect URI and port.
Backend
The backend is a Python server that handles all interactions with Zoho CRM via the Python SDK. It includes:
  • server.py : A custom HTTP server that:
    • Generates the Zoho login URL
    • Exchanges the authorization code for tokens
    • Initializes the SDK
    • Exposes endpoints like /create, /get_records, /update, and /delete
  • record.py : Contains functions to create, fetch, update, and delete records in CRM modules like Leads. Each function uses the Zoho Python SDK methods to perform a specific operation.
This server runs on http://127.0.0.1:8085/ in our example. 

Download the files from here.
Configuration Notes:
  • In server.py, replace the client_id with your actual client ID from Zoho's API Console.
  • In record.py, replace the client_secret with your actual client secret.
  • If required, change the front-end server’s host and port in the run() function at the bottom of server.py:
    def run(server_class=HTTPServer, handler_class=SDKInitialize, port=xxxx):

Sample project flow

      

Step 1: Register the application with Zoho API console

To initiate the login process, you need to register your application on the Zoho API Console. This is a one-time setup that provides your app with a Client ID and Client Secret, both of which are required to authenticate users and exchange authorization codes for tokens.
To register your application:
We will be using these values in the backend script (server.py)  that handles token exchange.

NotesNOTE: To support users from multiple data centres, make sure to enable multi-DC support for your application. You can do this by going to your app’s settings in the Zoho API Console and turning on the Multi-DC option.

Step 2: Implementing the login flow

Here is a walkthrough of the flow implemented in the project:

1. Page loads and triggers login

When a user opens the portal, the frontend automatically initiates the login sequence. It first makes a call to the backend to retrieve the Zoho authorization URL. 

In index.html, this triggers getRecords() on page load:
  1. <body onload="getRecords();">
In script.js, getRecords() calls the login() function:
  1. async function getRecords() {
  2.     login();
  3. }
The login() function sends a request to the backend to get the Zoho OAuth authorization URL.

2. Backend builds login URL

The backend responds with an OAuth URL that includes:
  • Your client ID
  • Scopes like ZohoCRM.modules.ALL
  • The redirect URI
In server.py, under do_GET, the /login endpoint generates the OAuth URL:
  1.    if parsed_url.path == '/login':
  2.             redirect_url = query_params.get('redirect_url', [''])[0]
  3.             scope = "ZohoCRM.settings.fields.ALL,ZohoCRM.modules.ALL,ZohoCRM.users.READ,ZohoCRM.org.READ"
  4.             url = "https://accounts.zoho.com/oauth/v2/auth?scope=" + scope + "&client_id=" + self.client_id + \
  5.                   "&redirect_uri=" + redirect_url + "&response_type=code&access_type=offline"
  6.             self._set_headers()
  7.             # Send response
  8.             response = {"url": url, "redirect_url": redirect_url}
  9.  self.wfile.write(json.dumps(response).encode('utf-8'))
Once the frontend (script.js) receives the login URL, it opens it in a popup window.
  1. const response = await fetch('http://127.0.0.1:8085/login?redirect_url=http://127.0.0.1:5501/redirect.html');
  2. const data = await response.json();
  3. const popup = openCenteredPopup(data.url, "PopupWindow", 600, 400);
Here's an example of the Zoho OAuth authorization URL format:
      scope=ZohoCRM.modules.ALL&
      client_id=YOUR_CLIENT_ID&
      response_type=code&
      access_type=offline&
      redirect_uri=YOUR_REDIRECT_URI

3. User logs in on Zoho

The user logs in with their Zoho credentials and is prompted to approve the app's access. Once they approve, Zoho redirects them to the specified redirect URI along with an authorization code and location parameter. The location parameter indicates which data centre the user belongs to.

4. Frontend captures the authorization code

The redirect page, a minimal HTML file (redirect.html),  reads the URL parameters and stores them in localStorage, then closes the popup:
  1. function setAccessToken() {
  2.     var hashProps = getPropertiesFromURL();
  3.     if (hashProps) {
  4.         for (var key in hashProps) {
  5.             if (hashProps.hasOwnProperty(key)) {
  6.                 localStorage.setItem(key, hashProps[key]);
  7.             }
  8.         }
  9.     }
  10.     setTimeout(function () { window.close(); }, 0);
  11. }

5. Token exchange and SDK initialization

Once the popup window is closed, the main window retrieves the authorization code and location and sends them to the backend’s /initialize endpoint.
In script.js:
  1. var code = localStorage.getItem("code");
  2. var location = localStorage.getItem("location");
  3. initialize(code, location, data.redirect_url);
  4. .
  5. .
  6. async function initialize(code, location, redirect_url) {
  7.     const response = await fetch('http://127.0.0.1:8085/initialize?code=' + code + '&location=' + location + '&redirect_url=' + redirect_url);
  8. }
In server.py, the /initialize endpoint handles SDK initialization:
  1. elif parsed_url.path == '/initialize':
  2.     code = query_params.get('code', [''])[0]
  3.     location = query_params.get('location', [''])[0]
  4.     redirect_url = query_params.get('redirect_url', [''])[0]
  5.     LeadsRecords().init(self.client_id, code, location, redirect_url)
In record.py, the SDK is initialized and tokens are stored.
  1. token = OAuthToken(client_id=client_id,
  2.                    client_secret=client_secret,
  3.                    grant_token=code,
  4.                    redirect_url=redirect_url)
  5. Initializer.initialize(environment=environment,
  6.                        token=token,
  7.                        logger=logger,
  8.                        store=store)  # FilePersistence or custom store
This exchanges the authorization code for:
  • An access token (valid for one hour)
  • A refresh token (used to get new access tokens)
These tokens are saved in a local file (sdk_tokens.json). This is configured using Zoho’s FilePersistence class during SDK initialization 

How are tokens linked to users?

The SDK maps each access and refresh token pair to a unique user-organization combination. This means tokens generated for different organizations by the same user are stored separately. Likewise, if a user generates new tokens for the same organization, the SDK updates the existing tokens instead of creating duplicates. This ensures that API calls always use the correct tokens tied to the authenticated user and their organization. 

To enable this mapping, the SDK retrieves the user and organization information in the background. This requires the appropriate scopes to be included during authentication, ZohoCRM.users.READ and ZohoCRM.org.READ. Without these scopes, the SDK cannot identify the user-org combination correctly, which can lead to multiple token entries for the same user. That is why, in our sample project, we have included these scopes explicitly in the server.py file during the SDK initialization.

Once the SDK is initialized, the user is logged in, and the app can begin making CRM API calls on their behalf.


Step 3: Accessing Zoho CRM

Once the user is authenticated and the Zoho SDK is initialized on the backend, the frontend can call custom backend endpoints like /create or /get_records. These endpoints use the authenticated SDK instance to make CRM API calls on behalf of the user.
  • GET /get_records?module=Leads : View all students
  • POST /create?module=Leads : Add new student
  • PUT /update?module=Leads&id=... : Edit existing entry
  • DELETE /delete?module=Leads&id=... : Remove existing entry

Deploying the sample project

To run this application, you will need two components:
  1. A frontend server to serve your HTML files (index.html, script.js, redirect.html). This can be done using any static web server (e.g., Live Server in VS Code).
  2. A Python backend server that handles login, token storage, and CRM API communication. You can run it using:
    python server.py
In the given example, both servers communicate over localhost. You should set your redirect URI accordingly when registering your app in the Zoho console.

Conclusion

Login with Zoho is a secure, OAuth-based mechanism that allows users to authorize your application to access their Zoho CRM data. In this example, we built a real-world use case, a student portal for Zylker Academy, that authenticates users and interacts with CRM directly using the Zoho CRM Python SDK.
By walking through the entire flow, you now understand:
  • Why OAuth is essential for secure CRM access
  • How to register an application in Zoho
  • What the login and token exchange flow looks like
  • How to implement "Login with Zoho" in your applications

What is next?

In this project, we have used a simple file persistence method to store the token files. But in a real world scenario, this may not always meet your business requirements. In next week's Kaizen, we will implement custom token persistence instead of file persistence in the current project. We will explain how to implement this using SQLite, In-Memory and List DBs. With that, you will be equipped to implement a persistence method that fits your application architecture and deployment environment.

We hope that you found this useful. If you have any queries, let us know the comments below, or send an email to support@zohocrm.com. As always, we would love to hear from you!!

Stay tuned for next week's Kaizen : Implementing Custom Token Persistence 


Download Links:
Further Reading:

    • Sticky Posts

    • Kaizen #198: Using Client Script for Custom Validation in Blueprint

      Nearing 200th Kaizen Post – 1 More to the Big Two-Oh-Oh! Do you have any questions, suggestions, or topics you would like us to cover in future posts? Your insights and suggestions help us shape future content and make this series better for everyone.
    • Kaizen #226: Using ZRC in Client Script

      Hello everyone! Welcome to another week of Kaizen. In today's post, lets see what is ZRC (Zoho Request Client) and how we can use ZRC methods in Client Script to get inputs from a Salesperson and update the Lead status with a single button click. In this
    • Kaizen #222 - Client Script Support for Notes Related List

      Hello everyone! Welcome to another week of Kaizen. The final Kaizen post of the year 2025 is here! With the new Client Script support for the Notes Related List, you can validate, enrich, and manage notes across modules. In this post, we’ll explore how
    • Kaizen #217 - Actions APIs : Tasks

      Welcome to another week of Kaizen! In last week's post we discussed Email Notifications APIs which act as the link between your Workflow automations and you. We have discussed how Zylker Cloud Services uses Email Notifications API in their custom dashboard.
    • Kaizen #216 - Actions APIs : Email Notifications

      Welcome to another week of Kaizen! For the last three weeks, we have been discussing Zylker's workflows. We successfully updated a dormant workflow, built a new one from the ground up and more. But our work is not finished—these automated processes are
      • Recent Topics

      • DKIM Now Mandatory - Changes to Zoho Forms Email Policies

        Hello Zoho Forms Users, This post is to inform you about an important update regarding the authentication of all email domains in your Zoho Forms account. This year, we are doubling down on our commitment to deliver a secure, seamless, and empowering
      • Call description in notes

        When completing a call, we type in the result of the call in the description. However, that does not show up under the notes history on the contact. We want to be able to see all the calls that have taken place for a contact wihtout having to go into
      • Email Address for Contact not Populating

        When I click "Send Mail" from a Contact's page, their email address does not auto populate the "To" field. How do I make this happen?
      • New in CRM: Dynamic filters for lookup fields

        Last modified on Oct 28, 2024: This feature was initially available only through Early Access upon request. It is now available to all users across all data centers, except for the IN DC. Users in the IN DC can temporarily request access using this form
      • Why hybrid project management might be the best fit for you?

        Project management techniques are designed to equip teams with proven methods for easy and efficient project execution. While management teams may have apprehensions about adopting the hybrid method of project management, we’ve compiled the top reasons
      • Allow all Company Users to view all projects, but only owner/admins can change projects

        I was wondering if there was a permission setting I could adjust to allow all our company users to see all projects created. Then, only the project owners and admins with the change permission. Thanks
      • Fail to send Email by deluge

        Hi, today I gonna update some email include details in deluge, while this msg pops up and restrict me to save but my rules has run for one year. can you tell me how to use one of our admin account or super admin account to send the email? I tried to update
      • Seeking help to be able to search on all custom functions that are defined

        Hello I have a lot of custom functions defined (around 200) and i would like to search some specific strings in the content of those. Is there a way to accomplish that? If not, is there a way to download all existing custom functions in some files locally
      • Totals for Sales Tax Report

        On the sales tax report, the column totals aren't shown for any column other than Total Tax. I can't think of a good reason that they shouldn't be included for the other columns, as well. It would help me with my returns, for sure. It seems ludicrous
      • Add Bulk Section / Grid Layout Duplicate Feature in Zoho Forms Builder

        Currently in Zoho Forms, users can only duplicate individual fields. There is no option to duplicate an entire section or two-column/grid layout with all internal fields. This becomes inefficient when building structured forms such as Family Details,
      • Leistungsdatum in Rechnungen (Zoho Books)

        Hallo, ist es irgendwie möglich den Leistungszeitraum in der Rechnung aufzuführen? Beste Grüße Aleks
      • Zoho Trident Windows - Streams Not Visible

        Namaste We’re having an issue with Streams not being visible in Trident (Windows), which is important for us as we share many emails internally. It appears that the feature to show Streams above the Inbox folder, as seen in the default mailbox view, is
      • Sales IQ Chat Widget is Only Displaying Last Name

        Can anyone suggest why the widget is only displaying "last name"?! We have the latest version of the wordpress plugin installed. Thanks Thanks!
      • Shopify - Item sync from Zoho Inventory

        Hi team, We’ve connected Shopify with Zoho Inventory. We want that when an item is created in Zoho Inventory, it must create a product in Shopify. But currently, new items created in Zoho Inventory are not getting created in Shopify even after clicking
      • Bulk upload image option in Zoho Commerce

        I dont know if I am not looking into it properly but is there no option to bulk upload images along with the products? Like after you upload the products, I will have to upload images one by one again? Can someone help me out here? And what should I enter
      • Is it possible to setup bin locations WITHOUT mandating batch tracking?

        Hi fellow zoho users, I'm wondering if anyone else has a similar issue to me? I only have some products batch tracked (items with shelf life expiry dates) but I am trying to setup bin locations for my entire inventory so we can do stock counting easier.
      • Kill zoho meeting

        Saying the quiet part out loud. Can zoho please just give up on the idea that they can make a meeting platform and just make our workplace licenses cheaper when you remove it so people can switch to zoom or teams. Tired of the excuses, you guys cant make
      • Utilisation de Zoho en conformité avec l’article 286 du Code général des impôts (CGI)

        Cher(e) client(e), Conformément à l’article 286 du Code général des impôts (CGI) impose aux entreprises assujetties à la TVA d’utiliser des systèmes de caisse ou de gestion commerciale certifiés lorsqu’elles enregistrent des ventes à des particuliers.
      • Unable to Create Task as a Support Administrator

        Hello! I want to ask for help regarding creating tasks within the tickets. I am by default the Support Admin. I should be able to create tasks or activities right? But there's a prompt that I need to contact the Administrator. See photos for reference.
      • Introducing Forms in Zoho Sheet

        We hereby bring you the power of ​forms in Zoho Sheet. ​Now, build and create your own customized forms using Zoho Sheet. Be it compiling a questionnaire or rolling out a survey, Zoho Sheet can do it all for you. Forms is an excellent feature that helps you collect information in the simplest of ways and having it in Zoho Sheet takes it a notch higher. Build Simple yet Powerful forms Building forms using Zoho Sheet is fairly simple. The exclusive 'Form' tab lets you create one quickly. Whether you
      • Layout one survey question in a time & redirect next Page based on previous response

        I have doubt while, I am scripting survey on the Zoho where I redirecting to next page based on my previous response but didn’t get success on this. Please help me on this and tell me how I layout one survey questions in a time when I submit response
      • Zoho Bookings form pre-filled with Zoho Forms in

        Hi, I've got a contact page on my website and I'd like to have the option to book an appointment (redirected to zoho bookings page) after an option is submitted on the contact form. how would I go about doing this? thanks
      • Support “Other” Option with Free Text in Dropdown Fields

        Hello Zoho Bookings Team, Greetings, We would like to request an enhancement to the registration form fields in Zoho Bookings, specifically for dropdown fields. Current Limitation: At the moment, dropdown fields do not support an “Other” option that allows
      • Detailed Balance Sheet for tax preparer

        I'm using the free edition of Zoho Books. My tax preparer is asking for "detailed" Profit & Loss and Balance Sheet reports which include all the activity and transactions within the various categories. The default reports do not include these details.
      • Sending automated messages that appear in the ticket's conversation thread

        Good morning, esteemed Zoho Desk community, warm greetings Today I am here to raise the following problem, seeking a solution that I can implement: I need to implement an automation that allows me to send reminder messages to customers when I am waiting
      • Payment Card or Identity form-fill from Vault?

        Hello! I'm working on replacing Bitwarden with Vault and one issue I've run into is that I can't find any option to fill address and payment forms from Payment Card or Identity info that has been saved in Vault. Is there a way to do this? Is it a planned
      • Ability to add VAT to Retainer Invoices

        Hello, I've had a telephone conversation a month ago with Dinesh on this topic and my request to allow for the addition of VAT on Retainer Invoices.  It's currently not possible to add VAT to Retainer Invoices and it was mutually agreed that there is absolutely no reason why there shouldn't be, especially as TAX LAW makes VAT mandatory on each invoice in Europe!   So basically, what i'm saying is that if you don't allow us to add VAT to Retainer Invoices, than the whole Retainer Invoices becomes
      • Time Log Reminder

        Tracking the time spent on tasks and issues is one of the most important functions of a timesheet. However, users may forget to update the time logs because they have their own goals to achieve. But, time logs must be updated at regular intervals to keep
      • [Early-access] Introducing Zoho's CommandCenter - Cross-Zoho business process automation

          Resources to help Webinar recording | Documentation  Feature Restrictions Currently available on early-access only for US data center accounts Features Role CommandCenter as a Service uses signals across Zoho services to propel the movement of records
      • Tip #58- Accessibility Controls in Zoho Assist: Learning- 'Insider Insights'

        Learning should be clear and interruption-free for everyone. Timely feedback plays an important role in helping users understand actions as they happen, without breaking their focus. In this post, we’ll explore the final section of Accessibility: Learning.
      • ZIA "Generate Content" action doesn't have contexual data from the ticket

        "Generate Content" action doesn't have contexual data from the ticket. I try to get AI to help me with this ticket but it doesn't seem to have any ticket information as context. Although the ticket has a lot of information in it.
      • Zoho Desk - Zoho FSM Integration issue on Mobile and iPad

        Hello Team, I am trying to create a Work Order (WO) using the Zoho FSM integration (Add-on Service) that is integrated with Zoho Desk. The issue is that the integration is not working on mobile devices and iPads. While I am able to create the WO, Request,
      • E-File Form 1099 Directly With the IRS From Zoho Books

        The Form 1099 filing season has begun, and businesses are required to e-file certain forms with the IRS to report payments made to vendors and contractors. If your business made qualifying payments during the year, you must e-file the appropriate Form
      • Suggestions for showing subscribed Topics in CRM (contact record)

        We have several Topics set up in ZMA. We also have a sync set up between ZMA and CRM. I'd like to display the subscribed topics on the CRM Contact record. This will allow the Sales team (who uses CRM) to see at a glance what topics a Contact is subscribed
      • Replies sometimes creating separate ticket

        Sometimes when a customer responds to an email coming from Zoho Desk, instead of adding a reply to the original ticket, a separate ticket is created. This happens even though the response subject line contained the ticket number, and the person responding
      • Re-hide fields when option is unselected

        Hi all Can anyone help me with this - when I create a 'show' field rule for when a dropdown option is selected, how to I make it so the 'show' option re-hides if that option is no longer selected?
      • Custom Fields

        There is no way to add a custom field in the "Timesheet" module. Honestly, the ability to add a custom field should be available in every module.
      • Consultant-Only Booking Page

        Zoho Bookings does not allow for Meeting Type OR Workspace-Wide booking pages to be turned off. This is detrimental to organizations that have territory-based or assigned accounts, because if prospects can go to these booking pages and either select the
      • Call result pop up on call when call ends

        I’d like to be able to create a pop up that appears after a call has finished that allows me to select the Call Result. I'm using RingCentral. I have seen from a previous, now locked, thread on Zoho Cares that this capability has been implemented, but
      • Custom Sorting based on other columns in table

        I need the ability to apply custom sorting to a text-based dimension in the X axis where the sorting is based on another column in the table. For example, I have a chart report where the X axis is a text label. I would like to be able to sort those text
      • Next Page