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

    • date & datetime client script getInput types

      Please add date & datetime as available types for the getInput client script function. https://www.zohocrm.dev/explore/client-script/clientapi/Client#getInput
    • Opt-out from mailing list means can't email at all??

      It seems that if a contact unsubscribes from a mailing list the only way to email them is to uncheck the email opt-out box first, then re-check after the email has been sent. I've been through a chat with support and they confirmed this. This seems bizarre. Many of my clients don't want to receive a monthly newsletter but they definitely want to be in email communication with me, often on a daily basis. Any thoughts out there?
    • Email Opt Out Question

      Has the problem where if a customer is emailed opt out prevents you sending standard emails? For me this feature is simply to stop any email marketing and should not block people from receiving emails via Zoho mobile, which makes no sense.
    • Multi-currency and Products

      One of the main reasons I have gone down the Zoho route is because I need multi-currency support. However, I find that products can only be priced in the home currency, We sell to the US and UK. However, we maintain different price lists for each. There
    • Approval Workflow Not Triggering When Status Updated via Custom Button

      Hi Team, I’m facing an issue with an approval workflow in my application. I have a workflow that updates a record’s Status field from “Pending” to “Waiting for Approval.” I have configured an Approval Workflow with the condition: Status = "Waiting for
    • Zoho TeamInbox stuck / unusable – possible orphaned organization?

      Hi all, I’m running into an issue with Zoho TeamInbox under Zoho One Trial and wondering if anyone has seen this before. We’re trying to set up a shared inbox for orders (orders@), but TeamInbox appears to be in a broken state: • The app does not load
    • Audio transcript not working in Zoho notebook

      Greetings all! I'm enjoying the Zoho notebook as I tested out over other notebook applications. I would really love the audio transcripts to work as that would save us a lot of time so that we can utilize the notes from an audio recording. Currently when
    • Re: Application Architecture in Zoho Creator — A Platform-Specific Deep Dive

      A recent community post on application architecture made some excellent points about planning architecture early in Zoho Creator projects. The core message is right — Creator applications have a habit of growing organically into maintenance nightmares,
    • Schedule Timeout 5 minutes vs. stated 15 minutes

      I am running into a function run timeout error after 5 minutes for my schedules. The Functions - Limits documents states it should be 15 minutes: Functions - Limits | Online Help - Zoho CRM. What should it actually be? Due to the 5 minute timeout, I'm
    • Cliq iOS can't see shared screen

      Hello, I had this morning a video call with a colleague. She is using Cliq Desktop MacOS and wanted to share her screen with me. I'm on iPad. I noticed, while she shared her screen, I could only see her video, but not the shared screen... Does Cliq iOS is able to display shared screen, or is it somewhere else to be found ? Regards
    • Placeholders in Ticket Templates

      We should be able to use placeholders in ticket templates. When we create a new ticket, our description field is shown to the client in the email they receive.  It would be very handy to be able to personalize that description field in our ticket templates to pull in the name of the client that the ticket is for. Using them in the subject field as well, so we can auto populate Account Names, etc. 
    • Favorite Views list sort order?

      Is there a way to rearrange the Favorite Views list?
    • Will I see emails sent via campaigns in CRM?

      It would be useful for people to be able to see emails sent via campagins in Zoho CRM is that possible?
    • Introducing Product Catalog in Bigin

      Greetings, I hope all of you are doing well. For many small businesses, sharing products and collecting customer interest often involves multiple tools, manual follow-ups, or even building a full ecommerce website. To address this challenge, we're excited
    • Products in Email Template

      I’m a little confused as to how to add the listed products, from within a specific deal, to an email template. I want to generate said email template when a deal reaches a specific stage and include the products that have been selected for the deal in
    • Option for - CSV Export from Pipeline Deals by Stage (Including Products, Companies, and Contacts)

      I would like to know when we will be able to export a simple CSV file from pipeline deals, with the option to select a specific stage within the pipeline. This export should include data for products, companies, and contacts, all in a single view. For
    • Zoho Billing Partial Proration...

      Hi, In Zoho billing, we offer upgrades, downgrades, suspends, and resumes. We use online payment for most subscriptions, but have only a few that are offline, but we treat them the same either way.. We prorate only parts of these changes.. Right now zoho
    • Announcing Zoho Sheet desktop app for macOS and Windows (Beta)

      Hello Sheet users, We know you’ve been waiting for this one. It has always been the top priority on our roadmap to provide a single native desktop app for macOS and Windows that works both online and offline. Today, we are excited to announce that the
    • What is the maximum length/size of a presentation on Apple TV?

      Hello, I have a presentation here that I regularly show on Apple TV. It’s always the same presentation, which keeps getting longer and more extensive over time. Almost every slide contains a graphic or photo that takes up the entire slide. That means:
    • CRM Notes

      Hello, We want to add a Note to the Contact record when a Note is added to a Case or Deal. I wasn't able to do this using a workflow, so I tried using Zoho Flow, but that didn't work either. Does anyone have a suggestion on how we can accomplish thi
    • Zoho Books | Product updates | April 2026

      Hello users, Welcome to our April 2026 product updates roundup! Highlights include profit margin for sales transactions, insights in reports, recording deposits from undeposited funds in banking, and faster production workflows with improved assembly
    • Sorter View No Longer Works Properly

      Today I realized that sorter view no longer works properly. I believe just yesterday, I was able to drag a slide or a group of slides and move it/them to the place I want to move it/them to. While I was dragging, I got a visual feedback of where I could
    • Ignroe_filtrers and drill through

      I have two charts, where one is connected to the other using drill-through. The issue is with the second chart (the drill-through target). It contains an aggregation formula that uses the ignore_filters function. Under normal conditions, the formula works
    • zoho creator panel add formula

      Hello. I'm using zoho panels which works good for me but i need to add an extra calculation the predefined sum function provided by zoho. not sure if this is possible? The code zoho is generating for the sum is as follows; <text margin='0px' padding='0px'
    • Kaizen #238: Fetching Employee Data from Microsoft SQL Server into Zoho CRM Using Queries

      Hello everyone! Welcome back to the Kaizen series! Many organizations manage workforce data such as employee designations, contact details, salary bands, and joining dates in an HRMS backed by Microsoft SQL Server, while their sales teams work in Zoho
    • Du prompt à la production : comprendre le fonctionnement du MCP

      Le Model Context Protocol (MCP) est un protocole ouvert qui standardise la manière dont les applications se connectent aux modèles de langage (LLM). En termes simples, le MCP est le « USB-C des agents IA » : une interface universelle qui relie l’intelligence
    • Add Flexible Recurrence Options for Meeting Scheduling in Zoho Cliq (e.g., Every 2 Weeks)

      Hello Zoho Cliq Team, We hope you are doing well. Currently, when scheduling a meeting inside Zoho Cliq, the recurrence options are limited to Daily, Weekly, Monthly, and Yearly. There is no ability to set a meeting to occur every X weeks — for example,
    • filter on sheets mobile (iOS, iPadOS) does not work

      re-posting this as a question because reporting as a problem netted zero responses... I have this issue on every spreadsheet, whether imported or created natively in Zoho Sheets. I can use on desktop without issue: but on iOS the filter dropdowns are
    • Inactive License for free account.

      I recently upgraded my Cliq subscription not my team (on the free version), are unable to login to their accounts. The error message received is Inactive License Looks like you have not been covered under the current free plan of users. Please contact
    • Zoho Books recurring invoicing link to a Zoho CRM Deal?

      With the Zoho CRM Finance Integration setup, normally when creating an invoice via Zoho CRM Finance Integration on a Deal, it creates the invoice in Zoho Books with the CRM Deal *Potential link for that invoice. Alternatively, if you create the invoice
    • Data Import

      Hello Latha, Is there any option to enable data import option in Equipment module? Best regards, Chethiya.
    • No background for video recordings, no playback speed, can't even playback longer recordings - have to download…

      Hi. We utilize heavily video messages on Slack, but wanted to migrate to Cliq with Zoho One, however very basic yet very frequently used feature is missing: backgrounds for video recordings and playback speed. We were not happy with Slack's 5 minute limits
    • Introducing PDF conversion in Deluge

      Hello everyone, We're pleased to introduce built-in PDF conversion capabilities in Deluge with the launch of the convertToPDF task. As PDF is one of the most widely used formats in professional environments, this task helps streamline document generation
    • Changing naming convention for Estimates

      Hi there, How do I customise the naming of the filenames for estimates when sending them? Currently they are named literally just ES11.PDF or whatever the number of the estimate is. There is no Company name, description of the work, customer name, date
    • Email templates for estimates

      Hi there, When I send an estimate the option to choose a template appears. However it is common to need to make some minor personalizations and modifications to the email before sending ie. adding a second person to the greeting, or adding a small note
    • How to transfer uploaded files from Zoho Survey to external systems?

      Hi everyone, I'm currently integrating Zoho Survey with an external system and have run into a limitation I haven't been able to solve yet. I'm using webhooks to send submitted survey data (JSON) to my external API, which works perfectly for all standard
    • On Duty Requests - Zoho People Data

      Hello Team, We are currently using the On Duty Form to record Work From Home (WFH) requests in our organization. However, we are facing an issue where pending On Duty requests are not appearing in the Attendance Module. For example, if I submit On Duty
    • Editing Estimates

      Hi team, How do I delete line items in an estimate when editing it? I can't see any option to do this - no X, no trashcan, no drop down... And how do I revert to the original estimate after cloning? I cloned to see if the option to edit were disabled
    • Reading emails sent from Zoho

      Hi there, How do I view an email sent from Zoho, for example an email which contains an estimate? I can see there is a tab in the screen for that estimate which is labelled 'Emails' and I can see a summary of the email sent listed there, but I cannot
    • Feature Request: Mass update selected Contacts to Accounts

      I can't believe this isn't an ability already. It's a quick fix that would save hours of manual entry time. This looks like it had been requested 3-4 years ago with no answers from staff! Please add all contact fields into the "mass update" menu. You
    • Next Page