@elvix.is/sdk
v0.7.24
Published
Official elvix SDK. Drop-in React components, server helpers, and an MCP server so AI coding agents integrate elvix on the first try.
Downloads
5,374
Maintainers
Readme
Why elvix
Auth is the highest-leverage place to get an integration right or wrong. Roll your own and you ship an insecure copy of OAuth that future-you debugs at 2am. Use a US provider and your German users live under American legal frame. elvix is opinionated so the first answer is the safe answer, and EU-resident so the legal frame matches your customers.
- Passwordless from day one. Email OTP, passkeys, Google.
- Drop-in React components. One provider, one form, zero boilerplate.
- Server-side verify in three lines.
- Console-configured. Brand colors, allowed methods, redirects all live in elvix Console. The SDK reads them at runtime. No prop drilling.
- Agent-friendly. Ships an MCP server so Claude, Cursor, Codex, and Gemini can integrate elvix without human shepherding.
Install
bun add @elvix.is/sdk
# or
npm install @elvix.is/sdkQuickstart
// app/layout.tsx
import { ElvixProvider } from "@elvix.is/sdk/react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<ElvixProvider clientId={process.env.NEXT_PUBLIC_ELVIX_CLIENT_ID!}>
{children}
</ElvixProvider>
</body>
</html>
);
}// app/sign-in/page.tsx
"use client";
import { ElvixSignIn } from "@elvix.is/sdk/react";
import { useRouter } from "next/navigation";
export default function SignInPage() {
const router = useRouter();
return (
<ElvixSignIn
onResult={(r) => {
if (r.ok) router.push(r.redirect ?? "/dashboard");
else console.warn(r.error, r.message);
}}
/>
);
}// app/api/protected/route.ts
export async function GET(request: Request) {
const token = request.headers.get("authorization")?.replace(/^Bearer /, "");
if (!token) return new Response("Unauthorized", { status: 401 });
const res = await fetch("https://elvix.is/api/v1/verify", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.ELVIX_API_KEY!}`,
},
body: JSON.stringify({ token }),
});
const { ok, user, roles } = await res.json();
if (!ok) return new Response("Unauthorized", { status: 401 });
return Response.json({ hello: user.id, roles });
}That is the entire integration.
Post-sign-in: onResult + navigation
onResult fires exactly once per sign-in, at the terminal state — after any in-frame onboarding panes (passkey enrollment, username claim, membership recovery) that the SDK renders itself. The host never sees those intermediate steps, so it is always correct to redirect from onResult. The success payload is self-describing:
type ElvixSignInResult =
| { ok: true; phase: "complete"; method: "google" | "email_otp" | "passkey" | "username"; redirect: string; token?: string }
| { ok: false; error: string; message?: string };phase: "complete"— terminal. Safe to redirect.method— the factor that completed sign-in.redirect— resolved destination (redirectAfterSignIn?? backend final ??/).token— present cross-origin only; verify it server-side withverifyElvixToken.
Who navigates. By default the SDK navigates to result.redirect itself after firing onResult. If you want to route yourself (SPA navigation, or to set a session cookie first), pass navigate={false} and redirect from onResult:
<ElvixSignInForm
navigate={false} // SDK stays put; host owns routing
onResult={(r) => {
if (!r.ok) return;
if (r.token) setSessionCookie(r.token); // cross-origin: persist the bearer
router.push(r.redirect); // SPA navigation
}}
/>
onAuthenticatedis deprecated — it predatesonResultcarryingmethod+ a resolvedredirect. It still fires, and its presence impliesnavigate={false}. Migrate tonavigate={false}+onResult.Migration note (0.7.13): if you previously redirected in
onResultwithoutonAuthenticated, addnavigate={false}— otherwise the SDK and your handler will both navigate.
Cross-origin passkeys (the one host rule that matters)
A passkey is bound to elvix's RP id (elvix.is). On your own origin the browser may refuse the WebAuthn call with "rp.id cannot be used with the current origin". The SDK handles this for you: it tries inline first, and on failure (passkey sign-in and passkey enrollment) it navigates the whole tab to a hosted ceremony on elvix.is, runs WebAuthn there where the RP id matches, and returns to the page it left with the session token in the URL fragment (#elvix_token=…). <ElvixProvider> reads + strips that fragment on mount and the SDK fires onResult to finish.
So the only rule for your host: mount the SDK on your sign-in PAGE and finish sign-in in onResult. Do not navigate away from the sign-in page until onResult fires.
- The ceremony returns to the page that launched it (your sign-in page, including any
?next=). If the SDK isn't still mounted there, the returned token is never consumed and the user lands unauthenticated — they'll bounce back to your gate even though the passkey was created. This is the #1 integration mistake. - Establish your own session from the token inside
onResult(verify it withverifyElvixToken, set your cookie), then navigate:
// app/sign-in/page.tsx — keep this mounted; let onResult finish the flow.
<ElvixSignInForm
navigate={false}
onResult={async (r) => {
if (!r.ok) return setError(r.message);
if (r.token) await establishSession(r.token); // server action → your httpOnly cookie
router.replace(r.redirect ?? nextParam ?? "/dashboard");
}}
/>- Nothing else is required cross-origin — no manifest config, no second redirect handler. If inline WebAuthn happens to work (browsers that honour elvix's Related Origin Requests manifest), there's no hop at all; the same
onResultfires. Either way your code is identical. - Set
redirectAfterSignInto your post-sign-in destination. It's the declarative landing target and it survives the ceremony round-trip (it's a prop, re-applied on mount).result.redirectis best-effort and falls back to/after a cross-origin hop (the backend's per-method redirect lived in state that the full-page navigation wiped). So if you have an intended destination (e.g. a?next=your gate set), pass it asredirectAfterSignIn={next}rather than relying onresult.redirect ?? next— otherwise"/"wins and the user lands on your home page instead of where they were headed.
Already signed in? Skip the form (redirectIfAuthenticated)
Pass redirectIfAuthenticated to send an already-signed-in visitor straight to the dashboard instead of showing them the sign-in form. On mount the SDK probes the session; while it checks it shows a brief loader (no form flash), and if a session exists it fires onResult with method: "session" + your resolved redirect (and navigates unless navigate={false}). No session → the form renders as normal.
<ElvixSignInForm
redirectIfAuthenticated
redirectAfterSignIn={next}
onResult={async (r) => {
if (!r.ok) return;
if (r.token) await establishSession(r.token); // same handler as a fresh sign-in
router.replace(r.redirect);
}}
/>It's opt-in (default off) so account-switch flows that want to show the form to a signed-in user keep working. You can also read the raw state via useElvixSession() → "loading" | "authenticated" | "anonymous".
Presence (automatic)
<ElvixProvider> beats a presence heartbeat automatically whenever the user is signed in — so they show as online on your app's users list in the elvix Console with zero wiring. It beats every 30s, pauses on a hidden tab, reports "idle" after 60s without input, and works cross-origin (bearer) or same-origin (cookie). Nothing to mount.
<ElvixProvider clientId={CLIENT_ID}>{children}</ElvixProvider> // presence is on
<ElvixProvider clientId={CLIENT_ID} presence={false}>…</ElvixProvider> // opt out<ElvixPresence> still exists for edge cases (beat for a different applicationId, or manual control when you set presence={false}), but you no longer need it.
AI coding agents
elvix ships first-class agent support. Three surfaces:
Discovery via llmstxt.org
https://elvix.is/llms.txt index https://elvix.is/llms-full.txt flat dump of every doc page https://elvix.is/docs/install.md per-page Markdown twin https://elvix.is/agent-prompt.md ready-to-paste system promptOpenAPI for typed REST access
https://elvix.is/openapi.yaml full spec https://elvix.is/openapi.roles.json per-endpoint role + admin scopeMCP server bundled with the SDK
{ "mcpServers": { "elvix": { "command": "npx", "args": ["-y", "-p", "@elvix.is/sdk", "elvix", "mcp"], "env": { "ELVIX_API_KEY": "eak_..." } } } }Read-only by default.
--adminopts in to mutation tools. Never logs the bearer token.
CLI
The package ships an elvix command:
# Diagnose an integration — base URL, clientId, verify endpoint, API key.
ELVIX_CLIENT_ID=client_… npx -p @elvix.is/sdk elvix doctor
# Launch the MCP server on stdio.
ELVIX_API_KEY=eak_… npx -p @elvix.is/sdk elvix mcpelvix doctor prints a green/red checklist so "why isn't elvix working" is a two-second answer. (elvix-mcp is kept as an alias for elvix mcp.)
Full agent guide: https://elvix.is/docs/agents
Components
Every <Elvix*> component the SDK ships. Drop-in React, brand chord from <ElvixProvider>, no prop drilling.
- Primitives:
ElvixCard,ElvixProvider - Sign-in:
ElvixSignIn - Identity:
ElvixUsername,ElvixIdentityForm,ElvixAvatar,ElvixBanner,ElvixRegion,ElvixLanguages - Account:
ElvixAddressBook,ElvixLegalEntities,ElvixSessions,ElvixExport,ElvixDeactivate,ElvixLeave - Hooks:
useElvixApp(),useElvixContext()
Full catalog with previews: https://elvix.is/docs/components
Server helpers
import { verifyElvixToken } from "@elvix.is/sdk/server";
const result = await verifyElvixToken(token, { apiKey: process.env.ELVIX_API_KEY! });
if (result.ok) {
// result.user, result.roles, result.scopes, result.memberships
// result.membershipBrands → [{ slug, name, logoUrl }] — full membership
// brand so you render partner branding from the session, not hardcoded
// per slug. `memberships` (slugs) stays for back-compat.
}Brand
Deep purple chord: #5d4dff (light) and #8e7dff (dark). Override per-app from the Console. Set explicit brand on <ElvixProvider> to win over the Console default.
Security
- All requests over TLS 1.3.
- Session cookies
Secure; HttpOnly; SameSite=Lax. - Per-app session TTL + sliding-window renewal, owner-configurable.
- API keys carry per-key rate limits (60/min, 10000/day default).
- CSP, CORS, CSRF double-submit, allowedOrigins enforcement all live on
elvix.is. - Disclosure: elvix.is/contact (subject "Security report").
License
MIT. See LICENSE.
Security
Found something? Read SECURITY.md. Reports go through elvix.is/contact (mark the subject "Security report"). The form confirms receipt automatically and routes to the maintainer privately.
Contributing
See CONTRIBUTING.md. PRs welcome — we run CI on every push and require it green before merge.
Maintained by
edvone · Aachen, Germany
elvix is an edvone product.
- General enquiries: edvone.dev/contact
- Sales / integration call: edvone.dev/book
- Security disclosure: elvix.is/contact (subject "Security report"; see SECURITY.md)
