Weave Code
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

Oauth2 Xero Laravel Package

calcinai/oauth2-xero

OAuth 2.0 provider for Xero built on the League OAuth2 Client. Supports the authorization code flow, scope configuration, fetching the authenticated user (OpenID) and retrieving authorized Xero tenants for making API requests.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Install the Package:
    composer require calcinai/oauth2-xero
    
  2. Configure Xero Credentials: Register your app in the Xero Developer Portal to obtain clientId, clientSecret, and set a redirectUri.
  3. Initialize the Provider:
    use Calcinai\OAuth2\Client\Provider\Xero;
    
    $provider = new Xero([
        'clientId'     => env('XERO_CLIENT_ID'),
        'clientSecret' => env('XERO_CLIENT_SECRET'),
        'redirectUri'  => env('XERO_REDIRECT_URI'),
    ]);
    
  4. First Use Case: Authorization Code Flow Redirect users to Xero for authentication:
    $authUrl = $provider->getAuthorizationUrl([
        'scope' => 'openid email profile accounting.transactions',
    ]);
    header('Location: ' . $authUrl);
    
    Handle the callback in a Laravel route/controller to exchange the code for a token and fetch user/tenant data.

Where to Look First

  • README.md: Follow the Authorization Code Flow example for basic setup.
  • Provider Class: \Calcinai\OAuth2\Client\Provider\Xero (extends League’s AbstractProvider).
  • Xero Scopes: Xero’s Scope Documentation for API permissions.
  • Laravel Integration: Use Laravel’s session() helper for CSRF state validation (as shown in the example).

Implementation Patterns

Usage Patterns

1. Authorization Code Flow (Web Apps)

  • Laravel Route:
    Route::get('/xero/auth', function () {
        $provider = resolve(Xero::class);
        $authUrl = $provider->getAuthorizationUrl(['scope' => 'accounting.transactions']);
        session(['oauth2state' => $provider->getState()]);
        return redirect($authUrl);
    });
    
  • Callback Handler:
    Route::get('/xero/callback', function (Request $request) {
        $provider = resolve(Xero::class);
        if (!session('oauth2state') || $request->state !== session('oauth2state')) {
            throw new \Exception('Invalid state');
        }
        $token = $provider->getAccessToken('authorization_code', [
            'code' => $request->code,
        ]);
        $user = $provider->getResourceOwner($token);
        $tenants = $provider->getTenants($token);
        // Store token/tenants in session or database
    });
    

2. Token Refresh

  • Automate refreshes using Laravel’s scheduler or queue:
    $refreshToken = $storedToken->refresh_token;
    $newToken = $provider->getAccessToken('refresh_token', [
        'refresh_token' => $refreshToken,
    ]);
    $storedToken->update(['access_token' => $newToken->getToken(), 'expires' => $newToken->getExpires()]);
    

3. Multi-Tenant Access

  • Fetch and iterate over tenants:
    $tenants = $provider->getTenants($token);
    foreach ($tenants as $tenant) {
        $api = new XeroAPI($tenant->tenantId, $token);
        $invoices = $api->getInvoices();
    }
    

4. PKCE (Progressive Web Apps)

  • Enable PKCE in the provider (if supported in future versions):
    $provider = new Xero([
        'clientId' => env('XERO_CLIENT_ID'),
        'redirectUri' => env('XERO_REDIRECT_URI'),
        'usePKCE' => true, // Check if this option exists in the package
    ]);
    

Workflows

User Authentication Flow

  1. Redirect to Xero: Generate auth URL with required scopes.
  2. Callback Handling: Validate state, exchange code for token.
  3. Store Token: Save access_token, refresh_token, and expires in the database.
  4. Fetch Tenants: Use getTenants() to list accessible orgs.
  5. API Calls: Use the token to make requests to Xero’s API (e.g., via Guzzle).

Token Management

  • Store Tokens: Use Laravel’s database or cache to persist tokens.
    $tokenData = [
        'access_token' => $token->getToken(),
        'refresh_token' => $token->getRefreshToken(),
        'expires' => $token->getExpires(),
        'scopes' => $token->getScopes(),
    ];
    Token::updateOrCreate(['user_id' => auth()->id()], $tokenData);
    
  • Refresh Logic: Check token expiry before API calls and refresh if needed.
    if (Carbon::now()->gt(Carbon::parse($token->expires))) {
        $newToken = $provider->getAccessToken('refresh_token', [
            'refresh_token' => $token->refresh_token,
        ]);
        // Update stored token
    }
    

Laravel Service Provider Integration

  • Bind the provider to Laravel’s container:
    // app/Providers/XeroServiceProvider.php
    public function register()
    {
        $this->app->singleton(Xero::class, function ($app) {
            return new \Calcinai\OAuth2\Client\Provider\Xero([
                'clientId' => config('services.xero.client_id'),
                'clientSecret' => config('services.xero.client_secret'),
                'redirectUri' => config('services.xero.redirect_uri'),
            ]);
        });
    }
    

Integration Tips

  1. Scopes: Always request the minimal required scopes (e.g., accounting.transactions instead of openid if not needed).
  2. Error Handling: Wrap provider calls in try-catch blocks to handle OAuth exceptions (e.g., League\OAuth2\Client\Provider\Exception\IdentityProviderException).
  3. Logging: Log OAuth interactions for debugging:
    try {
        $token = $provider->getAccessToken('authorization_code', [...]);
    } catch (\Exception $e) {
        \Log::error('Xero OAuth Error', ['error' => $e->getMessage()]);
        throw $e;
    }
    
  4. Testing: Use Laravel’s Http and Session facades to mock OAuth flows in tests:
    $this->get('/xero/auth')->assertRedirect();
    $this->get('/xero/callback?code=test&state=' . session('oauth2state'))
         ->assertSessionHas('xero_token');
    
  5. API Calls: Use Laravel’s HTTP client to make authenticated requests:
    $response = Http::withToken($token->getToken())
        ->get('https://api.xero.com/api.xro/2.0/Invoices');
    

Gotchas and Tips

Pitfalls

  1. State Validation:

    • Issue: Missing or mismatched state parameters can break the OAuth flow.
    • Fix: Always store and validate the state in the session:
      if (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
          exit('Invalid state');
      }
      
    • Laravel Tip: Use Laravel’s session() helper or middleware to manage state.
  2. Token Expiry:

    • Issue: Access tokens expire (typically 1 hour), requiring refreshes.
    • Fix: Implement a token refresh strategy (e.g., queue a job before expiry).
  3. PKCE Limitations:

    • Issue: PKCE support is WIP (as of v1.3.0). If using SPAs or mobile apps, monitor for updates or implement a custom solution.
    • Workaround: Use the traditional flow for now and switch to PKCE later.
  4. Tenant Context:

    • Issue: getTenants() returns all accessible orgs, but API calls require a specific tenant ID.
    • Fix: Store the selected tenant ID in the session or user model:
      auth()->user()->update(['xero_tenant_id' => $tenant->tenantId]);
      
  5. Scope Restrictions:

    • Issue: Xero may reject requests with insufficient scopes.
    • Fix: Test scopes in the Xero API Playground before implementation.
  6. Redirect URI Mismatch:

    • Issue: Xero’s redirectUri must exactly match the registered URI (including http vs. https).
    • Fix: Use environment variables and validate:
      if ($provider->getRedirectUri() !== env('XERO_REDIRECT_URI')) {
          throw new \Exception('Redirect URI mismatch');
      }
      

Debugging

  1. OAuth Errors:
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.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor