What this web example licenses
This is one customer's Python server installation, opened in a browser. KDPika licenses that server; your web app still controls who may sign in. A browser tab is not a licensed device, and the Python SDK does not replace your website's user accounts.
It is a complete local learning example, not an internet-facing multi-tenant SaaS authentication product. Run it on 127.0.0.1. For a real multi-customer service, authenticate each user and map that user to the correct server-side tenant/license/state; never accept a caller-supplied license identity as authorization. Application architectures.
1. Prepare the project
Download and extract the Python projects. Create the tutorial product/plan/customer/Sandbox license in shared setup. The normal software plan should include csv_export with usage credits off.
Create and activate a virtual environment as shown in the desktop guide, then install the example requirements:
python -m pip install -r requirements.txt
This installs the official SDK and Flask in your local project. It does not change KDPika's PHP hosting or add Flask to the SDK's own dependencies.
What are you licensing?
SandboxUse a separate test product. The name and App ID shown here are examples, not real account data.
2. Start the server
macOS/Linux:
export KDP_APP_ID="app_YOUR_PUBLIC_ID"
export KDP_ENVIRONMENT="SANDBOX"
python web.py
Windows PowerShell:
$env:KDP_APP_ID="app_YOUR_PUBLIC_ID"
$env:KDP_ENVIRONMENT="SANDBOX"
python web.py
On first start, enter the test license key privately in the server terminal and its matching customer email. Then the terminal prints a random local app access code. This code is for this local web app, not your KDPika password or license key. Keep it private.
3. Open the web app and export
- Visit
http://127.0.0.1:5000on that computer. - Enter the local app access code printed by this run.
- Type a message and choose Download CSV. The server checks the signed license and feature immediately before creating the file.
- The browser receives the CSV, not the activation token, raw key, fingerprint or signed customer payload.
- Choose Sign out to end the app session. This does not revoke the installed server license.
Restarting the local example generates a new app access code/session secret and signs its web users out. The SDK installation state stays in the same private product/environment folder. A production deployment needs stable server session secrets, HTTPS/secure cookies and real user authorization/rate limits. Do not expose Flask's development server to the public internet.
4. Follow the request
| Stage | What happens |
|---|---|
| App sign-in | A CSRF-protected local form establishes this example's app session |
| Export request | The backend checks the app session and CSRF, validates the text and asks the SDK for access |
| License result | The SDK verifies signature, product/environment, device binding and signed deadlines |
| Feature guard | The backend checks csv_export; hiding a browser button alone is never protection |
| Download | Only allowed work writes and returns the CSV; responses are private/no-store |
The same common.py from the desktop project contains the feature guard. The difference is where it runs: in the Python backend instead of a customer desktop window.
"""Local single-customer web deployment. License checks run on this server, never in JS."""
import getpass
import secrets
from hmac import compare_digest
from flask import Flask, abort, redirect, render_template_string, request, send_file, session
from common import AccessDenied, export_csv, make_client
PAGE = """<!doctype html><html lang="en"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>My licensed web app</title><main>
<h1>My licensed web app</h1><p>{{ message }}</p>
<form method="post"><input type="hidden" name="csrf" value="{{ csrf }}">
{% if signed_in %}<label>Message <input name="text" maxlength="1000" value="My first web export" required></label>
<button name="action" value="export">Download CSV</button>
<button name="action" value="logout">Sign out</button>
{% else %}<label>App access code <input type="password" name="access_code" autocomplete="current-password" required></label>
<button name="action" value="login">Open this app</button>{% endif %}</form>
<p>The app access code is for this local example. It is not a KDPika password or license key.</p>
</main></html>"""
def create_app(client, access_code: str):
if len(access_code) < 20:
raise ValueError("Use a randomly generated app access code of at least 20 characters.")
app = Flask(__name__)
app.config.update(SECRET_KEY=secrets.token_hex(32), MAX_CONTENT_LENGTH=4096,
SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE="Strict")
# Intentionally local HTTP only. Production needs HTTPS, stable session secrets,
# real user authentication/authorization and rate limits; do not expose this demo publicly.
app.extensions["license_client"] = client
@app.after_request
def private(response):
response.headers["Cache-Control"] = "private, no-store"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Content-Security-Policy"] = "default-src 'none'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'"
return response
@app.route("/", methods=["GET", "POST"])
def home():
session.setdefault("csrf", secrets.token_urlsafe(32))
message = "Open the local app, then try a licensed export."
if request.method == "POST":
if not compare_digest(request.form.get("csrf", ""), session["csrf"]):
abort(400)
action = request.form.get("action")
if action == "login":
if not compare_digest(request.form.get("access_code", ""), access_code):
message = "This access code could not be accepted."
else:
session.clear()
session["signed_in"] = True
session["csrf"] = secrets.token_urlsafe(32)
return redirect("/", code=303)
elif not session.get("signed_in"):
abort(401)
elif action == "logout":
session.clear()
return redirect("/", code=303)
elif action == "export":
text = request.form.get("text", "").strip()
if not text or len(text) > 1000:
abort(400)
try:
path = export_csv(client, text)
return send_file(path, as_attachment=True, download_name="my-export.csv")
except AccessDenied as error:
message = str(error)
except Exception:
message = "The export could not be completed. Please try again."
return render_template_string(PAGE, message=message, csrf=session["csrf"],
signed_in=bool(session.get("signed_in")))
return app
if __name__ == "__main__":
client = make_client()
result = client.check()
if result.error_code == "NOT_ACTIVATED":
result = client.activate(getpass.getpass("License key for this server: "),
input("Matching customer email: ").strip() or None)
if not result.valid or not result.signature_valid:
client.close()
raise SystemExit("This server's license is unavailable. Check it in KDPika.")
access_code = secrets.token_urlsafe(24)
print("Local app access code (not a license):", access_code)
print("Open http://127.0.0.1:5000 — this is a local, single-customer example.")
try:
create_app(client, access_code).run(host="127.0.0.1", port=5000, debug=False,
use_reloader=False, threaded=False)
finally:
client.close()
5. Add the advanced models
| Requirement | Implementation |
|---|---|
| Device limits | Configure the software plan or per-license override. The licensed installation here is the server, not each visitor |
| Advanced hardware matching | Configure the product policy; the Python SDK collects hashes on its server. Containers/VM identity changes need a deliberate deployment policy |
| Offline access | check() can use a verified normal cache within signed deadlines. Do not use this as evidence that a browser user's own subscription is paid |
| Floating seats | The SDK maintains a lease for this server process. A single shared Python client does not create one seat per signed-in browser user |
| Credits | Use the durable credit export as backend job logic; authenticate and authorize the user/job before calling it. The starter route refuses credit-based exports until you deliberately integrate charging |
| Portal and branding | Configure the hosted portal, then link to its real sign-in URL from your app. Never put a customer key/token in that URL |
| Plan edits and personal overrides | Use Plans or the customer's license in the KDPika dashboard; the SDK reads the effective signed result |
| Audit/history | Review eligible server-side licensing/account audit events in KDPika. Your web app needs its own user/action audit trail without secrets |
6. Deploy deliberately
The example runs a single process with the reloader/debugger off. For production, use a maintained production server and existing application authentication. Decide whether each worker/server is one licensed installation or whether licensing is handled by a dedicated trusted backend. Do not initialize heartbeat workers before forking or copy one machine's activation directory across containers.
Persist private SDK state across restarts, restrict its filesystem access and keep Sandbox and Live separate. In a hosted web app, use server-side HTTPS requests; do not send a management key or native hardware collector to browser JavaScript. Start each client after its process starts and close it on shutdown. Stop protected work when access is refused or its signed lease expires.
7. Verify before going live
- Without the app session, submitting an export is refused.
- With a missing/incorrect CSRF value, the request is refused before licensed work.
- With an eligible signed license and
csv_export, the CSV is downloaded. - With a disabled feature, expired/refused license or metered plan on this starter route, no free export is generated.
- The browser never receives a raw key or activation/session token from the SDK.
These controls are tested at code/request level; no visual or production deployment test of the sample Flask app is claimed. The live KDPika documentation page is deployed separately.