Licenses

Each license is associated with a product and can be activated on a defined number of devices. A license can also have an email associated with it, allowing customers to manage their licenses in the portal.

License types

Keyforge currently supports two types of licenses:

  • Perpetual: A license that never expires.
  • Timed: A license that expires at a specific date.

Devices

A license can be activated on multiple devices, as defined by the maximum active devices.

Each active device on a license has 3 properties:

  • Identifier: A unique identifier for the device.
  • Name: A name for the device. Does not need to be unique. Must have at most 64 characters.
  • Activation date: The date when the device was activated.

Device identifier

The device identifier must be unique inside the license scope. Keyforge accepts any string up to 96 characters as a deviceIdentifier. Use the most reliable method available on your platform. Below are some recommended approaches for each platform.

Use the IOPlatformUUID from the macOS I/O Registry for a hardware-level identifier. Fall back to a generated UUID stored in the Keychain if the hardware UUID is unavailable.

Store the resolved identifier in the Keychain so it remains stable across app restarts. This pattern has been applied in the macOS example app.

LicenseManager.swift
import IOKit

var deviceIdentifier: String {
    var id = UUID().uuidString

    let service = IOServiceGetMatchingService(
        kIOMainPortDefault,
        IOServiceMatching("IOPlatformExpertDevice")
    )
    if service != IO_OBJECT_NULL {
        if let uuidRef = IORegistryEntryCreateCFProperty(
            service, "IOPlatformUUID" as CFString, kCFAllocatorDefault, 0
        )?.takeRetainedValue() as? String {
            id = uuidRef
        }
        IOObjectRelease(service)
    }

    return id
}

Use the node-machine-id package for a hardware-based machine identifier. No manual persistence is needed.

license-manager.js
import { machineId } from 'node-machine-id';
import os from 'os';

const deviceIdentifier = await machineId();
const deviceName = os.hostname();

Use a hardware ID or generate a UUID and persist it securely with tauri-plugin-stronghold or the macOS Keychain.

import { Stronghold } from '@tauri-apps/plugin-stronghold';

async function getDeviceId(stronghold) {
  const store = stronghold.loadStore('device' /* password */);
  const existing = await store.get('device_id');
  if (existing) return new TextDecoder().decode(existing);

  const id = crypto.randomUUID();
  await store.insert('device_id', Array.from(new TextEncoder().encode(id)));
  await stronghold.save();
  return id;
}

Read the Machine GUID from the Windows registry for a stable hardware-level identifier.

This value is set by the OS and persists across app restarts and reinstalls.

LicenseManager.cs
using Microsoft.Win32;

var key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography");
var deviceIdentifier = key?.GetValue("MachineGuid")?.ToString();

Use the site URL via WordPress's built-in home_url() function. Each installation consumes one activation slot.

No manual persistence is needed. home_url() is always available from the WordPress database.

license-manager.php
$deviceIdentifier = home_url();
$deviceName = get_bloginfo('name');

Generate a UUID on first install and persist it in chrome.storage.local. Browser extensions have no access to hardware identifiers.

This works across Chrome, Firefox, and all Chromium-based browsers.

license-manager.js
async function getDeviceId() {
  const result = await chrome.storage.local.get('deviceId');
  if (result.deviceId) return result.deviceId;

  const id = crypto.randomUUID();
  await chrome.storage.local.set({ deviceId: id });
  return id;
}

Generate a UUID and persist it to the filesystem with Bun.write(), since node-machine-id is not available in Bun.

license-manager.ts
import { join } from 'path';
import { homedir } from 'os';

async function getDeviceId(): Promise<string> {
  const path = join(homedir(), '.config', 'my-app', 'device-id');
  const file = Bun.file(path);
  if (await file.exists()) return file.text();

  const id = crypto.randomUUID();
  await Bun.write(path, id);
  return id;
}

On this page