Available in v0.3.0Application-owned membership

More accounts. The same hard boundary.

Use createMultiUserAuth when an application needs more than one approved identity. You provide one authorization callback; the package keeps the email-code, passkey, origin, rate-limit, and session policy consistent.

Responsibility boundary

Authentication identifies. Your product authorizes.

The callback answers only whether an email may authenticate now. Invitations, teams, roles, billing access, and domain permissions stay in application-owned data and route logic.

Membership

Your application decides which normalized email addresses are currently approved.

Roles

Keep roles and permissions in your domain tables; the auth package returns identity only.

Revocation

Session creation, resolution, and authenticated passkey operations recheck current membership.

Isolation

Every application still owns a separate D1 database, secret, cookies, passkeys, and recovery policy.

Configuration

Supply a live membership check

The package normalizes the email before calling authorizeEmail. Check an indexed, authoritative membership source and return only active membership—not a cached role decision.

typescript
import { createMultiUserAuth } from
  "@santi020k/auth-cloudflare";

app.all("/api/auth/*", (context) => {
  const auth = createMultiUserAuth({
    appName: "Example team workspace",
    applicationOrigin: context.env.APPLICATION_ORIGIN,
    authServerURL: context.env.AUTH_SERVER_URL,
    cookiePrefix: "example-team",
    database: context.env.AUTH_DB,
    secret: context.env.AUTH_SECRET,
    authorizeEmail: (email) =>
      isActiveMember(context.env.AUTH_DB, email),
    sendVerificationOTP: ({ email, otp }) =>
      sendLoginCode(context.env, email, otp),
    waitUntil: (task) => {
      context.executionCtx.waitUntil(task);
    },
  });

  return auth.handler(context.req.raw);
});

Lifecycle

Check membership at every meaningful boundary

  1. Request a code.Approved and rejected addresses receive the same success-shaped response. Only approved addresses receive mail. Better Auth may persist a hashed, expiring verification record as part of that uniform flow.
  2. Create or update identity.The database hook blocks user writes when the callback no longer approves the normalized email.
  3. Create or resolve a session.The callback runs again and authenticated passkey operations are guarded. A removed member cannot create a new session, mutate passkeys, or receive a resolved identity even if an unexpired session record remains stored.
  4. Apply product permissions.Use the returned email and user ID to load current roles from application-owned domain data.

Migration

No authentication schema migration is required

The canonical user and session tables already support multiple identities. Add a consumer-owned membership table only if your product does not have an authoritative member source, and introduce it through that application's normal additive migration process.

Moving from createOwnerAuth

  1. Keep the same database, secret, cookie prefix, origins, delivery callback, and schema.
  2. Add the current owner to the authoritative membership source before changing the factory.
  3. Replace the owner factory, browser client, and Hono middleware with the generic names shown above.
  4. Verify owner access, a second approved account, a rejected account, and immediate membership revocation.