Skip to content

Django integration — centralized identity for client sites

Purpose

Onboard a portfolio (client) site so MKP Works owns login, authentication, account creation, and profile management for every product. One person has one MKP account across all ClientSites. Each site can show a basic profile (email + username / display name). Simple sites install a single integration library and may not need their own user database at all. Complex sites keep product data locally and still trust MKP for identity.

This page is the human guide. At the bottom is a pasteable LLM brief you can give to another developer or model to modify a client codebase.

What MKP owns vs what the client owns

Concern Owned by MKP Works Platform Owned by the client site
Sign in (email + password) Yes — Django session login under /accounts/login/ Redirect users here; do not rebuild login
Password hashing, suspension Yes Never store MKP passwords locally
Canonical user record Yes — accounts.User (public_id, email, display_name, …) Cache only if needed; key by public_id
Basic profile (email + username/display name) Yes — platform dashboard + /api/v1/me/ Show/edit via MKP or read-only cache
OIDC for separate Django origins Yes — /o/ (Authorization Code + PKCE) Complete the code flow; map sub → local row if any
Forms, email templates, wallet, AI, actions Yes — platform APIs Call via library / mkp.js / server client
Product-specific data (orders, CMS, posts) No Your models / DB (or none, for thin sites)

Centralization rule: There is one User in MKP. ClientSites do not each invent their own password database. A user who signs in on Site A is the same identity on Site B when both use MKP auth.

Profile fields every site can rely on

Field Source Notes
Stable id public_id (usr_…) Use as foreign key / sub. Never use email as the permanent join key.
Email email Unique login identifier on the platform
Username / display name display_name Human-facing name shown in UI; not the login field
Optional first_name, last_name Available on the platform user

Login is email. Treat display name as the site-facing “username” for UI and profiles.


Architecture (pick a mode)

[Email/password]
     MKP Works Platform
     (session auth + accounts.User)
     ┌────────┴────────┐
     │                 │
     ▼                 ▼
 Thin site          Django product
 (often no DB)      (OIDC + optional local DB)
 mkp.js / embeds    django-mkp style library
 session on MKP     local session after OIDC

Mode A — Thin site (no local user database)

Best for marketing sites, simple utilities, and Replit prototypes that only need login + forms + wallet UI.

  1. Register a ClientSite; issue a public site key.
  2. Load hosted mkp.js; call MKP.init({ baseUrl, siteKey }).
  3. Send users to MKP for login/signup (/accounts/login/, signup, social).
  4. After login, call GET /api/v1/me/ (session cookie or token) to show email + display name.
  5. Use declarative embeds for forms / credits when needed.

You still need somewhere to host HTML/templates, but you do not need a Django User table or SMTP on the client.

Best for a real Django app on its own origin that wants local sessions and optional local models.

  1. Register ClientSite + Allowed domains + OAuth Application + credentials (see Prerequisites).
  2. Install the MKP client library (Python package under sdk/python/mkp_client) and wire auth views (scaffold via generate_mkp_integration, or implement the OIDC steps in the LLM brief).
  3. Users click “Sign in” → Authorization Code + PKCE against {PLATFORM}/o/authorize/.
  4. On callback, exchange code at /o/token/, read userinfo / claims, establish a local Django session.
  5. Prefer a thin local user (or profile) keyed by mkp_subject = public_id, with cached email + display_name. Do not set usable local passwords for MKP users.
  6. Server-side platform features (AI, notifications, actions) use MKPClient with the server secret (skpub_ + sk_), not the OAuth app password.

Mode C — Complex hybrid

Same as Mode B for identity, plus your own database for domain models. MKP remains the IdP. Your tables reference mkp_subject / public_id. You may call wallet, billing, protected actions, and AI through the same library.


Prerequisites (platform admin)

Do this once per product before coding the client. Prefer the staff Control Center wizard:

Site Onboarding — one page for ClientSite, domains, OIDC callbacks, credentials, OAuth app, capabilities, and downloadable scaffold.

You can still use Django Admin (/admin/) for the same rows:

  1. ClientSite — name, slug, status active, site_type (static / django / …), support_email, homepage URL.
  2. Allowed domains — exact browser hosts that may call public APIs (and Origin checks for forms).
  3. Callback URLs — OIDC redirect URI(s) for your Django app (must also be on the linked OAuth Application).
  4. Credentials
  5. Public site key pk_… — browser / mkp.js only.
  6. Server secretX-MKP-Client-Id = skpub_…, X-MKP-Client-Secret = sk_… (shown once). Backend env only.
  7. OAuth Application (OIDC) — confidential or public + PKCE; redirect URIs matching your /accounts/mkp/callback/ (or equivalent). Linked from the ClientSite.
  8. Capabilities — enable only what the product needs (forms, billing, AI, actions, …).

