@elvix.is/sdk
Identity, kept in Europe.
Passwordless authentication for React + Next.js. Hosted in Aachen, German legal frame.
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, no boilerplate.
- Server-side verification of a session token in a single call.
- Console-configured. Brand colours, allowed sign-in methods and redirects live in the elvix Console; the SDK reads them at runtime, so none of it is hardcoded in your app.
- Agent-friendly. Ships an MCP server so Claude, Cursor, Codex and Gemini can integrate elvix without hand-holding.
Getting started
→ elvix.is/docs/install walks the whole integration, with copyable code for every step.
The shape of it: create an application in the Console to get a clientId, install the package, wrap your app in the provider, and mount the sign-in form. Everything else — which methods appear, how the card looks, where users land — is Console configuration rather than props.
Sign-in and sign-up are the same door. There is no separate registration flow to build; a new user is upserted silently.
What's in the package
| Import | What it gives you |
|---|---|
@elvix.is/sdk/react | The <Elvix*> components and hooks |
@elvix.is/sdk/server | Session-token verification, webhook verification, device login |
@elvix.is/sdk/types | Shared TypeScript types, no runtime code |
@elvix.is/sdk/mcp | The MCP server, embeddable in your own host |
Components — sign-in (<ElvixSignInForm> inline or in elvix's own modal or drawer; <ElvixSignInCard>, the same card with no container, for your own sheet or dialog, so your sheet changes without an elvix release), the compact email-code sign-in (<ElvixEmailCodeSignIn email>, for an address you already have), username, identity form, avatar, banner, region, languages, address book, legal entities, the legal-entity picker (<ElvixLegalEntityPicker selectedId onPick>, choose a company for an invoice or add one in place, asked only what an invoice needs), sessions, data export, deactivate, leave. The legal-entity components keep one height for every pane or grow with each (sizing="fixed" | "flexible", with maxHeight). Fields, rows and icons take their look from CSS variables you set on any ancestor (--elvix-field, --elvix-row, --elvix-picked, --elvix-radius, --elvix-icon, --elvix-key-hints, …). Full catalogue with live previews: elvix.is/docs/components.
Hooks — read-only, current without polling. Read these instead of calling elvix yourself:
useElvixUser()— the signed-in user (display name with fallbacks, username, identity summary, languages) andloading/signed-in/signed-out. Every SDK editor refreshes it after a save;useElvixRefresh()does it on demand.useElvixRoles()/useElvixScopes()/useElvixMemberships()— what the app's admins granted the user, with names;has("admin")for checks. Changes in the Console arrive live. There is no setter: users cannot change their own access.- After a redirect sign-in (Google, GitHub), mount
<ElvixOnboarding steps={["passkey"]} />once in a layout: it opens itself on the step the server asked for (add a passkey, pick a username) and finishes it in place.useElvixOnboarding()tells a custom UI a step is pending (open your sheet on<ElvixSignInCard>, and call itsdismiss()when the person closes it); a modal<ElvixSignInForm>already resumes on its own. useElvixPronounsLabel(pronouns),useElvixLanguageNames()anduseElvixLanguageLevelLabel(level)— ready-to-render labels in the SDK's locale, the same words the editors show.- Your own words:
<ElvixProvider locale="de" messages={{ "buttons.signIn": "Rein da" }}>rewords any string, or supplies a language elvix does not ship. All fifteen shipped locales translate every button; a component'slabelprop always wins. useElvixUserMedia(userId?)— "does this user have a photo", from the same cache the avatars use.
Server helpers — verify a session token against elvix on each protected request and get the live user, roles, scopes and memberships back. Because it re-checks on every call, a banned or signed-out user stops verifying within one request, so bans take effect server-side without you writing anything. Details: elvix.is/docs/verify-backend.
getElvixLegalEntity({ apiKey, applicationId, userId, entityId, purpose? }) reads the legal entity a user picked for your app, by id, for the purpose they granted (invoicing, or payouts with what the person saved of their birth data and phone, for a payment provider's prefill), with elvix's own VAT check and name match: take only the id from the browser. A pre-selected entity is not shared until a tap or your Continue grants it (useElvixLegalEntityGrant).
If you would rather not call elvix on every request, exchange the session token once for a short-lived signed JWT and verify that locally against our JWKS. Same guide covers it.
Sessions bound to the browser (DPoP). An app that turns DPoP on in the Console binds each session to a key the browser keeps and cannot export, so a token copied out of the page is useless anywhere else. Every SDK call proves itself, and a session binds the moment it is created. Your backend takes part with two calls: elvixProof(url, { method, token }) in the browser makes a proof for your route, and verifyElvixToken({ token, proof, htm, htu }) on the server passes it on. exchangeElvixToken() gives a DPoP-bound JWT for local verification. Same guide.
The two rules worth knowing before you ship
Cross-origin passkeys must return to the page that launched them. If your app is on its own domain, a passkey ceremony has to come back to the page holding the sign-in form, not to your final destination. Send the user straight to the dashboard and the passkey registers correctly but the session is never established, so they bounce back to the sign-in gate. The SDK does the right thing by default; this only bites if you override the return URL.
Presence is automatic. The provider reports a signed-in user as online by itself. You do not need to mount anything extra, and mounting the presence component "to be safe" is the common mistake. Turn it off with a single prop if you do not want it.
CLI
The package ships an elvix command with three subcommands: doctor diagnoses an integration and prints a green/red checklist, login signs a headless tool into an app via the OAuth 2.0 device flow, and mcp launches the MCP server on stdio.
For AI coding agents
Point your agent at elvix.is/docs/developers, or give it the MCP server so it can read and manage your elvix tenant directly. It is read-only unless you opt in to mutations, and it needs an API key from the Console.
Each MCP tool is bound to one endpoint. Callers fill in the placeholders of that endpoint's path; they cannot supply a path, host or scheme of their own, and the server refuses to follow redirects. That matters because an agent's context routinely contains untrusted text, and the server holds a live API key.
If you ran the MCP server on 0.10.1 or earlier, upgrade and rotate that key — those versions let a tool argument redirect the request and take the key with it. See SECURITY.md.
Compatibility
React 18+, Next.js 15+ (optional), Node 20+. Published as ESM.
Security
TLS 1.3 throughout. Session cookies are Secure; HttpOnly; SameSite=Lax, with a per-app TTL and sliding renewal you configure in the Console. API keys carry per-key rate limits. CSP, CORS, CSRF protection and allowed-origin enforcement live on elvix.is.
Found something? Read SECURITY.md and report through elvix.is/contact with the subject "Security report". The form confirms receipt automatically and routes to the maintainer privately. Past advisories are listed in SECURITY.md.
Brand
Deep purple: #5d4dff on light, #8e7dff on dark. Override per application from the Console, or set it explicitly on the provider to win over the Console default.
The mark itself is <ElvixLogo size={14} /> (add withText for the wordmark). Its shield takes your text colour, so it fits light and dark: use it to show what elvix handles in your own UI, such as the settings rows that open elvix's editors.
Contributing
See CONTRIBUTING.md. CI runs on every push and must be green before merge.
License
MIT. See LICENSE.
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")