# OAuth Integration Guide for External Implementers How to integrate "Log in with MNW" into an external service (Multithreaded, future MNW-integrated services). This document is the contract. Implementers should not reverse-engineer entitlement logic from MNW source code — read the `perks` object and the rules below. --- ## Flow Standard OAuth 2.0 Authorization Code + PKCE (RFC 7636). See `src/routes/oauth.rs` for the server side. 1. Redirect the user to `GET /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&state=...&code_challenge=...&code_challenge_method=S256`. 2. User authenticates with MNW, gets redirected to your `redirect_uri` with `?code=...&state=...`. 3. Exchange the code at `POST /oauth/token` (form-encoded): `grant_type=authorization_code&code=...&redirect_uri=...&code_verifier=...&client_id=...`. 4. Use the returned `access_token` as a Bearer token on subsequent requests. --- ## `GET /oauth/userinfo` Canonical "what is this user entitled to" endpoint. Always returns fresh state from the MNW database — no MNW-side caching. **Auth:** `Authorization: Bearer ` **Response (200):** ```json { "user_id": "uuid", "username": "alice", "display_name": "Alice Example", "avatar_url": "https://...", "perks": { "fan_plus": false, "is_creator": true, "creator_tier": { "tier": "big_files", "features": ["file_uploads", "large_files"] } } } ``` **Errors:** - `401 invalid_token` — missing, malformed, or revoked access token. - `401 user_not_found` — token valid but user deactivated/deleted. --- ## The `perks` contract `perks` is the extension point. Implementers consume the fields they care about. New capabilities are added here as they ship — old fields are not renamed or removed without coordination. ### `fan_plus: bool` True iff the user has an active Fan+ consumer subscription (`fan_plus_subscriptions.status = 'active'`). ### `is_creator: bool` True iff the user has an active creator subscription at any tier. Equivalent to `creator_tier != null` and provided for ergonomic boolean checks. ### `creator_tier: { tier, features } | null` `null` when the user is not a creator. Otherwise: - `tier`: snake-cased tier name (`"basic" | "small_files" | "big_files" | "everything"`). Implementers **should not** gate features on this string — gate on `features` instead. The tier names exist for display and analytics. - `features`: array of capability strings backed by live platform behavior. Today: `"file_uploads"` (SmallFiles+), `"large_files"` (BigFiles+). New capabilities (e.g., `"live_streaming"`) are added when they actually launch — never as "coming soon" placeholders. ### Why structured, not flat booleans A flat `creator_tier: "big_files"` would force every implementer to memorize the tier lineup. Adding a new tier (or splitting an existing one) would break callers. The structured form means implementers gate on capabilities, and the platform owns the mapping. --- ## Refresh ergonomics Implementers cache `perks` per session, not per request. State changes (Fan+ subscribe, tier upgrade, cancellation) become visible only after refresh. **Refresh on:** 1. Login (initial `userinfo` call after token exchange). 2. Session cycle / token refresh. 3. **On demand** — when the user takes an action that should have changed perks. Example: after returning from a Fan+ checkout flow. Hit `userinfo` again and overwrite cached fields. There is no push notification of perk changes. If pull-on-demand isn't sufficient, an `/internal/perks-webhooks/register` API will be added — talk to MNW maintainers before relying on stale data. --- ## Recommended implementer pattern (Rust) ```rust struct CachedSession { user_id: Uuid, username: String, perks: Perks, fetched_at: DateTime, } // One place that calls /oauth/userinfo and overwrites cached perks. async fn refresh_session(session_id: &str) -> Result<()> { /* … */ } // Authorization check used everywhere. fn effective_plus(perks: &Perks) -> bool { perks.fan_plus || perks.is_creator } ``` Every gated route reads `effective_plus(&session.perks)`. Refresh-on-demand routes call `refresh_session` first. --- ## Stability rules - **Additive only.** New `perks` fields are non-breaking; missing fields default to "absent" (false / null / empty array). - **No renames.** Once a `features` string ships, it stays. Mistakes are deprecated, not deleted. - **No silent semantics changes.** Behavior changes to existing capability strings (e.g., raising the size threshold for `large_files`) are coordinated with implementers.