# 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=& │ │ nonce= │ ├──────────────────────────────────────────────────────────────────┤ │ 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 |