Airbyte source connector for extracting financial and accounting data from Xero's cloud-based accounting platform.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
OAuth2 authentication implementation for the Airbyte Source Xero connector. This module handles secure authentication with Xero's API, including automatic token refresh, rate limiting, and error handling.
import base64
import logging
from typing import Any, Mapping
import backoff
import requests
from airbyte_cdk.sources.streams.http.exceptions import DefaultBackoffException
from airbyte_cdk.sources.streams.http.requests_native_auth import SingleUseRefreshTokenOauth2AuthenticatorCustom OAuth2 authenticator that extends Airbyte CDK's standard OAuth2 implementation with Xero-specific requirements for token refresh and authentication headers.
class XeroSingleUseRefreshTokenOauth2Authenticator(SingleUseRefreshTokenOauth2Authenticator):
"""
OAuth2 authenticator for Xero API with custom token refresh handling.
Inherits from airbyte_cdk SingleUseRefreshTokenOauth2Authenticator and
implements Xero-specific authentication patterns including Basic Auth
headers for token refresh and specialized retry logic.
"""
def build_refresh_request_body(self) -> Mapping[str, Any]:
"""
Build request body for OAuth2 token refresh.
Removes client_id and client_secret from request body as Xero
requires these in the Authorization header instead.
Returns:
Request body mapping without client credentials
"""
def build_refresh_request_headers(self) -> Mapping[str, Any]:
"""
Build request headers for OAuth2 token refresh.
Creates Basic Authentication header using client_id and client_secret
as required by Xero's OAuth2 implementation.
Returns:
Headers mapping with Authorization header containing Basic auth credentials
"""
def _get_refresh_access_token_response(self):
"""
Execute token refresh request with Xero-specific retry logic.
Implements backoff and retry for 429 (rate limiting) and 500+
(server error) status codes with exponential backoff strategy.
Returns:
JSON response from token refresh endpoint
Raises:
requests.exceptions.RequestException: For unrecoverable HTTP errors
"""The Xero OAuth2 flow follows these steps:
# The authenticator automatically handles:
AuthenticationHeaders = {
"Authorization": "Bearer {access_token}", # OAuth2 bearer token
"Xero-Tenant-Id": "{tenant_id}", # Required for all Xero API calls
"Accept": "application/json", # Content type specification
"User-Agent": "Airbyte/{version}" # Client identification
}from source_xero.oauth import XeroSingleUseRefreshTokenOauth2Authenticator
# Configuration with OAuth2 credentials
config = {
"authentication": {
"client_id": "your-xero-client-id",
"client_secret": "your-xero-client-secret",
"refresh_token": "your-refresh-token",
"access_token": "current-access-token",
"token_expiry_date": "2024-12-31T23:59:59Z"
},
"tenant_id": "your-xero-tenant-id"
}
# Create authenticator instance
authenticator = XeroSingleUseRefreshTokenOauth2Authenticator(
connector_config=config,
token_refresh_endpoint="https://identity.xero.com/connect/token",
client_id=config["authentication"]["client_id"],
client_secret=config["authentication"]["client_secret"],
access_token_config_path=["authentication", "access_token"],
refresh_token_config_path=["authentication", "refresh_token"],
token_expiry_date_config_path=["authentication", "token_expiry_date"]
)import requests
# The authenticator can be used with any HTTP client
session = requests.Session()
# Apply authentication to request
authenticated_request = authenticator.apply(
session.prepare_request(
requests.Request("GET", "https://api.xero.com/api.xro/2.0/Accounts")
)
)
# Execute authenticated request
response = session.send(authenticated_request)# Token refresh is handled automatically, but you can monitor it:
try:
# This will automatically refresh token if needed
response = authenticator._get_refresh_access_token_response()
print("Token refreshed successfully")
except requests.exceptions.RequestException as e:
print(f"Token refresh failed: {e}")The authenticator implements exponential backoff for rate limiting:
# Automatic retry with backoff for rate limiting
# - Initial delay: 1 second
# - Maximum retries: 3 attempts
# - Backoff multiplier: 2x
# - Maximum delay: 60 secondsServer errors are handled with retry logic:
# Retry for server errors (500, 502, 503, 504)
# - Same backoff strategy as rate limiting
# - Distinguishes between client (4xx) and server (5xx) errors
# - Only retries recoverable server errors# Token expiry and invalid credentials handling:
# - Automatic token refresh on 401 responses
# - Fallback to refresh_token if access_token is invalid
# - Error reporting for invalid refresh_token# Secure token handling practices:
TokenSecurity = {
"access_token": "Treated as secret, never logged",
"refresh_token": "Single-use, invalidated after refresh",
"client_secret": "Never included in request bodies",
"token_expiry": "Validated before each request"
}To use this authenticator, you need:
The connector requires these OAuth2 scopes for full functionality:
RequiredScopes = [
"accounting.transactions", # Access to financial transactions
"accounting.contacts", # Access to contact information
"accounting.settings", # Access to account settings
"accounting.attachments" # Access to document attachments
]Install with Tessl CLI
npx tessl i tessl/pypi-source-xero