CLI alternative (same scaffold the UI generates):

python manage.py generate_mkp_integration \
  --site YOUR_SITE_SLUG \
  --type django \
  --features auth,forms,billing \
  --output ./generated/YOUR_SITE_SLUG

Complete OIDC callback + session wiring using the steps below (or the LLM brief).


Install on the client (single library mental model)

Treat MKP as one integration surface:

Piece Role Where it lives
Hosted mkp.js Browser embeds, optional login helpers {PLATFORM_BASE_URL}/sdk/v1/mkp.js
mkp_client (Python) Trusted server calls (AI, notifications, actions, usage) Copy/install sdk/python/mkp_client (depends on httpx)
OIDC against /o/ Login for separate Django origins Built into your Django auth views / middleware
Platform /accounts/ Human signup, password reset, social, profile editing Users leave your site briefly, then return

Production base URL example: https://mkpworks.com
Local / Replit: your platform origin (same host that serves /documentation/ and /api/v1/).

Environment variables (client)

# Platform origin (no trailing slash)
MKP_BASE_URL=https://mkpworks.com

# Browser / embeds only
MKP_SITE_KEY=pk_...

# Trusted backend only (SiteCredential — NOT the OAuth Toolkit client_id)
MKP_SERVER_CLIENT_ID=skpub_...
MKP_SERVER_CLIENT_SECRET=sk_...

# OIDC Application (from ClientSite → OAuth app)
MKP_OIDC_CLIENT_ID=...
MKP_OIDC_CLIENT_SECRET=...          # confidential apps
MKP_OIDC_REDIRECT_URI=https://your-app.example/accounts/mkp/callback/

Keep sk_… and OIDC client secrets out of frontend bundles and public repos.


Auth flows in detail

1. Account creation

  • Users register on MKP: /accounts/signup/ (email + password, verification required) or Google/Facebook on the platform.
  • Client sites must not create parallel password accounts for the same person.
  • After signup, the user completes OIDC (Mode B/C) or continues with a platform session (mkp.js, Mode A).

2. Login

Mode A (thin):

<script src="https://mkpworks.com/sdk/v1/mkp.js"></script>
<script>
  MKP.init({ baseUrl: "https://mkpworks.com", siteKey: "pk_YOUR_SITE_KEY" });
  // Prefer platform login helpers / redirect to /accounts/login/?next=...
</script>

Mode B/C (Django OIDC):

  1. Redirect browser to: {MKP_BASE_URL}/o/authorize/?response_type=code&client_id=...&redirect_uri=...&scope=openid%20profile%20email&state=...&code_challenge=...&code_challenge_method=S256
  2. User authenticates on MKP (password or social).
  3. MKP redirects to your redirect_uri with ?code=&state=.
  4. Your backend exchanges the code at {MKP_BASE_URL}/o/token/ (include code_verifier for PKCE).
  5. Call {MKP_BASE_URL}/o/userinfo/ or decode the ID token; resolve sub / public_id, email, name.
  6. login() a local Django user (or session) mapped by mkp_subject. Do not invent a password.

PKCE is required on the platform (PKCE_REQUIRED).

3. Profile (email + username)

  • Canonical edits happen on MKP (dashboard profile).
  • Client UIs should display at least:
  • Email
  • Username / display name (display_name)
  • Read current user from the platform:
GET {MKP_BASE_URL}/api/v1/me/
Authorization: Bearer {access_token}
# or session cookie when same-site / credentials include
Accept: application/json

Example JSON:

{
  "public_id": "usr_...",
  "email": "ada@example.com",
  "display_name": "Ada",
  "email_verified": true
}

If you cache profile fields locally, refresh them on login and treat MKP as source of truth.

4. Logout

  • Clear the local Django session (Mode B/C).
  • Optionally redirect to MKP logout (/accounts/logout/) when you need the platform session cleared too (Mode A / shared browser session).

Using platform features from Django

After identity works, call product APIs through the same library:

from mkp_client import MKPClient
import os

client = MKPClient(
    base_url=os.environ["MKP_BASE_URL"],
    client_id=os.environ["MKP_SERVER_CLIENT_ID"],      # skpub_...
    client_secret=os.environ["MKP_SERVER_CLIENT_SECRET"],  # sk_...
)

# Examples (capability must be enabled on the ClientSite)
client.ai.execute("ai.grammar_review", {"text": "..."}, user_public_id="usr_...")
client.notifications.send(
    "forms.submission_received",
    "user@example.com",
    context={"payload": {"name": "Ada"}},
)

Browser forms and public embeds use site key only — see JavaScript client and Implement email on a client site.


Simple site without its own database

