-
Notifications
You must be signed in to change notification settings - Fork 0
Authentication
Each project has a secret. Nothing sensitive ever ships in the browser bundle.
flowchart TD
R["request"] --> P{"project found?"}
P -- no --> E404["404"]
P -- yes --> ADM{"X-Loupe-Admin == secret?"}
ADM -- yes --> OKA["authorized (admin) — dashboard / MCP"]
ADM -- no --> USR{"HMAC-SHA256(user, secret)<br/>== X-Loupe-Hmac?"}
USR -- yes --> OKU["authorized (user) — SDK"]
USR -- no --> E401["401"]
The host app's server computes HMAC-SHA256(user.id, PROJECT_SECRET) and injects it
into the page as userHmac. The SDK sends it as X-Loupe-User + X-Loupe-Hmac. Writes
are gated, and a user can only post comments as themselves. The browser never sees the
secret, so identity can't be spoofed.
// on the host app's server (never in the browser)
const userHmac = crypto.createHmac("sha256", PROJECT_SECRET).update(user.id).digest("hex");
// → inject into the page and pass to Loupe.init({ userHmac })The back-office authenticates with X-Loupe-Admin = the project secret. The dashboard
reads it from ?key= (persisted to localStorage); the MCP server reads LOUPE_ADMIN_KEY.
In production these sit behind a real authenticated session.
Blobs are served by unguessable id today; production should use signed URLs.
Project secrets are seeded by hand (npm run seed) in the prototype. Production adds a
project/team management UI.
Loupe — pin feedback to the live UI, hand it to Claude. MIT licensed. GitHub · Landing page · Changelog · Report an issue
Get started
Concepts
Reference