0
# OAuth2 Authentication
1
2
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.
3
4
## Core Imports
5
6
```python
7
import base64
8
import logging
9
from typing import Any, Mapping
10
import backoff
11
import requests
12
from airbyte_cdk.sources.streams.http.exceptions import DefaultBackoffException
13
from airbyte_cdk.sources.streams.http.requests_native_auth import SingleUseRefreshTokenOauth2Authenticator
14
```
15
16
## Capabilities
17
18
### Xero OAuth2 Authenticator
19
20
Custom OAuth2 authenticator that extends Airbyte CDK's standard OAuth2 implementation with Xero-specific requirements for token refresh and authentication headers.
21
22
```python { .api }
23
class XeroSingleUseRefreshTokenOauth2Authenticator(SingleUseRefreshTokenOauth2Authenticator):
24
"""
25
OAuth2 authenticator for Xero API with custom token refresh handling.
26
27
Inherits from airbyte_cdk SingleUseRefreshTokenOauth2Authenticator and
28
implements Xero-specific authentication patterns including Basic Auth
29
headers for token refresh and specialized retry logic.
30
"""
31
32
def build_refresh_request_body(self) -> Mapping[str, Any]:
33
"""
34
Build request body for OAuth2 token refresh.
35
36
Removes client_id and client_secret from request body as Xero
37
requires these in the Authorization header instead.
38
39
Returns:
40
Request body mapping without client credentials
41
"""
42
43
def build_refresh_request_headers(self) -> Mapping[str, Any]:
44
"""
45
Build request headers for OAuth2 token refresh.
46
47
Creates Basic Authentication header using client_id and client_secret
48
as required by Xero's OAuth2 implementation.
49
50
Returns:
51
Headers mapping with Authorization header containing Basic auth credentials
52
"""
53
54
def _get_refresh_access_token_response(self):
55
"""
56
Execute token refresh request with Xero-specific retry logic.
57
58
Implements backoff and retry for 429 (rate limiting) and 500+
59
(server error) status codes with exponential backoff strategy.
60
61
Returns:
62
JSON response from token refresh endpoint
63
64
Raises:
65
requests.exceptions.RequestException: For unrecoverable HTTP errors
66
"""
67
```
68
69
## Authentication Flow
70
71
### Token Refresh Process
72
73
The Xero OAuth2 flow follows these steps:
74
75
1. **Initial Authentication**: Uses provided access_token and refresh_token
76
2. **Token Validation**: Checks token expiry before API requests
77
3. **Automatic Refresh**: Refreshes expired tokens using refresh_token
78
4. **Retry Logic**: Handles rate limiting and server errors during refresh
79
5. **Header Management**: Applies proper authentication headers to API requests
80
81
### Request Authentication
82
83
```python { .api }
84
# The authenticator automatically handles:
85
AuthenticationHeaders = {
86
"Authorization": "Bearer {access_token}", # OAuth2 bearer token
87
"Xero-Tenant-Id": "{tenant_id}", # Required for all Xero API calls
88
"Accept": "application/json", # Content type specification
89
"User-Agent": "Airbyte/{version}" # Client identification
90
}
91
```
92
93
## Usage Examples
94
95
### Manual Authenticator Creation
96
97
```python
98
from source_xero.oauth import XeroSingleUseRefreshTokenOauth2Authenticator
99
100
# Configuration with OAuth2 credentials
101
config = {
102
"authentication": {
103
"client_id": "your-xero-client-id",
104
"client_secret": "your-xero-client-secret",
105
"refresh_token": "your-refresh-token",
106
"access_token": "current-access-token",
107
"token_expiry_date": "2024-12-31T23:59:59Z"
108
},
109
"tenant_id": "your-xero-tenant-id"
110
}
111
112
# Create authenticator instance
113
authenticator = XeroSingleUseRefreshTokenOauth2Authenticator(
114
connector_config=config,
115
token_refresh_endpoint="https://identity.xero.com/connect/token",
116
client_id=config["authentication"]["client_id"],
117
client_secret=config["authentication"]["client_secret"],
118
access_token_config_path=["authentication", "access_token"],
119
refresh_token_config_path=["authentication", "refresh_token"],
120
token_expiry_date_config_path=["authentication", "token_expiry_date"]
121
)
122
```
123
124
### Integration with HTTP Requests
125
126
```python
127
import requests
128
129
# The authenticator can be used with any HTTP client
130
session = requests.Session()
131
132
# Apply authentication to request
133
authenticated_request = authenticator.apply(
134
session.prepare_request(
135
requests.Request("GET", "https://api.xero.com/api.xro/2.0/Accounts")
136
)
137
)
138
139
# Execute authenticated request
140
response = session.send(authenticated_request)
141
```
142
143
### Token Refresh Handling
144
145
```python
146
# Token refresh is handled automatically, but you can monitor it:
147
try:
148
# This will automatically refresh token if needed
149
response = authenticator._get_refresh_access_token_response()
150
print("Token refreshed successfully")
151
except requests.exceptions.RequestException as e:
152
print(f"Token refresh failed: {e}")
153
```
154
155
## Error Handling
156
157
### Rate Limiting (429 Responses)
158
159
The authenticator implements exponential backoff for rate limiting:
160
161
```python
162
# Automatic retry with backoff for rate limiting
163
# - Initial delay: 1 second
164
# - Maximum retries: 3 attempts
165
# - Backoff multiplier: 2x
166
# - Maximum delay: 60 seconds
167
```
168
169
### Server Errors (500+ Responses)
170
171
Server errors are handled with retry logic:
172
173
```python
174
# Retry for server errors (500, 502, 503, 504)
175
# - Same backoff strategy as rate limiting
176
# - Distinguishes between client (4xx) and server (5xx) errors
177
# - Only retries recoverable server errors
178
```
179
180
### Authentication Errors (401 Responses)
181
182
```python
183
# Token expiry and invalid credentials handling:
184
# - Automatic token refresh on 401 responses
185
# - Fallback to refresh_token if access_token is invalid
186
# - Error reporting for invalid refresh_token
187
```
188
189
## Security Considerations
190
191
### Credential Management
192
193
- **Client Secrets**: Never logged or exposed in error messages
194
- **Tokens**: Stored securely and rotated automatically
195
- **Refresh Tokens**: Single-use pattern prevents token replay attacks
196
- **HTTPS Only**: All authentication requests use secure HTTPS
197
198
### Token Storage
199
200
```python
201
# Secure token handling practices:
202
TokenSecurity = {
203
"access_token": "Treated as secret, never logged",
204
"refresh_token": "Single-use, invalidated after refresh",
205
"client_secret": "Never included in request bodies",
206
"token_expiry": "Validated before each request"
207
}
208
```
209
210
## Xero API Requirements
211
212
### OAuth2 Configuration
213
214
To use this authenticator, you need:
215
216
1. **Xero Developer Account**: Register at https://developer.xero.com
217
2. **Custom Connection**: Create a custom connection (production) or demo company (testing)
218
3. **OAuth2 App**: Configure OAuth2 application with appropriate scopes
219
4. **Credentials**: Obtain client_id, client_secret, and initial tokens
220
221
### Required Scopes
222
223
The connector requires these OAuth2 scopes for full functionality:
224
225
```python
226
RequiredScopes = [
227
"accounting.transactions", # Access to financial transactions
228
"accounting.contacts", # Access to contact information
229
"accounting.settings", # Access to account settings
230
"accounting.attachments" # Access to document attachments
231
]
232
```
233
234
### API Limitations
235
236
- **Rate Limits**: 60 API calls per minute per tenant
237
- **Token Lifetime**: Access tokens expire after 30 minutes
238
- **Refresh Tokens**: Single-use, must be stored after each refresh
239
- **Tenant Scope**: Each token set is limited to one Xero organization