Minimum viable product site:

  1. Static or Django templates only (SQLite optional for non-user data, or no DB).
  2. No AUTH_USER_MODEL of your own for customers — or a stub user table filled only from OIDC.
  3. Auth + profile → MKP.
  4. Contact form → MKP forms + email gateway.
  5. Optional credits / AI → MKP wallet + AI gateway via mkp.js / mkp_client.

You are hosting UI and product-specific pages; identity and shared backend services stay on MKP.


Hard rules

  1. One identity provider — MKP. Do not add a second Google OAuth stack on the client for the same users.
  2. Never put sk_… or OIDC client secrets in browser code.
  3. Never store or reset MKP passwords in the client database.
  4. Join records with public_id / mkp_subject, not email (emails can change).
  5. Distinguish credentials:
  6. pk_… → browser
  7. skpub_… + sk_… → server APIs (MKPClient)
  8. OAuth Toolkit client_id / client_secret → OIDC login only
  9. Register exact Allowed domains and OIDC redirect URIs before testing login.

Checklist

  1. Create/activate ClientSite + domains + support email.
  2. Issue public site key + server secret; link OAuth Application with redirect URI.
  3. Choose Mode A (thin / mkp.js) or Mode B/C (Django OIDC + library).
  4. Install mkp_client and/or mkp.js; set env vars.
  5. Implement login → callback → local session (B/C) or platform redirect (A).
  6. Show profile from /api/v1/me/ (email + display name).
  7. Wire forms / notifications / AI only as needed; enable capabilities in admin.
  8. Verify: signup on MKP → return to client → same public_id on a second ClientSite.


LLM implementation brief

Copy everything in this section into another chat when you want an LLM to modify a client codebase so MKP Works becomes the centralized identity and backend for that site. Fill in the bracketed values first.

# Task: Integrate this client site with MKP Works Platform (centralized Django identity)

## Goal
MKP Works Platform must manage login, authentication, account creation, and basic profile management for this product. One human = one MKP user across all ClientSites. This site must NOT run its own customer password database or its own Google/Facebook OAuth for the same users.

Basic profile every site can show:
- email
- username / display name  (platform field: display_name)
Stable join key:
- public_id (usr_...)  — also used as OIDC subject when available

Simple sites should work with a single integration library surface and may avoid a local User DB entirely. Complex sites may keep product tables locally but still authenticate via MKP.

## Platform facts (do not invent alternatives)
- Platform base URL: {{PLATFORM_BASE_URL}}   # e.g. https://mkpworks.com
- ClientSite slug: {{SITE_SLUG}}
- Public site key (browser only): {{SITE_KEY}}   # pk_...
- Server client id: {{SERVER_CLIENT_ID}}         # skpub_...
- Server client secret: {{SERVER_CLIENT_SECRET}} # sk_...  (backend env ONLY)
- OIDC application client id: {{OIDC_CLIENT_ID}}
- OIDC application client secret: {{OIDC_CLIENT_SECRET}}  # if confidential
- OIDC redirect URI (this app): {{OIDC_REDIRECT_URI}}
  # example: https://my-app.example/accounts/mkp/callback/
- Allowed browser origin/host registered on ClientSite: {{CLIENT_ORIGIN}}
- Integration mode for THIS site: {{MODE_A_THIN | MODE_B_DJANGO | MODE_C_HYBRID}}

## Credential rules (critical)
1. pk_... = browser / mkp.js / public form APIs only. Header: X-MKP-Site-Key
2. skpub_... + sk_... = trusted server APIs only. Headers: X-MKP-Client-Id, X-MKP-Client-Secret
3. OIDC client_id/secret = login code flow against /o/ only — NOT the same as skpub_/sk_
4. NEVER put sk_ or OIDC client secrets in frontend, mobile binaries, or public docs.

## What already exists on the platform (call these; do not rebuild)
- People auth: {{PLATFORM_BASE_URL}}/accounts/login/  (email/password session login)
- OIDC: {{PLATFORM_BASE_URL}}/o/authorize/ , /o/token/ , /o/userinfo/
- PKCE is REQUIRED
- Current user API: GET {{PLATFORM_BASE_URL}}/api/v1/me/
  Auth: Bearer access_token OR session cookie
  JSON fields: public_id, email, display_name, email_verified
- Hosted browser SDK: {{PLATFORM_BASE_URL}}/sdk/v1/mkp.js
- Python server client package (copy/install from MKP repo): sdk/python/mkp_client
  Dependency: httpx
  Usage:
    from mkp_client import MKPClient
    client = MKPClient(
      base_url="{{PLATFORM_BASE_URL}}",
      client_id="{{SERVER_CLIENT_ID}}",
      client_secret="{{SERVER_CLIENT_SECRET}}",
    )
