Components
ElvixAppPasskeys

ElvixAppPasskeys

Per-app passkey manager. Lists and lets the user add or remove WebAuthn passkeys scoped to ONE application — the credential can only sign them in to this app. Account-level passkeys (added on `/account/security`) work everywhere and are NOT shown here.

Passkeys for Acme

Phishing-proof. These passkeys can only sign you in to Acme. Account-level passkeys you added in your elvix security settings work here too and are managed there.

app/account/apps/[appId]/page.tsx
import {
ElvixAppPasskeys,
ElvixCard,
ElvixProvider,
} from "@elvix.is/sdk/react";
// /account/apps/[appId]/page.tsx — host renders one ElvixAppPasskeys
// per app pane. The provider applies the app's brand color via CSS
// vars so the add button + row icons match the app brand chord.
export default async function AccountAppPage({ params }: { params: { appId: string } }) {
const { appId } = await params;
// ... fetch the app + brand ...
return (
<ElvixProvider theme="light" brand={appBrand} baseUrl="">
<ElvixAppPasskeys
appId={appId}
appName={app.name}
onResult={(r) => {
if (r.ok && r.kind === "added") console.log("registered");
if (r.ok && r.kind === "removed") console.log("removed", r.passkeyId);
}}
/>
</ElvixProvider>
);
}

Props

PropTypeRequiredDefaultDescription
appIdstringoptionalfrom <ElvixProvider> bootstrapDatabase id of the application this card lists passkeys for. Falls back to the app id surfaced by <ElvixProvider clientId>'s bootstrap envelope.
appNamestringoptionalfrom <ElvixProvider> bootstrapDisplay name shown in the heading, subtitle, and "Add a passkey for X" CTA. Overrides the bootstrap-derived name (handy on first-party elvix pages that already have the app row in scope).
onResult(r: ElvixAppPasskeysResult) => voidoptionalnoneTerminal callback. Fires for both add ({ ok: true, kind: "added" }) and remove ({ ok: true, kind: "removed", passkeyId }) success states, plus typed failures. ResponseDto-shape.
onAdded() => voidoptionalnoneShortcut fired ONLY when a passkey successfully registers. Hosts that need to call router.refresh() for their own server-rendered list wire this.
onRemoved(passkeyId: string) => voidoptionalnoneShortcut fired ONLY when a passkey is successfully removed. Symmetric with onAdded.
classNamestringoptional""Extra classes merged onto the outer elvix-card.
width / height / etc.size propsoptionalautoStandard <Elvix*> size knobs. See `ElvixSizeProps`.

App-scoped vs account-level. Passkeys with applicationId=null (added on /account/security) sign the user in to every app they belong to. Passkeys registered through <ElvixAppPasskeys> are bound to one app's id by the elvix register/start challenge — picking the credential on another app's sign-in screen returns credential_not_for_app. The WebAuthn RP (elvix.is) is identical for both flavors; the scope is an elvix-level policy filter.

Brand chord. The add button and row icons paint from var(--elvix-primary) installed by <ElvixProvider brand>. Mount the provider with the per-app brand colors (e.g. via the elvix resolveBrandColors helper on first-party pages) so the card feels like part of that app, not the elvix shell.

Endpoints used. GET /api/account/apps/<appId>/passkeys for the list, DELETE /api/account/apps/<appId>/passkeys?passkeyId=<id> for removal, and the standard /api/auth/passkey/register/start + /finish with an extra applicationId body field for registration. All three require the signed-in account session and a non-deleted membership in the named app. The passkey belongs to the person's user in that app, so it signs them in there. An app session can only register a passkey for its own app.

elvix.is account pages only. The component manages passkeys from the person's elvix account, on elvix.is/account/apps/<appId>/passkeys. Mounted on a customer origin it does not load a list: it says passkeys are managed in the elvix account and links there (since 0.13.0; before that it wrongly told a signed-in person to sign in first). On your own app, link to the account page instead: ${baseUrl}/account/apps/${useElvixApp().applicationId}/passkeys, or /account/apps/<applicationId> for every per-app page. Passkeys added there sign the person in to your app.

Result shape

``ts type ElvixAppPasskeysResult = | { ok: true; kind: "added" } | { ok: true; kind: "removed"; passkeyId: string } | { ok: false; error: string; message?: string }; ``

Related