OAuth Handlers
Location: /inc/Core/OAuth/
Overview
Data Machine uses a unified base class architecture for authentication providers, eliminating code duplication across all OAuth 1.0a and OAuth 2.0 implementations. All authentication providers extend standardized base classes that centralize option storage, configuration management, and authentication validation.
Base Authentication Architecture (@since v0.2.6)
BaseAuthProvider
Location: /inc/Core/OAuth/BaseAuthProvider.php
Since: v0.2.6
Abstract base class providing core functionality for all authentication providers.
Key Features:
- Centralized option storage and retrieval via WordPress options
- Unified callback URL generation
- Configuration and authentication state checking
- Account data management (save, retrieve, clear)
Core Methods:
// Abstract methods (must be implemented by child classes)
abstract public function get_config_fields(): array;
abstract public function is_authenticated(): bool;
// Concrete methods (inherited by all providers)
public function __construct(string $provider_slug);
public function is_configured(): bool;
public function get_callback_url(): string;
public function get_account(): array;
public function get_config(): array;
public function save_account(array $data): bool;
public function save_config(array $data): bool;
public function clear_account(): bool;
public function get_account_details(): ?array;
Storage Pattern:
All providers store data in WordPress options using a consistent structure:
// Data stored in 'datamachine_auth_data' option
[
'provider_slug' => [
'config' => [
'client_id' => '...',
'client_secret' => '...'
],
'account' => [
'access_token' => '...',
'refresh_token' => '...',
'user_id' => '...'
]
]
]
BaseOAuth1Provider
Location: /inc/Core/OAuth/BaseOAuth1Provider.php
Since: v0.2.6
Base class for OAuth 1.0a authentication providers extending BaseAuthProvider.
Features:
- OAuth1Handler instance for three-legged flow
- Standardized configuration validation (api_key, api_secret)
- Authentication validation (access_token, access_token_secret)
Abstract Methods:
abstract public function get_authorization_url(): string;
abstract public function handle_oauth_callback();
Providers Using BaseOAuth1Provider:
- TwitterAuth (OAuth 1.0a for tweet publishing)
Example Implementation:
class TwitterAuth extends BaseOAuth1Provider {
public function __construct() {
parent::__construct('twitter');
}
public function get_config_fields(): array {
return [
'api_key' => [
'label' => __('API Key', 'datamachine'),
'type' => 'text',
'required' => true
],
'api_secret' => [
'label' => __('API Secret', 'datamachine'),
'type' => 'text',
'required' => true
]
];
}
public function get_authorization_url(): string {
$config = $this->get_config();
$request_token = $this->oauth1->get_request_token(
'https://api.twitter.com/oauth/request_token',
$config['api_key'],
$config['api_secret'],
$this->get_callback_url(),
'twitter'
);
return $this->oauth1->get_authorization_url(
'https://api.twitter.com/oauth/authenticate',
$request_token['oauth_token'],
'twitter'
);
}
public function handle_oauth_callback() {
$config = $this->get_config();
$this->oauth1->handle_callback(
'twitter',
'https://api.twitter.com/oauth/access_token',
$config['api_key'],
$config['api_secret'],
function($access_token_data) {
return [
'access_token' => $access_token_data['oauth_token'],
'access_token_secret' => $access_token_data['oauth_token_secret'],
'user_id' => $access_token_data['user_id'],
'screen_name' => $access_token_data['screen_name']
];
},
[$this, 'save_account']
);
}
}
BaseOAuth2Provider
Location: /inc/Core/OAuth/BaseOAuth2Provider.php
Since: v0.2.0 (enhanced in v0.2.6)
Base class for OAuth 2.0 authentication providers extending BaseAuthProvider.
Features:
- OAuth2Handler instance for authorization code flow
- Standardized configuration validation (client_id, client_secret)
- Authentication validation (access_token presence)
- Account details formatting with username, scope, refresh timestamps
Abstract Methods:
abstract public function get_config_fields(): array;
abstract public function get_authorization_url(): string;
abstract public function handle_oauth_callback();
Optional Methods:
public function refresh_token(): bool; // Token refresh implementation
Providers Using BaseOAuth2Provider:
- RedditAuth (subreddit fetching)
- FacebookAuth (Graph API publishing)
- ThreadsAuth (Meta Threads integration)
- GoogleSheetsAuth (spreadsheet operations, moved to
/inc/Core/OAuth/Providers/in v0.2.6)
Example Implementation:
class RedditAuth extends BaseOAuth2Provider {
public function __construct() {
parent::__construct('reddit');
}
public function get_config_fields(): array {
return [
'client_id' => [
'label' => __('Client ID', 'datamachine'),
'type' => 'text',
'required' => true
],
'client_secret' => [
'label' => __('Client Secret', 'datamachine'),
'type' => 'text',
'required' => true
]
];
}
public function get_authorization_url(): string {
$config = $this->get_config();
$state = $this->oauth2->create_state('reddit');
$params = [
'client_id' => $config['client_id'],
'response_type' => 'code',
'state' => $state,
'redirect_uri' => $this->get_callback_url(),
'duration' => 'permanent',
'scope' => 'identity read'
];
return $this->oauth2->get_authorization_url(
'https://www.reddit.com/api/v1/authorize',
$params
);
}
public function handle_oauth_callback() {
$config = $this->get_config();
$this->oauth2->handle_callback(
'reddit',
'https://www.reddit.com/api/v1/access_token',
[
'grant_type' => 'authorization_code',
'code' => $_GET['code'],
'redirect_uri' => $this->get_callback_url()
],
function($token_data) {
return [
'access_token' => $token_data['access_token'],
'refresh_token' => $token_data['refresh_token'],
'username' => $this->fetch_username($token_data['access_token']),
'scope' => $token_data['scope'],
'token_expires_at' => time() + $token_data['expires_in']
];
},
null,
[$this, 'save_account']
);
}
public function refresh_token(): bool {
$account = $this->get_account();
$config = $this->get_config();
// Refresh logic using $account['refresh_token']
// Update account data via $this->save_account()
return true;
}
}
Bluesky Authentication (BaseAuthProvider Direct Extension)
Provider: BlueskyAuth
Location: /inc/Core/Steps/Publish/Handlers/Bluesky/BlueskyAuth.php
Since: v0.1.0 (updated to extend BaseAuthProvider in v0.2.6)
Bluesky authentication uses app password authentication and extends BaseAuthProvider directly.
Implementation Pattern:
class BlueskyAuth extends BaseAuthProvider {
public function __construct() {
parent::__construct('bluesky');
}
public function get_config_fields(): array {
return [
'username' => [
'label' => __('Bluesky Handle', 'datamachine'),
'type' => 'text',
'required' => true,
'description' => __('Your Bluesky handle (e.g., user.bsky.social)', 'datamachine')
],
'app_password' => [
'label' => __('App Password', 'datamachine'),
'type' => 'password',
'required' => true,
'description' => __('Generate an app password at bsky.app/settings/app-passwords', 'datamachine')
]
];
}
public function is_authenticated(): bool {
$config = $this->get_config();
return !empty($config) &&
!empty($config['username']) &&
!empty($config['app_password']);
}
public function authenticate(): bool {
$config = $this->get_config();
// Validate credentials with API
$session = $this->create_session(
$config['username'],
$config['app_password']
);
if (is_wp_error($session)) {
return false;
}
// Store session data
return $this->save_config([
'username' => $config['username'],
'app_password' => $config['app_password'],
'session_token' => $session['accessJwt'],
'did' => $session['did']
]);
}
}
OAuth Handler Services
OAuth2Handler
Location: /inc/Core/OAuth/OAuth2Handler.php
Centralized OAuth 2.0 authorization code flow handler used by all OAuth2 providers.
Key Methods:
public function create_state(string $provider_key): string;
public function verify_state(string $provider_key, string $state): bool;
public function get_authorization_url(string $auth_url, array $params): string;
public function handle_callback(
string $provider_key,
string $token_url,
array $token_params,
callable $account_details_fn,
?callable $token_transform_fn = null,
?callable $save_fn = null
): bool;
State Management:
- CSRF protection via WordPress transients
- 15-minute expiration window
- Automatic cleanup on verification
Token Exchange:
- Authorization code to access token exchange
- Optional token transformation (e.g., Meta long-lived tokens)
- Account details retrieval via callback
- Storage via custom save function or default filter
OAuth1Handler
Location: /inc/Core/OAuth/OAuth1Handler.php
Centralized OAuth 1.0a three-legged flow handler used by OAuth1 providers.
Key Methods:
public function get_request_token(
string $request_token_url,
string $consumer_key,
string $consumer_secret,
string $callback_url,
string $provider_key = 'oauth1'
): array|WP_Error;
public function get_authorization_url(
string $authorize_url,
string $oauth_token,
string $provider_key = 'oauth1'
): string;
public function handle_callback(
string $provider_key,
string $access_token_url,
string $consumer_key,
string $consumer_secret,
callable $account_details_fn,
?callable $save_fn = null
): bool;
Temporary Token Management:
- Transient storage with provider and token scoping
- 15-minute expiration for request tokens
- Automatic cleanup after exchange
OAuth Providers Directory
Location: /inc/Core/OAuth/Providers/
Since: v0.2.5
Centralized directory for shared OAuth provider implementations used by multiple handlers.
GoogleSheetsAuth
Location: /inc/Core/OAuth/Providers/GoogleSheetsAuth.php
Since: v0.2.0 (moved to Providers/ in v0.2.5, updated to extend BaseOAuth2Provider in v0.2.6)
OAuth2 provider for Google Sheets API access shared by both fetch and publish handlers.
Key Features:
- OAuth2 authentication with offline access
- Automatic token refresh 5 minutes before expiry
- Service access method returning valid access token
- Spreadsheet-specific scopes
Usage Pattern:
use DataMachineCoreOAuthProvidersGoogleSheetsAuth;
class GoogleSheetsFetch extends FetchHandler {
private $auth;
public function __construct() {
$this->auth = new GoogleSheetsAuth();
}
public function fetch($flow_step_config, $job_id) {
$access_token = $this->auth->get_service();
if (is_wp_error($access_token)) {
return $this->errorResponse($access_token->get_error_message());
}
// Use access token for API calls
}
}
Provider Integration Patterns
Configuration Storage
All providers use BaseAuthProvider methods for configuration storage:
// Save configuration
$config_data = [
'client_id' => $client_id,
'client_secret' => $client_secret
];
$this->save_config($config_data);
// Retrieve configuration
$config = $this->get_config();
$client_id = $config['client_id'] ?? '';
Account Data Storage
All providers use BaseAuthProvider methods for account data:
// Save account data
$account_data = [
'access_token' => $access_token,
'refresh_token' => $refresh_token,
'user_id' => $user_id,
'username' => $username
];
$this->save_account($account_data);
// Retrieve account data
$account = $this->get_account();
$access_token = $account['access_token'] ?? '';
// Clear account data
$this->clear_account();
Authentication Checks
Base classes provide standardized authentication checks:
// Check if configured
if (!$this->is_configured()) {
return new WP_Error('not_configured', 'Provider not configured');
}
// Check if authenticated
if (!$this->is_authenticated()) {
return new WP_Error('not_authenticated', 'User not authenticated');
}
Security Features
State Nonce Protection (OAuth2):
- CSRF protection via WordPress transient system
- 15-minute expiration window
- Automatic cleanup on verification
Temporary Token Management (OAuth1):
- Transient storage with provider and token scoping
- 15-minute expiration for request tokens
- Automatic cleanup after exchange
Input Sanitization:
- All callback parameters sanitized via
sanitize_text_field() - WordPress
wp_unslash()before sanitization - Comprehensive error handling
Redirect Security:
- Success/error redirects to admin settings page
- Error codes passed via query parameters
- Admin capability required for OAuth URLs
Centralized Storage:
- All credentials stored in WordPress options
- No direct database access from providers
- Consistent encryption and security patterns
Error Handling
OAuth2 Errors:
oauth_denied– User denied authorizationinvalid_state– State verification failedtoken_exchange_failed– Token exchange errortoken_transform_failed– Token transformation erroraccount_fetch_failed– Account details retrieval errorstorage_failed– Account data storage error
OAuth1 Errors:
access_denied– User denied accessmissing_parameters– Missing oauth_token or oauth_verifiertoken_secret_expired– Temporary token secret expiredaccess_token_failed– Access token exchange failedstorage_failed– Account data storage errorrequest_token_failed– Request token retrieval failed
Logging:
All OAuth operations logged via datamachine_log action:
do_action('datamachine_log', 'info', 'OAuth2: Authentication successful', [
'provider' => $provider_key,
'account_id' => $account_data['id']
]);
do_action('datamachine_log', 'error', 'OAuth1: Failed to get access token', [
'provider' => $provider_key,
'http_code' => $http_code
]);
Benefits of Base Class Architecture
Code Elimination:
- Removes duplicated storage logic across all providers
- Eliminates redundant configuration validation code
- Centralizes callback URL generation
- Unified authentication state checking
Consistency:
- Identical option storage patterns across all providers
- Standardized error handling and logging
- Uniform security implementation
- Consistent API for all authentication types
Maintainability:
- Single point of update for storage improvements
- Centralized security enhancements
- Easier debugging with consistent patterns
- Reduced testing surface area
Extensibility:
- New providers integrate via simple base class extension
- Minimal boilerplate required for new authentication types
- Inherited functionality ensures feature parity
- Clear extension points for custom behavior
Migration from Legacy Pattern
Before (v0.2.5 and earlier):
class RedditAuth {
public function get_account() {
$all_auth = get_option('datamachine_auth_data', []);
return $all_auth['reddit']['account'] ?? [];
}
public function save_account(array $data) {
$all_auth = get_option('datamachine_auth_data', []);
$all_auth['reddit']['account'] = $data;
return update_option('datamachine_auth_data', $all_auth);
}
public function is_authenticated(): bool {
$account = $this->get_account();
return !empty($account) &&
!empty($account['access_token']);
}
}
After (v0.2.6):
class RedditAuth extends BaseOAuth2Provider {
public function __construct() {
parent::__construct('reddit');
}
// Inherits: get_account(), save_account(), is_authenticated()
// Only implements provider-specific logic
}
Elimination:
- Removed ~50 lines of storage code per provider
- Removed ~30 lines of validation code per provider
- Eliminated 6 duplicate implementations of identical patterns
- Reduced authentication provider code by approximately 60%
Related Documentation
- Core Filters – OAuth service discovery filters
- Twitter Handler – OAuth1 implementation example
- Reddit Handler – OAuth2 implementation example
- Facebook Handler – OAuth2 with token transformation
- Bluesky Handler – Direct BaseAuthProvider extension example
- Settings Configuration – OAuth credential management
Implementation: /inc/Core/OAuth/ directory with BaseAuthProvider, BaseOAuth1Provider, and BaseOAuth2Provider base classes, OAuth1Handler and OAuth2Handler services, and Providers/ directory (GoogleSheetsAuth)
Architecture: Inheritance-based provider system with centralized storage and validation