- 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
392 lines
16 KiB
Markdown
392 lines
16 KiB
Markdown
# 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
|
|
|
|
1. User navigates to license page → clicks [ACTIVATE]
|
|
2. Redirect to vendor site (licensing.woonoow.com or similar)
|
|
3. Vendor site: login required
|
|
4. Vendor shows licenses for user's account, filtered by product
|
|
5. User selects license to connect
|
|
6. Click "Connect This Site"
|
|
7. Return to `return_url` after 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)
|
|
|
|
```php
|
|
// 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)
|
|
|
|
1. **Login Gate**: If user not logged in → redirect to login with `?redirect=/connect?...`
|
|
2. **Validate Request**: Check `state`, `nonce`, `timestamp` (reject if >10 min old)
|
|
3. **Fetch User Licenses**: Query licenses owned by authenticated user for `product_id`
|
|
4. **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] │
|
|
└─────────────────────────────────────────────────┘
|
|
```
|
|
5. **Record Activation**: Insert into `license_activations` table
|
|
6. **Generate Callback**: Redirect to `return_url` with:
|
|
- `activation_token`: Short-lived token (5 min expiry)
|
|
- `state`: Original state for verification
|
|
|
|
#### Phase 3: Callback (Client Plugin)
|
|
|
|
```php
|
|
// 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)
|
|
|
|
```sql
|
|
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
|
|
|
|
```php
|
|
// 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)
|
|
1. Create `/connect` authorization page
|
|
2. Build license selection UI
|
|
3. Implement activation recording
|
|
4. Create token exchange API
|
|
|
|
### Phase 2: Client-Side (WooNooW Plugin)
|
|
1. Create Settings → License admin page
|
|
2. Implement connect redirect
|
|
3. Handle callback and token exchange
|
|
4. Store license securely
|
|
5. Add disconnect functionality
|
|
|
|
### Phase 3: Validation & Updates
|
|
1. Implement periodic license checks
|
|
2. Gate premium features behind valid license
|
|
3. 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 |
|