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 |
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.
- Register a ClientSite; issue a public site key.
- Load hosted
mkp.js; callMKP.init({ baseUrl, siteKey }). - Send users to MKP for login/signup (
/accounts/login/, signup, social). - After login, call
GET /api/v1/me/(session cookie or token) to show email + display name. - 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.
Mode B — Django product with the MKP library (recommended default)¶
Best for a real Django app on its own origin that wants local sessions and optional local models.
- Register ClientSite + Allowed domains + OAuth Application + credentials (see Prerequisites).
- Install the MKP client library (Python package under
sdk/python/mkp_client) and wire auth views (scaffold viagenerate_mkp_integration, or implement the OIDC steps in the LLM brief). - Users click “Sign in” → Authorization Code + PKCE against
{PLATFORM}/o/authorize/. - On callback, exchange code at
/o/token/, read userinfo / claims, establish a local Django session. - Prefer a thin local user (or profile) keyed by
mkp_subject = public_id, with cachedemail+display_name. Do not set usable local passwords for MKP users. - Server-side platform features (AI, notifications, actions) use
MKPClientwith 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:
- ClientSite — name, slug, status active,
site_type(static / django / …),support_email, homepage URL. - Allowed domains — exact browser hosts that may call public APIs (and Origin checks for forms).
- Callback URLs — OIDC redirect URI(s) for your Django app (must also be on the linked OAuth Application).
- Credentials
- Public site key
pk_…— browser /mkp.jsonly. - Server secret —
X-MKP-Client-Id=skpub_…,X-MKP-Client-Secret=sk_…(shown once). Backend env only. - OAuth Application (OIDC) — confidential or public + PKCE; redirect URIs matching your
/accounts/mkp/callback/(or equivalent). Linked from the ClientSite. - 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):
- 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 - User authenticates on MKP (password or social).
- MKP redirects to your
redirect_uriwith?code=&state=. - Your backend exchanges the code at
{MKP_BASE_URL}/o/token/(includecode_verifierfor PKCE). - Call
{MKP_BASE_URL}/o/userinfo/or decode the ID token; resolvesub/public_id, email, name. login()a local Django user (or session) mapped bymkp_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:
- 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:
- Static or Django templates only (SQLite optional for non-user data, or no DB).
- No
AUTH_USER_MODELof your own for customers — or a stub user table filled only from OIDC. - Auth + profile → MKP.
- Contact form → MKP forms + email gateway.
- 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¶
- One identity provider — MKP. Do not add a second Google OAuth stack on the client for the same users.
- Never put
sk_…or OIDC client secrets in browser code. - Never store or reset MKP passwords in the client database.
- Join records with
public_id/mkp_subject, not email (emails can change). - Distinguish credentials:
pk_…→ browserskpub_…+sk_…→ server APIs (MKPClient)- OAuth Toolkit
client_id/client_secret→ OIDC login only - Register exact Allowed domains and OIDC redirect URIs before testing login.
Checklist¶
- Create/activate ClientSite + domains + support email.
- Issue public site key + server secret; link OAuth Application with redirect URI.
- Choose Mode A (thin /
mkp.js) or Mode B/C (Django OIDC + library). - Install
mkp_clientand/ormkp.js; set env vars. - Implement login → callback → local session (B/C) or platform redirect (A).
- Show profile from
/api/v1/me/(email + display name). - Wire forms / notifications / AI only as needed; enable capabilities in admin.
- Verify: signup on MKP → return to client → same
public_idon a second ClientSite.
Related Pages¶
- Authentication
- Client registration
- JavaScript client
- Python client
- Implement email on a client site
- Admin guide
- API
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/