Skip to content

apex-surface-auth

The standard pattern for public apex auth: login, signup, forgot-password, and account-deletion on your marketing surface — schema-less, anonymously reachable, without leaking admin nav or internal topology.

A typical app has two mounts sharing locale and primitives:

  • Admin UIcreateKumikoApp (full schema, injectSchema: true)
  • ApexcreatePublicSurface (no schema, injectSchema: false)
  • Four public routes/login, /signup, /forgot-password, /delete-account (+ confirm) using bundled auth screens.
  • createPublicSurface — stacks clientFeatures providers and i18n only, not their gates (an AuthGate would lock the public surface).
  • AuthShellProvider — auth card inside marketing chrome instead of fullscreen (optional; default fullscreen still works).
  • composeApexAccountApp() — server feature list for all four flows.
  • Anonymous deletion — email-verified magic-link flow when the user cannot log in (GDPR Art. 17 lockout case).
  • anonymousAccess — lets unauthenticated /api/write hit anonymous handlers on the apex host tenant.
import {
ForgotPasswordScreen, LoginScreen, SignupScreen,
AuthShellProvider, emailPasswordClient,
} from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";
import {
RequestAccountDeletionScreen, ConfirmAccountDeletionScreen,
} from "@cosmicdrift/kumiko-bundled-features/user-data-rights/web";
import { createPublicSurface } from "@cosmicdrift/kumiko-renderer-web";
createPublicSurface({
clientFeatures: [emailPasswordClient()], // SessionProvider + i18n
shell: ({ children }) => (
<MarketingChrome>
<AuthShellProvider shell={(card) => <div className="py-12 flex justify-center">{card}</div>}>
{children}
</AuthShellProvider>
</MarketingChrome>
),
routes: [
{ path: "/login", component: <LoginScreen /> },
{ path: "/signup", component: <SignupScreen loginHref="/login" /> },
{ path: "/forgot-password", component: <ForgotPasswordScreen loginHref="/login" /> },
{ path: "/delete-account", component: <RequestAccountDeletionScreen /> },
{ path: "/delete-account/confirm", component: <ConfirmAccountDeletionScreen /> },
],
fallback: <LoginScreen />,
});

composeApexAccountApp() (see src/feature.ts) mounts features for all four flows. Login/signup/password reset come from auth-email-password; the anonymous email-verified deletion flow from user-data-rights:

  • request-deletion-by-email (anonymous, enumeration-safe) → magic link
  • confirm-deletion-by-token (anonymous) → starts grace period
runProdApp({
features: composeApexAccountApp({
deletionTokenSecret: process.env.DELETION_TOKEN_SECRET!,
deletionVerifyUrl: "https://app.example.com/delete-account/confirm",
sendDeletionVerificationEmail: async ({ email, verifyUrl }) => {
// Must be non-blocking (enqueue) — synchronous send enables timing oracle.
await delivery.notify("account.deletion.verify", { email, verifyUrl });
},
}),
anonymousAccess: { defaultTenantId: APEX_TENANT_ID },
});

GDPR Art. 17 applies when the user cannot log in (lockout). The flow is email-verified (magic link), not login-gated. The HMAC token carries userId

  • expiry (no DB table); purpose "deletion-request" blocks replay on other token endpoints. Second confirm is idempotent (user_not_in_active_state).
Terminal window
bun test src/__tests__/feature.integration.test.ts

Proves end-to-end over real /api/write without auth:

  1. request-deletion-by-email → verification email enqueued
  2. confirm-deletion-by-token → user status DeletionRequested
  3. Unknown email → same 200 response, no mail (enumeration-safe)
  • user-profile — logged-in self-service profile (password, email, deletion while authenticated).
  • session-revocation — wire createSessionCallbacks() so logout invalidates JWTs server-side.

The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):

