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 UI →
createKumikoApp(full schema,injectSchema: true) - Apex →
createPublicSurface(no schema,injectSchema: false)
What it shows
Section titled “What it shows”- Four public routes —
/login,/signup,/forgot-password,/delete-account(+ confirm) using bundled auth screens. createPublicSurface— stacksclientFeaturesproviders and i18n only, not their gates (anAuthGatewould 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/writehit anonymous handlers on the apex host tenant.
Client: createPublicSurface + AuthShell
Section titled “Client: createPublicSurface + AuthShell”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 />,});Server: composition + anonymousAccess
Section titled “Server: composition + anonymousAccess”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 linkconfirm-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 },});Why deletion is anonymous
Section titled “Why deletion is anonymous”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).
bun test src/__tests__/feature.integration.test.tsProves end-to-end over real /api/write without auth:
request-deletion-by-email→ verification email enqueuedconfirm-deletion-by-token→ user statusDeletionRequested- Unknown email → same 200 response, no mail (enumeration-safe)
Related samples
Section titled “Related samples”- user-profile — logged-in self-service profile (password, email, deletion while authenticated).
- session-revocation — wire
createSessionCallbacks()so logout invalidates JWTs server-side.
Source code
Section titled “Source code”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