OAuth Activation Flow

User-approved license activation with signed state and combined website identity

Overview

OAuth activation adds customer account approval to normal license activation. The customer signs in to the WooNooW-powered vendor store and explicitly approves the requesting website.

The merchant chooses Simple API or Secure OAuth globally and may allow per-product overrides. The client uses the same activation endpoint for both methods; it does not select the method with an activation_mode request field.

OAuth activation still uses the canonical website identity:

text
persistent installation UUID + normalized domain

Read Website Identity before implementing this flow.

Security properties

WooNooW's signed OAuth state binds:

  • license key;
  • original and normalized requesting domain;
  • persistent installation UUID;
  • callback return_url;
  • state expiry.

The callback URL must normalize to the same domain as the requesting site. State expires after 10 minutes. The final activation token:

  • expires after 5 minutes in UTC;
  • is stored only as a SHA-256 hash by WooNooW;
  • is bound to the approved UUID + domain identity;
  • can be used only once.

Activation flow

  1. 1. Client requests activation

    Send the license key, complete website identity, and a callback URL on the requesting site.

    http
    POST /wp-json/woonoow/v1/licenses/activate
    Content-Type: application/json
    
    json
    {
      "license_key": "XXXX-YYYY-ZZZZ-WWWW",
      "domain": "https://customer-site.com",
      "installation_id": "550e8400-e29b-41d4-a716-446655440000",
      "return_url": "https://customer-site.com/wp-admin/admin.php?page=my-plugin-license"
    }
    
  2. 2. Client receives an approval URL

    When the product requires OAuth, WooNooW responds with HTTP 200 OK and success: false:

    json
    {
      "success": false,
      "code": "oauth_required",
      "message": "This license requires account verification. You will be redirected to complete activation.",
      "redirect_url": "https://your-store.com/my-account/license-connect/?license_key=XXXX-YYYY-ZZZZ-WWWW&site_url=https%3A%2F%2Fcustomer-site.com&return_url=https%3A%2F%2Fcustomer-site.com%2Fcallback&installation_id=550e8400-e29b-41d4-a716-446655440000&state=eyJhbGci...&nonce=7a8b9c0d1e"
    }
    

    Treat redirect_url as opaque. Do not reconstruct or modify its signed state parameters.

  3. 3. Customer approves the website

    Open redirect_url in the user's browser. The customer signs in to the vendor store, verifies the product and requesting site, and approves the activation.

  4. 4. Vendor redirects to the client callback

    WooNooW redirects the browser back to the approved return_url. Depending on whether the vendor store handled approval through the Customer SPA or the PHP fallback, the query parameters differ:

    • Customer SPA flow (LicensesController::oauth_confirm): appends activation_token, license_key, and nonce.
    • PHP template flow (LicensingModule::process_license_confirmation): appends activation_token, license_key, and state.
  5. 5. Client exchanges the activation token

    Complete activation by sending the same license key, combined identity, and single-use activation_token to the activation endpoint:

    http
    POST /wp-json/woonoow/v1/licenses/activate
    Content-Type: application/json
    
    json
    {
      "license_key": "XXXX-YYYY-ZZZZ-WWWW",
      "domain": "https://customer-site.com",
      "installation_id": "550e8400-e29b-41d4-a716-446655440000",
      "activation_token": "temporary-single-use-token"
    }
    

    A successful response returns the activation ID, remaining seats, and product identifiers:

    json
    {
      "success": true,
      "activation_id": 123,
      "activations_remaining": 2,
      "product_id": 42,
      "variation_id": 0
    }
    

Sequence

sequenceDiagram
    participant C as Client website
    participant B as Customer browser
    participant V as WooNooW vendor store

    C->>V: POST /licenses/activate<br/>key + domain + UUID + return_url
    V-->>C: oauth_required + redirect_url
    C->>B: Open redirect_url
    B->>V: Sign in and approve website
    V->>V: Verify signed state and license ownership
    V-->>B: Redirect to approved return_url<br/>with short-lived activation_token
    B->>C: Callback
    C->>V: POST /licenses/activate<br/>same key + domain + UUID + token
    V-->>C: activation_id + activations_remaining

OAuth backend endpoints

On the vendor store, the OAuth confirmation UI interacts with two REST API endpoints. Both endpoints require authentication (is_user_logged_in()) and verify that the requested license key belongs to the currently logged-in customer (user_id === current_user_id).

1. Validate connection request

http
GET /wp-json/woonoow/v1/licenses/oauth/validate?license_key={key}&state={state}

Validates the signed state token, checks that the state's embedded license key matches license_key, verifies account ownership, and resolves the requesting site identity.

Response (200):

json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "product_id": 42,
  "variation_id": 0,
  "product_name": "WooNooW Pro",
  "variation_name": "",
  "status": "active",
  "activation_limit": 3,
  "activation_count": 1,
  "expires_at": "2027-09-08 12:00:00",
  "usage_expires_at_utc": "2027-09-08T12:00:00Z"
}

2. Confirm connection and issue token

http
POST /wp-json/woonoow/v1/licenses/oauth/confirm
Content-Type: application/json
json
{
  "license_key": "XXXX-YYYY-ZZZZ-WWWW",
  "state": "eyJhbGciOiJIUzI1NiIs...",
  "nonce": "7a8b9c0d1e"
}

Verifies ownership and signed state, issues a single-use activation token (5-minute TTL) bound to the approved identity, and constructs the callback redirect URL:

Response (200):

json
{
  "success": true,
  "redirect_url": "https://customer-site.com/callback?activation_token=tok_abc123&license_key=XXXX-YYYY-ZZZZ-WWWW&nonce=7a8b9c0d1e",
  "activation_token": "tok_abc123"
}

Callback example for WordPress

php
$activation_token = sanitize_text_field(wp_unslash($_GET['activation_token'] ?? ''));
$license_key       = sanitize_text_field(wp_unslash($_GET['license_key'] ?? ''));
$installation_id   = get_option('my_product_installation_id', '');

$response = wp_remote_post('https://your-store.com/wp-json/woonoow/v1/licenses/activate', [
    'timeout' => 15,
    'headers' => ['Content-Type' => 'application/json'],
    'body'    => wp_json_encode([
        'license_key'     => $license_key,
        'domain'          => home_url(),
        'installation_id' => $installation_id,
        'activation_token'=> $activation_token,
    ]),
]);

Validate the local pending request before running this exchange. Do not write the raw activation token to logs or persistent settings.

Failure handling

CodeAction
missing_return_urlAdd a callback URL to the initial activation request
invalid_return_urlUse a callback URL on the same normalized domain as domain
missing_stateState token query parameter was omitted on OAuth validation
invalid_stateRestart activation; the signed state is invalid or expired
unauthorizedThe logged-in customer does not own the requested license
license_not_foundLicense key does not exist
invalid_tokenRestart approval; the token is invalid, expired, or belongs to another identity
token_consumedDo not reuse the token; validate the license or restart the flow
activation_limit_reachedAsk the customer to deactivate an old installation or upgrade entitlement
activation_transaction_failedDatabase error starting activation transaction on vendor store; retry request

A network failure must not cause the client to generate a new installation UUID. Reuse the persistent identity and start a fresh OAuth request when necessary.

Last updated Jul 29, 2026