// apex-surface-auth Recipe — der evidente Weg für öffentlichen Apex-Content.
//
// Eine Kumiko-App hat eine öffentliche Apex-Präsenz (Landing/Login) UND eine
// schema-getriebene Admin-UI. Die Admin-UI mountet via createKumikoApp (volles
// Schema). Die Apex mountet via createPublicSurface — schema-LOS, anonym
// erreichbar, kein Admin-Nav/Topologie-Leak. Beide teilen Locale + Primitives.
//
// Dieses Recipe zeigt:
// 1. Server: die Feature-Komposition für die 4 Account-Flows + den anonymen,
// email-verifizierten Deletion-Flow (Lockout-sicher).
// 2. Client (Kommentar unten + README): wie createPublicSurface + AuthShell
// die Screens in der Apex-Chrome mounten.
//
// CLIENT-WIRING (apex.tsx der App — renderer-web, hier nur als Referenz, da
// Recipes keine Browser-Deps ziehen):
//
// import {
// ForgotPasswordScreen, LoginScreen, SignupScreen,
// AuthShellProvider, emailPasswordClient,
// } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";
// import {
// RequestAccountDeletionScreen, ConfirmAccountDeletionScreen,
// defaultTranslations as deletionI18n,
// } from "@cosmicdrift/kumiko-bundled-features/user-data-rights/web";
// import { createPublicSurface } from "@cosmicdrift/kumiko-renderer-web";
//
// // AuthShell: die Auth-Card rendert in der Marketing-Chrome statt
// // Fullscreen. Default (ohne Provider) bleibt der Fullscreen-Wrapper.
// const shell = ({ children }) => (
// <MarketingChrome>
// <AuthShellProvider shell={(card) => <div className="py-12 flex justify-center">{card}</div>}>
// {children}
// </AuthShellProvider>
// </MarketingChrome>
// );
//
// createPublicSurface({
// clientFeatures: [emailPasswordClient()], // bringt SessionProvider + i18n
// shell,
// routes: [
// { path: "/login", component: <LoginScreen /> },
// { path: "/signup", component: <SignupScreen loginHref="/login" /> },
// { path: "/forgot-password", component: <ForgotPasswordScreen loginHref="/login" /> },
// { path: "/delete-account", component: <RequestAccountDeletionScreen /> },
// { path: "/delete-account/confirm", component: <ConfirmAccountDeletionScreen /> },
// ],
// fallback: <LoginScreen />,
// });
//
// SERVER-WIRING: die App aktiviert `anonymousAccess` (defaultTenantId = der
// Apex-Host-Tenant), damit /api/write die anonymen Deletion-Handler erreicht:
//
// runProdApp({ features: composeApexAccountApp({...}),
// anonymousAccess: { defaultTenantId: APEX_TENANT_ID } })
import { createAuthEmailPasswordFeature } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles";
import { createConfigFeature } from "@cosmicdrift/kumiko-bundled-features/config";
import { createDataRetentionFeature } from "@cosmicdrift/kumiko-bundled-features/data-retention";
import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
import { createTenantFeature } from "@cosmicdrift/kumiko-bundled-features/tenant";
import { createUserFeature } from "@cosmicdrift/kumiko-bundled-features/user";
import {
createUserDataRightsFeature,
type SendDeletionVerificationEmailFn,
} from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
export type ApexAccountAppOptions = {
/** HMAC-Secret für das Deletion-Verify-Token. */
readonly deletionTokenSecret: string;
/** Apex-Route des Confirm-Screens; der Handler hängt `?token=` an. */
readonly deletionVerifyUrl: string;
/** Versand des Verify-Magic-Links. MUSS non-blocking sein (enqueue) — ein
* synchroner Send würde ein Timing-Oracle für Account-Enumeration öffnen. */
readonly sendDeletionVerificationEmail: SendDeletionVerificationEmailFn;
};
// Volle Feature-Komposition für die öffentlichen Account-Flows. Die
// user-data-rights-Options aktivieren den anonymen Deletion-Flow; die übrigen
// Features liefern Login/Register/PW (auth-email-password) + die
// Require-Kette (user-data-rights → data-retention + compliance-profiles +
// sessions).
export function composeApexAccountApp(opts: ApexAccountAppOptions): FeatureDefinition[] {
return [
createConfigFeature(),
createUserFeature(),
createTenantFeature(),
createAuthEmailPasswordFeature(),
createDataRetentionFeature(),
createComplianceProfilesFeature(),
createSessionsFeature(),
createUserDataRightsFeature({
deletionTokenSecret: opts.deletionTokenSecret,
deletionVerifyUrl: opts.deletionVerifyUrl,
sendDeletionVerificationEmail: opts.sendDeletionVerificationEmail,
}),
];
}

📄 On GitHub: samples/recipes/apex-surface-auth/src/feature.ts