cashdash-pro / zaar
A robust Laravel package for Shopify authentication handling both embedded and external app flows. Features JWT session management, seamless online/offline token handling, and automatic re-authentication for embedded apps. Built for Laravel 10+ and PHP 8.2+.
Fund package maintenance!
CashDash
Installs: 1 177
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 1
Forks: 0
Open Issues: 0
Requires
- php: ^8.2
- firebase/php-jwt: ^6.10
- illuminate/contracts: ^10.0||^11.0|^12.0
- laravel/prompts: ^0.1.0|^0.2.0|^0.3.0|^1.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^2.9
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^9.0.0||^8.22.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
This package is auto-updated.
Last update: 2025-05-08 09:36:14 UTC
README
Overview
Zaar is a Laravel package that simplifies Shopify authentication for your Laravel applications. It provides seamless integration for both embedded and external Shopify apps, handling session management, authentication flows, and user management.
Features
- 🔒 Secure authentication for embedded and external Shopify apps
- 🔄 Session management with online/offline token support
- 🛡️ Built-in CSRF protection configuration
- 🔌 Easy integration with Laravel's authentication system
- 📱 Support for both web and API authentication
- 🎯 Public app endpoints support
- ⚡ Automatic Axios interceptor setup for session tokens
Installation
composer require cashdash-pro/zaar php artisan zaar:install
The install command will:
- Publish the configuration file
- Create necessary migrations
- Set up Axios interceptors (optional)
- Add the
@zaarHead
directive to your Blade layout (optional)
Core Concepts
Authentication Flow and Events
Zaar implements different authentication strategies but follows a consistent flow through the run()
method. Here's how it works:
// From HasAuthEvents trait public function run(?Authenticatable $user): ?Authenticatable { return $this ->withOnlineSession($user) // Step 1 ->withUser() // Step 2 ->withDomain() // Step 3 ->when( // Step 4 (Conditional) Zaar::sessionType() === SessionType::OFFLINE, fn (AuthFlow $auth) => $auth->withOfflineSession() ) ->mergeSessions() // Step 5 ->bindData() // Step 6 ->withShopifyModel() // Step 7 ->dispatchEvents() // Step 8 ->getUser(); // Final Result }
Embedded Apps (Admin Panel)
-
Online Session (
withOnlineSession
)- Validates Shopify's session token
- Extracts user and shop information
- Fires
OnlineSessionLoaded
-
User Resolution (
withUser
)- Uses session data to find/create user
- Can be customized via
findUserUsing
/createUserUsing
-
Domain Resolution (
withDomain
)- Uses domain from session token by default
- Can be overridden via
setShopifyDomain
(good for store switching)
-
Offline Session (Conditional)
- Only if configured for offline tokens
- Fires
OfflineSessionLoaded
External Apps (API/Standalone)
The same flow is followed, but with key differences:
-
Online Session (
withOnlineSession
)- Tries to load the online session from the authenticated user
- Fires
OnlineSessionLoaded
if successful
-
User Resolution (
withUser
)- Uses your existing authenticated user (configured via 'guards' in zaar.php)
-
Domain Resolution (
withDomain
)- Must be explicitly provided via
setShopifyDomain
- Critical for determining which store to use
- Must be explicitly provided via
-
Offline Session (Conditional)
- Required for external apps
- Fires
OfflineSessionLoaded
Common Steps (Both Types)
After strategy-specific steps:
-
Session Merging (
mergeSessions
)- Combines available session data
- Creates unified access token available via
Zaar::session()
-
Data Binding (
bindData
)- Makes sessions available via container
- Enables
Zaar::session()
,Zaar::onlineSession()
,Zaar::offlineSession()
helpers
-
Store Loading (
withShopifyModel
)- Loads/creates Shopify store record
- Fires
ShopifyTenantLoaded
-
Event Dispatch (
dispatchEvents
)- Fires all accumulated events
- Ends with
SessionAuthenticated
This flow ensures consistent behavior while accommodating the different requirements of embedded and external apps.
Event Flow and Importance
The events in Zaar are fired in a specific order through dispatchEvents()
, each serving a crucial purpose:
-
OnlineSessionLoaded
- Fired when an online session token is validated
- Contains user identity from Shopify
- Perfect for tracking user activity or session starts
- Only fires for embedded apps or when online token exists
-
OfflineSessionLoaded
- Fired when offline token is available
- Critical for setting up API access
- Use this to initialize API clients or background job configurations
- Contains the permanent access token
-
ShopifyTenantLoaded
- Most important event for multi-tenant apps
- Fired when the Shopify store model is loaded
- This is your chance to:
- Set up database connections
- Initialize tenant-specific services
- Configure API settings
- Load store preferences
- Always fires regardless of authentication type
-
SessionAuthenticated
- Final event with complete context
- Provides access to:
- Session data (merged online/offline)
- Shopify store model
- Authenticated user
- Perfect for:
- Logging successful authentications
- Starting background processes
- Initializing store-specific features
Additional Critical Events
These events fire during specific operations:
-
ShopifyFoundEvent
- Fires when an existing store is found
- Critical for:
- Updating store metadata
- Syncing store settings
- Checking for plan changes
- Validating store status
-
ShopifyCreated
- Fires for new store installations
- Use for:
- Initial store setup
- Creating default settings
- Welcome notifications
- First-time configurations
-
ShopifyUserCreated
- Fires when a new user is created
- Perfect for:
- Setting up user preferences
- Sending welcome emails
- Initial role assignment
-
ShopifyOnlineSessionCreated
/ShopifyOfflineSessionCreated
- Fire when new sessions are created
- Use for:
- Token storage
- Session monitoring
- Access logging
The event system is designed to give you complete control over the authentication and initialization process. Each event provides specific context and timing for different aspects of your application's setup.
Domain Resolution
The most important configuration in Zaar is setting up how shop domains are resolved. This controls which store is loaded for both embedded and external apps:
use CashDash\Zaar\Facades\Zaar; Zaar::setShopifyDomain(function (Request $request, User $user, ?string $current_domain) { // For external apps (outside Shopify Admin), you MUST return the shop domain if (!Zaar::isEmbedded()) { return $request->header('X-Shopify-Shop-Domain'); // or from query params: return $request->get('shop'); // or from route parameters: return $request->route('shop'); } // For embedded apps, you can override the domain to switch stores // The $user parameter lets you check permissions if ($user->can('access', $otherStore)) { return 'other-store.myshopify.com'; } // Return null to use the domain from the session token return null; });
This resolver is called during the authentication flow and determines which store's data and sessions are loaded. For external apps, you must return a domain. For embedded apps, returning null
will use the domain from Shopify's session token.
Usage
Middleware Types
-
Embedded Apps (
shopify.web
)- For apps within Shopify Admin iframe
- Handles session token exchange
- Includes necessary headers
- Example:
Route::middleware('shopify.web')->group(function () { Route::get('/app', function () { // Your embedded app's main entry point }); });
-
External Apps (
shopify
)- For standalone/API applications
- Uses offline tokens
- No iframe handling
- Example:
Route::middleware('shopify')->group(function () { Route::get('/api/products', function () { $session = Zaar::offlineSession(); }); });
-
Public Endpoints (
shopify.public
)- For public-facing endpoints
- Limited shop context
- Example:
Route::middleware('shopify.public')->group(function () { Route::get('/public/products', function () { // Public endpoint logic }); });
Session Management
-
Accessing Sessions
// Get current session $session = Zaar::session(); $accessToken = $session->accessToken; $shop = $session->shop; // Check session type if (Zaar::sessionType() === SessionType::ONLINE) { // Online session logic }
-
Manual Session Control
// Start session for different store Zaar::startSessionManually($newShop, $user); // Handle expired sessions if (!Zaar::sessionStarted()) { Zaar::clearExpiredSessionsAndReauthenticate($domain); }
User Management
use CashDash\Zaar\Dtos\OnlineSessionData; // Custom user lookup Zaar::findUserUsing(function (OnlineSessionData $session) { return User::where('email', $session->email) ->orWhere('shopify_id', $session->sub) ->first(); }); // Custom user creation Zaar::createUserUsing(function (OnlineSessionData $session) { return User::create([ 'name' => $session->name, 'email' => $session->email, 'shopify_id' => $session->sub ]); });
Configuration
Environment Setup
SHOPIFY_CLIENT_ID=your_client_id SHOPIFY_CLIENT_SECRET=your_client_secret SHOPIFY_API_VERSION=2024-01 SHOPIFY_SESSION_TYPE=offline # or online SHOPIFY_SCOPES=read_products,write_products SHOPIFY_REDIRECT_URI=https://your-app.com/auth/callback
Package Configuration
// config/zaar.php return [ 'shopify_app' => [ 'client_id' => env('SHOPIFY_CLIENT_ID'), 'client_secret' => env('SHOPIFY_CLIENT_SECRET'), 'scopes' => env('SHOPIFY_SCOPES'), 'redirect' => env('SHOPIFY_REDIRECT_URI'), 'api_version' => env('SHOPIFY_API_VERSION', '2024-01'), 'session_type' => env('SHOPIFY_SESSION_TYPE', 'OFFLINE'), ], 'guards' => 'web', 'force_embedded_https' => true, 'disabled_csrf_routes' => ['*'], 'default_session_repository' => 'database', ];
Frontend Integration
The package automatically sets up Axios interceptors:
window.axios.interceptors.request.use(async function (config) { if (!window.shopify) { return config; } const token = await window.shopify.idToken(); config.headers['Authorization'] = `Bearer ${token}`; config.headers['X-Referrer'] = window.location.href; return config; });
Advanced Usage
Repository Configuration
'repositories' => [ 'user' => [ 'type' => YourUserRepository::class, 'model' => User::class, 'email_column' => 'email', ], 'shopify' => [ 'type' => YourShopifyRepository::class, 'model' => Shopify::class, 'shop_domain_column' => 'domain', ], 'sessions' => [ 'database' => [ 'type' => YourSessionRepository::class, 'model' => ShopifySession::class, ], ], ],
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.