Concepts

Auth model

Ellivate is a shared-shell platform — users sign in once to Ellivate, and your app receives a pre-authenticated viewer. No login routes, no auth libraries, no session management in your code.

The shared-shell principle

Every app on Ellivate runs inside a shell — the dashboard on web, the super-app on iPhone. The shell is where users sign in. Your app inherits that identity.

This is the single most load-bearing decision Ellivate has made. It's what lets a user open Ellivate on their phone and find every app anyone in their circle has published, all opened with one tap, without logging into each one separately. It's also what keeps app code simple — you don't need to know how OAuth works, or where sessions live, or whether someone is verified.

How the viewer token gets to your code

When a user opens your app through Ellivate's shell:

  1. Shell mints a short-lived JWT. Signed by Ellivate, valid for 15 minutes, contains the viewer's ID and the target app ID.
  2. Shell navigates to the app's handoff route <app-url>/ellivate-handoff?token=...&next=/. Ellivate wrote this route into your app at publish time.
  3. Handoff route sets the cookie. The route response is a 307 redirect to the real page, with a Set-Cookie: ellivate_viewer_token=...; HttpOnly; Secure; SameSite=None; Partitioned header.
  4. Subsequent requests carry the cookie. Your server-rendered code (Next.js server components, Flask, FastAPI) reads the cookie via standard framework APIs. Your client-side JavaScript reads window.ELLIVATE_VIEWER_TOKEN (injected by the shell) or the URL fragment.
  5. SDK validates and attaches to KV calls. When your code calls ellivate.kv.get(...), the SDK sends the viewer token as an HTTP header. The KV backend uses it to resolve per-viewer and per-group data.

Four delivery mechanisms, picked by context

1. Cookie (server-rendered runtimes)

ellivate_viewer_token as an HttpOnly cookie. Used by Next.js server components (read via next/headers), Flask (read in @before_request), FastAPI (read in the middleware). The cookie is the canonical delivery mechanism for any framework with server-side rendering.

Because the Next.js SDK reads the cookie via next/headers, it's server-only: call ellivate.* from a server component, server action, or route handler — never from a "use client" component. See Publish a Next.js app for the wrap-it-in-a-server-action pattern.

2. X-Ellivate-Viewer-Token header (server-to-server)

For Python frameworks, the auth middleware reads the cookie then stashes the token on a contextvars.ContextVar. The SDK reads the contextvar and forwards the token as a header on every KV fetch — so the KV backend sees the viewer even when the SDK is called from deep in application code with no direct access to the request.

3. window.ELLIVATE_VIEWER_TOKEN (browser / mobile)

The shell injects this into the iframe before first paint. Client-side JavaScript reads it directly:

const token = window.ELLIVATE_VIEWER_TOKEN;

Also used by the iPhone super-app — the native WebView injects the token via pre-content JS.

4. URL fragment (static SPA first load)

#__ellivate_auth=<token> on the first navigation. Fragments aren't sent to the server, so this only works for apps whose browser-side JS reads window.location.hash — primarily vanilla HTML apps. The SDK scrubs the fragment from the URL after reading so app-level hash routing isn't polluted.

Client lane vs server lane

When your code calls ellivate.kv.get(...), the SDK picks a dispatch "lane" that affects how the KV backend resolves the key:

  • Client lane — used whenever there's a per-request viewer context. Browser calls, Next.js server components, Flask/FastAPI requests after the auth middleware. KV applies per-viewer and per-group scoping.
  • Server lane — used for no-viewer contexts. Streamlit apps, long-running Python scripts, startup-time code. KV uses a single app-wide namespace.

The lane is picked automatically by the SDK. You don't configure it.

Why not run your own auth?

You could, technically. But the publish gate will reject your code. Libraries like NextAuth, Clerk, Flask-Login, firebase/auth, and hand-rolled JWT cookies are rejected by the classifier — they fight the shared-shell model and produce double-login UX where users have to sign in twice (once to Ellivate, once to your app).

Ellivate also rejects routes named /login, /signup, /auth/callback. If you need something like "connect Google Drive" to access external services on behalf of the viewer, use Ellivate's connections feature — OAuth integrations managed by the shell, exposed to your code as pre-authed API clients.

Reading the viewer

# Python
viewer = ellivate.viewer()
# { "id": "usr_...", "email": "...", "username": "...", "verified": True } or None
// JS
const viewer = await ellivate.viewer();
// { id, email, username, verified } or null

Returns null for anonymous visitors on public + open apps. Don't use viewer().id as an auth check — by the time your code runs, the shell has already gated out unauthenticated visitors on gated apps. Use it for personalization (display name, welcome message), not authorization.

What's next