- Optional scaffold generator ON THE PLATFORM repo (not on client):
  python manage.py generate_mkp_integration --site {{SITE_SLUG}} --type django --output ./generated/{{SITE_SLUG}}

## MODE_A_THIN — no (or minimal) local user database
Implement when MODE is MODE_A_THIN:
1. Load mkp.js; MKP.init({ baseUrl: "{{PLATFORM_BASE_URL}}", siteKey: "{{SITE_KEY}}" }).
2. Send users to {{PLATFORM_BASE_URL}}/accounts/login/ (and signup) with a return next URL to this site.
3. After return, fetch GET /api/v1/me/ with credentials/session or documented mkp.js helpers and render email + display_name.
4. Do NOT add django.contrib.auth User registration for customers.
5. Optional: embed forms with <div data-mkp="form" data-form="{{FORM_SLUG}}"></div>.
6. Do not install SMTP/OAuth social SDKs on this client for MKP-owned features.

## MODE_B_DJANGO / MODE_C_HYBRID — Django app with OIDC + library
Implement when MODE is MODE_B_DJANGO or MODE_C_HYBRID:

### Env (client backend)
MKP_BASE_URL={{PLATFORM_BASE_URL}}
MKP_SITE_KEY={{SITE_KEY}}
MKP_SERVER_CLIENT_ID={{SERVER_CLIENT_ID}}
MKP_SERVER_CLIENT_SECRET={{SERVER_CLIENT_SECRET}}
MKP_OIDC_CLIENT_ID={{OIDC_CLIENT_ID}}
MKP_OIDC_CLIENT_SECRET={{OIDC_CLIENT_SECRET}}
MKP_OIDC_REDIRECT_URI={{OIDC_REDIRECT_URI}}

### Auth implementation requirements
1. Add routes, e.g.:
   - GET /accounts/mkp/login/  → start OIDC Authorization Code + PKCE
   - GET {{OIDC_REDIRECT_URI path}} → callback
   - POST/GET /accounts/logout/ → clear local session (optionally redirect to platform logout)
2. Authorize URL:
   {{PLATFORM_BASE_URL}}/o/authorize/
   params: response_type=code, client_id={{OIDC_CLIENT_ID}}, redirect_uri={{OIDC_REDIRECT_URI}},
           scope=openid profile email, state=<csrf>, code_challenge, code_challenge_method=S256
3. Token URL:
   POST {{PLATFORM_BASE_URL}}/o/token/
   exchange code + code_verifier (+ client auth if confidential)
4. Load user profile from userinfo and/or GET {{PLATFORM_BASE_URL}}/api/v1/me/ with the access token.
5. Local identity mapping:
   - Create/update a thin local user OR profile row with:
     mkp_subject = public_id (usr_...)
     email (cached)
     display_name (cached; this is the site "username")
   - set_unusable_password() for MKP-authenticated users
   - login() that local user into Django session
6. Do NOT implement local email/password signup for customers.
7. Do NOT configure Google/Facebook OAuth on this client; social login stays on MKP /accounts/.
8. Profile UI on this site may be read-only from /api/v1/me/, or link users to the platform dashboard to edit display_name/email.

### MODE_C_HYBRID extra
- Keep product-specific models in this site's database.
- Foreign keys to people must use mkp_subject/public_id (not email).
- Server features (AI, notifications, protected actions) go through MKPClient with server secrets.
- Pass user_public_id=mkp_subject when APIs require the acting user.

## UI requirements (all modes)
- Sign in / Sign up CTAs that use MKP (redirect or OIDC).
- Authenticated header/profile showing email + display_name (username).
- Logged-out state when session missing/expired.
- Clear error if OIDC callback fails (state mismatch, token error).

## Hard constraints for the implementing agent
1. Do not add Auth0/Firebase/Supabase/Clerk as a substitute IdP.
2. Do not store MKP passwords locally or build password reset against a local User table for customers.
3. Do not put server secrets in templates or JS.
4. Do not confuse OAuth Toolkit client_id with skpub_ server client id.
5. Prefer updating existing project auth URLs/templates rather than inventing a parallel auth stack.
6. If this repo is static HTML only, use MODE_A_THIN.

## Acceptance criteria
- [ ] New user can register on MKP and return to this site authenticated
- [ ] Existing MKP user can log in and see email + display_name
- [ ] Local/customer password signup path is removed or unreachable
- [ ] public_id/mkp_subject is stored or available for API calls
- [ ] Server secrets only in backend env
- [ ] (MODE_C) product models reference mkp_subject when tying data to a person
- [ ] Smoke test against {{PLATFORM_BASE_URL}} succeeds for login + GET /api/v1/me/