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 #197: Frequently Asked Questions on GraphQL APIs

        🎊 Nearing 200th Kaizen Post – We want to hear from you! 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 #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.
      • Celebrating 200 posts of Kaizen! Share your ideas for the milestone post

        Hello Developers, We launched the Kaizen series in 2019 to share helpful content to support your Zoho CRM development journey. Staying true to its spirit—Kaizen Series: Continuous Improvement for Developer Experience—we've shared everything from FAQs
      • Kaizen #193: Creating different fields in Zoho CRM through API

        🎊 Nearing 200th Kaizen Post – We want to hear from you! 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.
      • Client Script | Update - Introducing Commands in Client Script!

        Have you ever wished you could trigger Client Script from contexts other than just the supported pages and events? Have you ever wanted to leverage the advantage of Client Script at your finger tip? Discover the power of Client Script - Commands! Commands

        • Recent Topics

        • Introducing Rollup summary in Zoho CRM

          ------------------------------------------Moderated on 5th July'23---------------------------------------------- Rollup summary is now available for all organizations in all the DCs. Hello All, We hope you're well! We're here with an exciting update that
        • Introducing Connected Workflows in Zoho CRM for Everyone : Free Your Teams to Focus on What Matters

          Hello Everyone, We’re thrilled to introduce the next big evolution in Zoho CRM for Everyone -- Connected Workflows. This new feature builds on our commitment to deliver a CRM that’s truly inclusive, adaptable, and designed for consistent collaboration
        • Introducing Profile Summary: Faster Candidate Insights with Zia

          We’re excited to launch Profile Summary, a powerful new feature in Zoho Recruit that transforms how you review candidate profiles. What used to take minutes of resume scanning can now be assessed in seconds—thanks to Zia. A Quick Example Say you’re hiring
        • Introducing Connected Records to bring business context to every aspect of your work in Zoho CRM for Everyone

          Hello Everyone, We are excited to unveil phase one of a powerful enhancement to CRM for Everyone - Connected Records, available only in CRM's Nextgen UI. With CRM for Everyone, businesses can onboard all customer-facing teams onto the CRM platform to
        • Cooling-off Period Just Got Better: More Coverage, More Control

          We’ve enhanced the Cooling-off Period feature in Zoho Recruit to give you more control over repeat applications and referrals. This helps you maintain a cleaner, more efficient recruitment pipeline. With this enhancement, you can: Prevent duplicate candidate
        • 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
        • Revenue Management: #7 Revenue Recongition in Construction & Real Estate Industry

          If you are in the construction or real estate business, you are used to long project timelines and progressive invoicing to keep up with your billing. But when does revenue get recognized? Will it happen when the contract gets signed? At different milestones
        • Zoho Forms - Zoho Drive connection - Shared Drives not supported

          Hello i am stuck with Google Drive Connection There is no supported shared drives Connection is not support shared drives boolean Query Parameters - supportsAllDrives=true&supportsTeamDrives=true to activate fetch files from the shared drives. Ahat need
        • TikTok (and other social platform) Messages and comments of the past

          When I link a social channel, Zoho will show in "Inbox", "Messages" and "Contact" sections the interaction done in the past? (comment, messages...)
        • Email Integration - Zoho CRM - OAuth and IMAP

          Hello, We are attempting to integrate our Microsoft 365 email with Zoho CRM. We are using the documentation at Email Configuration for IMAP and POP3 (zoho.com) We use Microsoft 365 and per their recommendations (and requirements) for secure email we have
        • How do I fix this? Unable to send message; Reason:554 5.1.8 Email Outgoing Blocked.

          How do I fix this? Unable to send message; Reason:554 5.1.8 Email Outgoing Blocked.
        • Restrict Employee mail deletion

          Dear Zoho, Is there a way where i can restrict my employees to delete any mails from their account
        • 554 5.1.8 Email Outgoing Blocked.

          Hi guys, I just singed up for mateusz.nowicki@zoho.com mail and I can't send any mails.. Why? Everytime I try to send something I got error like the one in the screenshot. Please, help me.
        • Zoho IP blocked by SpamHaus

          ERROR CODE :550 - 5.7.0 Your server IP address is in the SpamHaus SBL-XBL database, bye
        • File Upload in Creator's Subfrom

          Hello Sir/Madam, Here is a Problem......... Scenario: In CRM One Custom Module (Payments) have one File Upload Field now we have to Upload that File into Creator's Custom Form (Documents) have one Subform (Documents) in Document Upload Field using Deluge
        • integarting attachments from crm to creator

          when i tried to integrate pdf attachments from crm to creator via deluge i am getting this error {"code":2945,"description":"UPLOAD_RULE_NOT_CONFIGURED"} the code i used is attachments = zoho.crm.getRelatedRecords("Attachments","Sales_Orders",203489100020279XXX8);
        • Error AS101 when adding new email alias

          Hi, I am trying to add apple@(mydomain).com The error AS101 is shown while I try to add the alias.
        • Trigger workflow base on email clic

          Searching the help and forum, I see that there were workflow trigger rules based on email. But now, I can't find this type of trigger when I create a custom workflow. What I'm looking for would be to automate the sending of an email for a new prospect,
        • Bigin Form Acknowledgement

          How to troubleshoot and find out why form acknowledgement is not sending emails after form submission?
        • Option to Customize Career Site URL Without “/jobs/Careers”

          Dear Zoho Recruit Team, I hope you are doing well. We would like to request an enhancement to the Career Site URL structure in Zoho Recruit. In the old version of the career site, our URL was simply: 👉 https://jobs.domain.com However, after moving to
        • Zoho Mail POP & IMAP Server Details

          Hello all! We have been receiving a number of requests regarding the errors while configuring or using Zoho Mail account in POP/ IMAP clients. The server details vary based on your account type and the Datacenter in which your account is setup. Ensure
        • Ever since the new Android App udpates notifications are not working

          notifications are not working for the app is its closed I followed the tutuorial to the notificaction fixed and everythig seems to be right but notifications are not workig
        • Zoho Analytics & Zoho Desk - but not all desks

          I have several desks in our company and one of those is used by our HR department. I want to bring through the data to the shared Zoho Analytics workspace - except for the HR desk. Can this be excluded at data import stage ?
        • Incoming Emails Not Showing Up in Zoho Inbox

          Hi - I have my Zoho email account set up to forward a copy of all incoming emails to a secondary Gmail address, whilst retaining the original email in the Zoho inbox. However, all my incoming emails are currently not showing up in my Zoho inbox, so I'm
        • Form Accessibility

          Hi, is there an update on the accessibility standard of Zoho forms? Are the forms WCAG 2.1 AA compliant? 
        • How to retrieve my following requests on this forum?

          Sorry, but I did not find the proper subforum for this question.
        • How to list emails in a folder, e.g. Inbox, on multiple pages when using Zoho mail webpage?

          Something as shown in the figure. There are totally 50 emails in Sent folder. If "Mail per page" equals 20, then the Sent folder is split into 3 pages. When I wander through Sent folder, I can just select a specific page to jump to. BTW, it seems that
        • Unable to Create Zoho Booking via the Book Appointment API

          Its giving the below error {     "response": {         "errormessage": "Error setting value for the variable:customer_details\n null",         "status": "Error"     } } Request: POST Url: https://www.zohoapis.in/bookings/v1/json/appointment attached Zoho-oauthtoken
        • SHEET - Send email when a cell changes

          I would like to create a custom function for Zoho Sheet that triggers when a paticular cell changes to a specific value. This would result in sending an email to a recipient (this would be an address that remains the same and included in the script). Example: = IF(N4= "Drafted", <>EmailFunction) 1)     Cell N4 changes to "Drafted" 2)    Email is sent to recipient            or alternatively 3)    Post to chat channel I have found the Custom function editor in Sheet. I am not bad at scripting, but
        • 【開催報告】 福岡 ユーザー交流会 2025/8/8(金)

          皆さま、こんにちは。コミュニティチームの中野です。 8/8(金)に、福岡 ユーザー交流会を開催しました。 本投稿では、その様子をお届けします。当日の登壇資料などもこちらに共有しますので、参加できなかった皆さまもご参照ください。 今年初の開催となる福岡 ユーザー交流会では、CreativeStudio樂合同会社 前田さんによるZoho CRM / Sign / Survey の事例セッションのほか、 Zoho社員セッションでは、Zoho Forms の活用法を解説。 さらに、「見込み客・顧客データの管理/活用方法」をテーマに参加者同士でZoho
        • Blueprint Issue - Being able to set a subform field as mandatory

          I'm creating a blueprint. My record involves a subform which is only shown once field is set but the field gets set in step two of the process. My problem - I can't save the record as the subform field is set to mandatory - If I unset the mandatory field,
        • hiding a topic from all but one segment (or list)

          My organization sends out a number of newsletters using Zoho Campaigns. One of those newsletters is for volunteers. In order to become a volunteer, a person has to first go through our volunteer orientation (training). After that, they can receive newsletters
        • no me llegan los correos a Zoho mail

          No puedo recibir correos pero sí enviarlos, ya hice la modificación de MX y la verificación de teléfonos, qué es lo que ocurre? gracias
        • Error: Invalid login: 535 Authentication Failed

          I have used zoho with nodemailer. const transporter = nodemailer.createTransport({ host: 'smtp.zoho.com', port: 465, secure: true, auth: { user: 'example@example.com', pass: 'password' } }); While sending the mail, it shows the following error: Error:
        • Zoho Renewal

          Hello, If I am not going for zoho email renewal. will i get back my free zoho account? and if yes then is it possible to get back my all free user. how many user get back 10 or 25?
        • javax.mail.authenticationfailedexception 535 authentication failed

          Hi, I am facing 535 authentication failed error when trying to send email from zoho desktop as well as in webmail. Can you suggest to fix this issue,. Regards, Rekha
        • Not reciving emails

          Apparently i cannot recive emails on my adress contact@sportperformance.ro I can send, but do not recive. The mail i'm trying to send from mybother adress gets sent and doesn't bounce back... but still doesn't get in my inbox. Please advise
        • Not receiving MailChimp verification e-mail

          It seems that their verification e-mails are blocked. I can receive their other e-mails, but not their verification of domain ownership e-mail. I've checked and double checked how I typed the e-mail, using different e-mails (my personal e-mail can receive it), white listing the domain and all that is left is for the IP's to be white listed, but I don't have that power.  If a staff member could take a look at this -> http://mailchimp.com/about/ips/ And perhaps white list them for me, that would be
        • Creating my 2nd email account

          After creating my first email address, I decided to get another email address. I would like to use this new address as the primary address too. I don't know how to set it up there doesn't seem to be an option for that
        • Cannot - create more email account - Unusual activity detected from this IP. Please try again after some time

          Hello, I come across the error message in Control Panel. Unusual activity detected from this IP. Please try again after some time and i cannot create any more users We are an IT company and we provide service for another company Please unlock us.
        • Next Page