- Add LicenseConnect.tsx focused OAuth confirmation page in customer SPA - Add /licenses/oauth/validate and /licenses/oauth/confirm API endpoints - Update App.tsx to render license-connect outside BaseLayout (no header/footer) - Add license_activation_method field to product settings in Admin SPA - Create LICENSING_MODULE.md with comprehensive OAuth flow documentation - Update API_ROUTES.md with license module endpoints
16 KiB
16 KiB
OAuth-Style License Activation Research Report
Date: January 31, 2026
Objective: Design a strict license activation system requiring vendor site authentication
Executive Summary
After researching Elementor Pro, Tutor LMS, EDD Software Licensing, and industry standards, the redirect-based OAuth-like activation flow is the most secure and user-friendly approach. This pattern:
- Prevents license key sharing by tying activation to user accounts
- Provides better UX than manual key entry
- Enables flexible license management
- Creates an anti-piracy layer beyond just key validation
Industry Analysis
1. Elementor Pro
| Aspect | Implementation |
|---|---|
| Flow | "Connect & Activate" button → redirect to Elementor.com → login required → authorize connection → return to WP Admin |
| Why Listed | Market leader with 5M+ users; sets the standard for premium plugin activation |
| Anti-Piracy | Account-tied activation; no ability to share just a license key |
| Fallback | Manual key entry via hidden URL parameter ?mode=manually |
Key Pattern: Elementor never shows the license key in the normal flow—users authenticate with their account, not a key.
2. Tutor LMS (Themeum)
| Aspect | Implementation |
|---|---|
| Flow | License settings → Enter key → "Connect" button → redirect to Themeum → login → confirm connection |
| Why Listed | Popular LMS plugin; hybrid approach (key + account verification) |
| Anti-Piracy | License keys tied to specific domains registered in user account |
| License Display | Keys visible in account dashboard for copy-paste |
Key Pattern: Requires domain registration in vendor account before activation works.
3. Easy Digital Downloads (EDD) Software Licensing
| Aspect | Implementation |
|---|---|
| Flow | API-based: plugin sends key + site URL to vendor → server validates → returns activation status |
| Why Listed | Powers many WordPress plugin vendors (WPForms, MonsterInsights, etc.) |
| Anti-Piracy | Activation limits (e.g., 1 site, 5 sites, unlimited); site URL tracking |
| Management | Customer can manage activations in their EDD account |
Key Pattern: Traditional key-based but with strict activation limits and site tracking.
4. WooCommerce Software License Manager
| Aspect | Implementation |
|---|---|
| Flow | REST API with key + secret authentication |
| Why Listed | Common for WooCommerce-based vendors |
| Anti-Piracy | API-key authentication; activation records |
Key Pattern: Programmatic API access, less user-facing UX focus.
Best Practices Identified
Anti-Piracy Measures
| Measure | Effectiveness | UX Impact |
|---|---|---|
| Account authentication required | ★★★★★ | Minor inconvenience |
| Activation limits per license | ★★★★☆ | None |
| Domain/URL binding | ★★★★☆ | None |
| Tying updates/support to valid license | ★★★★★ | Incentivizes purchase |
| Periodic license re-validation | ★★★☆☆ | Can cause issues |
| Encrypted API communication (HTTPS) | ★★★★★ | None |
UX Considerations
| Consideration | Priority |
|---|---|
| One-click activation (minimal friction) | High |
| Clear error messages | High |
| License status visibility in WP Admin | Medium |
| Easy deactivation for site migrations | High |
| Fallback manual activation | Medium |
Security Comparison
| Method | Piracy Resistance | Implementation Complexity |
|---|---|---|
| Simple key validation | Low | Simple |
| Key + site URL binding | Medium | Medium |
| Key + activation limits | Medium-High | Medium |
| OAuth redirect + account tie | High | Complex |
| OAuth + key + activation limits | Very High | Complex |
Your Proposed Flow Analysis
Original Flow Points
- User navigates to license page → clicks [ACTIVATE]
- Redirect to vendor site (licensing.woonoow.com or similar)
- Vendor site: login required
- Vendor shows licenses for user's account, filtered by product
- User selects license to connect
- Click "Connect This Site"
- Return to
return_urlafter short delay
Identified Gaps
| Gap | Risk | Solution |
|---|---|---|
| No state parameter | CSRF attack possible | Add signed state token |
| No nonce verification | Replay attacks | Include one-time nonce |
| Return URL manipulation | Redirect hijacking | Validate return URL on server |
| No deactivation flow | User can't migrate | Add disconnect button |
Perfected Implementation Plan
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ WP ADMIN (Client Site) │
├─────────────────────────────────────────────────────────────────┤
│ Settings → License │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Status: Not Connected │ │
│ │ [🔗 Connect & Activate] │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│
▼ Redirect with signed params
┌──────────────────────────────────────────────────────────────────┐
│ VENDOR SITE (License Server) │
├──────────────────────────────────────────────────────────────────┤
│ /license/connect? │
│ product_id=woonoow-pro& │
│ site_url=https://customer-site.com& │
│ return_url=https://customer-site.com/wp-admin/...& │
│ state=<signed_token>& │
│ nonce=<one_time_code> │
├──────────────────────────────────────────────────────────────────┤
│ 1. Force login if not authenticated │
│ 2. Show licenses owned by user for this product │
│ 3. User selects: "Pro License (3/5 sites used)" │
│ 4. Click [Connect This Site] │
│ 5. Server records activation │
│ 6. Redirect back with activation token │
└──────────────────────────────────────────────────────────────────┘
│
▼ Callback with activation token
┌──────────────────────────────────────────────────────────────────┐
│ WP ADMIN (Client Site) │
├──────────────────────────────────────────────────────────────────┤
│ Callback handler: │
│ 1. Verify state matches stored value │
│ 2. Exchange activation_token for license_key via API │
│ 3. Store license_key securely │
│ 4. Show success: "License activated successfully!" │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Status: ✅ Active │ │
│ │ License: Pro (expires Dec 31, 2026) │ │
│ │ Sites: 4/5 activated │ │
│ │ [Disconnect] │ │
│ └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Detailed Flow
Phase 1: Initiation (Client Plugin)
// User clicks "Connect & Activate"
$params = [
'product_id' => 'woonoow-pro',
'site_url' => home_url(),
'return_url' => admin_url('admin.php?page=woonoow-license&action=callback'),
'nonce' => wp_create_nonce('woonoow_license_connect'),
'state' => $this->generate_state_token(), // Signed, stored in transient
'timestamp' => time(),
];
$redirect_url = 'https://licensing.woonoow.com/connect?' . http_build_query($params);
wp_redirect($redirect_url);
Phase 2: Authentication (Vendor Server)
- Login Gate: If user not logged in → redirect to login with
?redirect=/connect?... - Validate Request: Check
state,nonce,timestamp(reject if >10 min old) - Fetch User Licenses: Query licenses owned by authenticated user for
product_id - Display License Selector:
┌─────────────────────────────────────────────────┐ │ Connect site-name.com to your license │ ├─────────────────────────────────────────────────┤ │ ○ WooNooW Pro - Agency (Unlimited sites) │ │ ● WooNooW Pro - Business (3/5 sites) ←selected │ │ ○ WooNooW Pro - Personal (1/1 sites) [FULL] │ ├─────────────────────────────────────────────────┤ │ [Cancel] [Connect This Site] │ └─────────────────────────────────────────────────┘ - Record Activation: Insert into
license_activationstable - Generate Callback: Redirect to
return_urlwith:activation_token: Short-lived token (5 min expiry)state: Original state for verification
Phase 3: Callback (Client Plugin)
// Handle callback
$activation_token = sanitize_text_field($_GET['activation_token']);
$state = sanitize_text_field($_GET['state']);
// 1. Verify state matches stored transient
if (!$this->verify_state_token($state)) {
wp_die('Invalid state. Possible CSRF attack.');
}
// 2. Exchange token for license details via secure API
$response = wp_remote_post('https://licensing.woonoow.com/api/v1/token/exchange', [
'body' => [
'activation_token' => $activation_token,
'site_url' => home_url(),
],
]);
// 3. Store license data
$license_data = json_decode(wp_remote_retrieve_body($response), true);
update_option('woonoow_license', [
'key' => $license_data['license_key'],
'status' => 'active',
'expires' => $license_data['expires_at'],
'tier' => $license_data['tier'],
'sites_used' => $license_data['sites_used'],
'sites_max' => $license_data['sites_max'],
]);
// 4. Redirect with success
wp_redirect(admin_url('admin.php?page=woonoow-license&activated=1'));
Security Parameters
| Parameter | Purpose | Implementation |
|---|---|---|
state |
CSRF protection | HMAC-signed, stored in transient, expires 10 min |
nonce |
Replay prevention | One-time use, verified on server |
timestamp |
Request freshness | Reject requests >10 min old |
activation_token |
Secure exchange | Short-lived (5 min), single-use |
site_url |
Domain binding | Stored with activation record |
Database Schema (Vendor Server)
CREATE TABLE license_activations (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
license_id BIGINT NOT NULL,
site_url VARCHAR(255) NOT NULL,
activation_token VARCHAR(64),
token_expires_at DATETIME,
activated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_check DATETIME,
status ENUM('active', 'deactivated') DEFAULT 'active',
metadata JSON,
UNIQUE KEY unique_license_site (license_id, site_url),
FOREIGN KEY (license_id) REFERENCES licenses(id)
);
Deactivation Flow
Client: [Disconnect] button clicked
→ POST /api/v1/license/deactivate
→ Body: { license_key, site_url }
→ Server removes activation record
→ Client clears stored license
→ Show "Disconnected" status
Periodic Validation
// Cron check every 24 hours
add_action('woonoow_daily_license_check', function() {
$license = get_option('woonoow_license');
if (!$license) return;
$response = wp_remote_post('https://licensing.woonoow.com/api/v1/license/validate', [
'body' => [
'license_key' => $license['key'],
'site_url' => home_url(),
],
]);
$data = json_decode(wp_remote_retrieve_body($response), true);
if ($data['status'] !== 'active') {
update_option('woonoow_license', ['status' => 'invalid']);
// Optionally disable premium features
}
});
API Endpoints (Vendor Server)
| Endpoint | Method | Purpose |
|---|---|---|
/connect |
GET | OAuth-like authorization page |
/api/v1/token/exchange |
POST | Exchange activation token for license |
/api/v1/license/validate |
POST | Validate license status |
/api/v1/license/deactivate |
POST | Remove site activation |
/api/v1/license/info |
GET | Get license details |
Comparison: Your Flow vs. Perfected
| Aspect | Your Original | Perfected |
|---|---|---|
| CSRF Protection | ❌ None | ✅ State token |
| Replay Prevention | ❌ None | ✅ Nonce + timestamp |
| Token Exchange | ❌ Direct return | ✅ Secure exchange |
| Return URL Security | ❌ Unvalidated | ✅ Server whitelist |
| Deactivation | ❌ Not mentioned | ✅ Full flow |
| Periodic Validation | ❌ Not mentioned | ✅ Daily cron |
| Fallback | ❌ None | ✅ Manual key entry |
Implementation Phases
Phase 1: Server-Side (Licensing Portal)
- Create
/connectauthorization page - Build license selection UI
- Implement activation recording
- Create token exchange API
Phase 2: Client-Side (WooNooW Plugin)
- Create Settings → License admin page
- Implement connect redirect
- Handle callback and token exchange
- Store license securely
- Add disconnect functionality
Phase 3: Validation & Updates
- Implement periodic license checks
- Gate premium features behind valid license
- Integrate with plugin update checker
References
| Source | Relevance |
|---|---|
| Elementor Pro Activation | Primary reference for UX flow |
| Tutor LMS / Themeum | Hybrid key+account approach |
| OAuth 2.0 Authorization Code Flow | Security pattern basis |
| EDD Software Licensing | Activation limits pattern |
| OWASP API Security | State/nonce implementation |