Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Zoho Laravel Package

socialiteproviders/zoho

Zoho OAuth 2.0 provider for Laravel Socialite. Adds Zoho single sign-on to your Laravel app through the SocialiteProviders manager, so users can log in with their Zoho account using the familiar Socialite::driver('zoho') API. Install via Composer and configure your client ID and secret.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require socialiteproviders/zoho — this pulls in the shared socialiteproviders/manager package. You don't write or register a service provider; the driver is attached via an event listener.
  • Register the listener for SocialiteWasCalled:
// app/Providers/EventServiceProvider.php
protected $listen = [
    \SocialiteProviders\Manager\SocialiteWasCalled::class => [
        \SocialiteProviders\Zoho\ZohoExtendSocialite::class.'@handle',
    ],
];

On Laravel 11+ (no EventServiceProvider by default), either Event::listen(...) in AppServiceProvider::boot() or drop a listener in app/Listeners with a handle(SocialiteWasCalled $event) type-hint — Laravel's event auto-discovery will find it.

  • Add credentials to config/services.php. Socialite reads services.zoho, so the key name matters:
'zoho' => [
    'client_id'     => env('ZOHO_CLIENT_ID'),
    'client_secret' => env('ZOHO_CLIENT_SECRET'),
    'redirect'      => env('ZOHO_REDIRECT_URI'),
    // data-center TLD: com | eu | in | com.au | jp | ca
    // confirm the exact key name and default in the provider source
    'domain'        => env('ZOHO_DOMAIN', 'com'),
],
  • Read vendor/socialiteproviders/zoho/src/Provider.php before writing any app code. It is short, and it is the only authoritative answer to: which config keys are read, which scopes are requested by default, which host serves the profile, and how Zoho's PascalCase payload is mapped onto Socialite's User. The docs for this package are thin; the source is not.
  • First use case: the login redirect/callback pair — two routes, one controller, then Socialite::driver('zoho')->user() in tinker. Get that green before you think about token storage or calling Zoho APIs.

Implementation Patterns

  • The canonical flow. Redirect with return Socialite::driver('zoho')->redirect();, then in the callback $user = Socialite::driver('zoho')->user(); and look up/create your local user. Resolve by ZUID ($user->getId()), not by email — Zoho emails can change and can be unverified.
  • Scope selection is the real configuration decision. The default scopes in the provider are chosen for profile retrieval only. If you need Zoho CRM, Books, Desk, etc. later, add those product scopes (e.g. ZohoCRM.modules.contacts.READ) via ->scopes([...]) or ->setScopes([...]), and register the same scopes on the client in the Zoho API console first. Zoho rejects unregistered scopes outright with invalid_scope.
  • Data centers are a per-user property, not a per-app setting. Zoho accounts are partitioned by DC; an eu account cannot authenticate against accounts.zoho.com. If your user base is single-region, hardcode domain in config. If it isn't, resolve the driver per tenant/region and build it explicitly rather than relying on the singleton:
$provider = Socialite::buildProvider(
    \SocialiteProviders\Zoho\Provider::class,
    config('services.zoho_eu') // same shape, domain => 'eu'
);
  • Persist tokens at login time. Store the access token and refresh token on the identity record when the callback fires. Zoho access tokens are short-lived (~1 hour); without the refresh token the integration is a one-shot login and nothing more. $user->accessTokenResponseBody (Socialite 5.x) gives you the raw token response if the provider doesn't expose refresh tokens directly.
  • Wrap the driver behind a thin application service (ZohoAuthenticator or similar) with redirect(): RedirectResponse and userFromCallback(): ZohoIdentity. This gives you one place to handle DC resolution, scope lists, and the eventual replacement of this package — which, given its release history, you should assume will happen.
  • Use stateless() for API/mobile flows. Session-based state validation will fail on any client that doesn't maintain the cookie jar.
  • Test through a fake, not through HTTP. Socialite's internals use the framework's HTTP client in a way that's awkward to Http::fake() at the driver boundary. Mock the Socialite facade (Socialite::shouldReceive('driver->user')) or, better, fake your own ZohoAuthenticator interface and keep a single integration test that exercises the real driver against a stubbed Guzzle handler.

Gotchas and Tips

  • Data-center mismatch is the #1 failure and it looks like a bug in your code. Wrong domain produces a failed token exchange or a profile with null fields, not a clear "wrong data center" error. Confirm which DC each test account lives in before debugging anything else.
  • Zoho uses Zoho-oauthtoken <token>, not Bearer <token>. The provider handles this for you for the profile call — but the moment you write your own refresh or CRM client call, using Bearer gets you a 401 with no useful body.
  • Refresh tokens require access_type=offline (and usually prompt=consent). Without them Zoho issues a short-lived access token only. Pass them on the redirect: Socialite::driver('zoho')->with(['access_type' => 'offline', 'prompt' => 'consent'])->redirect();. Zoho also rotates/limits refresh tokens per client — re-consenting on every login can invalidate earlier ones, so guard against re-issuing on every request.
  • The token endpoint needs the right headers. Zoho returns JSON only when the request is application/x-www-form-urlencoded with Accept: application/json; otherwise you can get an HTML redirect or an empty body. Also, Zoho sometimes returns HTTP 200 with an error key in the JSON — Socialite's getAccessToken will fail with a generic InvalidArgumentException, so log the raw body when the exchange fails.
  • Email and getId() may be null/empty if the profile scope wasn't granted. Check EmailVerified (Zoho returns it) before trusting an email match for account linking, and always fall back to ZUID.
  • This package is stale — treat it as a fork candidate. Last release was 2020-12-01 (4.1.0, "Support PHP 8"). That predates Socialite 5.x signature changes and the PHP 8.1/8.2 deprecation waves. Pin the version in composer.lock, and if you hit return-type or ?param = null deprecations, vendor the ~150 lines into app/Socialite/ZohoProvider.php and register with Socialite::extend('zoho', ...) rather than fighting the dependency.
  • Redirect URI must match the Zoho console byte-for-byte, including trailing slash and scheme. Trailing-slash drift between .env values is a common source of invalid_client / redirect-mismatch errors.
  • Always php artisan config:clear after deploying credential or domain changes. Cached config from a previous data center is a genuinely confusing failure mode.
  • Debugging checklist, in order: dump the authorization URL and confirm domain, scope, and redirect_uri query params; log the raw token response body; then log the raw profile array before it reaches mapUserToObject. Those three dumps localize nearly every failure.
  • Extension points worth knowing before you fork: Socialite::extend() for a custom driver, Socialite::buildProvider() for per-tenant instances, and ->with() for extra authorization parameters. Those three cover most of what "I need to change the package's behaviour" actually means in practice.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core