@12-apps/auth

中文文档
2.35.0发布于 2026/11/05MITTypeScriptES Module构建来源已验证近 30 天下载 3.5万

Plug-and-play authentication plugin: Auth.js wiring and the e-mail + password flow (.), host-mounted backend surface (./server: routes, messages, mail templates, the duck-typed credential and settings store seams), auth UI (./react: screens, whole login/s

原始使用文档
文档目录5 节
  1. The tables are the package's
  2. What stays yours
  3. Security model
  4. Conventions
  5. Stable login-session binding

@12-apps/auth

Authentication as a plug-in: Auth.js wiring, the e-mail + password flow, the screens and whole pages that flow needs, a mountable backend surface, and the three tables it all runs on — with no host's vocabulary anywhere in it.

Same shape as @12-apps/report-builder, deliberately. If you have adopted that one, you already know this one's layout.

Entry pointNeedsWhat is in it
.nothingcreateEmailCredentials, password policy, tokens, the admin allowlist, the rate limiter, device detection
./servernothing (Node)the Auth.js bridge (createApiAuth, auth, handlers, authConfig, credentialsProvider), routes, status vocabulary, mail templates, and the duck-typed credential + settings store seams
./reactreact, react-dom, @12-apps/uithe screens, the whole login/sign-up pages, the platform settings screen
./honohonoone mount call for the whole backend surface
./notifications@12-apps/notificationscreateAuthMailer — the four auth e-mails over an EmailDriver
./e2e@playwright/test, playwright-bddthe packaged Gherkin journeys and their steps

An entry point marks a distinct peer, never merely a module. Every peer is optional, so a backend that never renders a page installs no react.

. is the light half, and that is enforced. It value-imports nothing — no @auth/core, no react, no hono — so a background job that expires stale tokens can import { hashToken } from '@12-apps/auth' and load only that. The Auth.js runtime lives in ./server for the same reason report-builder keeps createReportBuilder there. src/__tests__/light-root.test.ts walks the root's import graph and fails if that ever stops being true.

The tables are the package's

prisma/auth.prisma owns auth_credentials, auth_tokens and auth_platform_settings, with migrations beside them.

pnpm --filter @12-apps/auth prisma:sync        # copy the partial into your schema
pnpm --filter @12-apps/auth prisma:sync:check  # CI: fail if the copy drifted

The partial is copied, never symlinked: npm pack drops symlinked entries, turbo prune dangles a link whose owner is not a declared dependency, and Prisma lstats a migration directory — so a symlinked one is silently skipped, which is a green deploy that applied no schema.

Every user reference is an opaque user_id with no foreign key into any host table. These migrations therefore apply in a repo whose users live under another name, in another database, or do not exist yet.

What stays yours

One thing, because it is the one thing a package cannot know: who your users are. EmailIdentityDelegate is three methods — findByEmail, findById, upsert — over your own user table. Everything else is here.

Alongside that, the choices that are genuinely a product's:

  • the words — every copy pack is required, never defaulted (PT_BR_* ships as one for each surface);
  • the branding — a slot on the pages, completely opaque to this package;
  • the providers — a node you render, because an OAuth button carries a callback URL, a consent gate and a redirect this package cannot own;
  • the mail vendor — you name the environment VARIABLES; which service, which key and which from-address are yours. The resolution between them is not: createEnvAuthMailer owns the sink / vendor / refuse tree, because every branch of it is a correctness property rather than a preference.

Config, and only config. The origin host's whole wiring is now:

export const authMailer = createEnvAuthMailer({
  env: { provider: "NOTIFICATIONS_EMAIL_PROVIDER", apiKey: "RESEND_API_KEY",
         from: "NOTIFICATIONS_EMAIL_FROM", sinkFile: "AUTH_EMAIL_LOG_FILE",
         origin: ["APP_PUBLIC_URL", "AUTH_URL"] },
  drivers: EMAIL_DRIVERS,
  log: createFeatureLogger("auth-email"),
});

export const { GET, POST, PUT } = mountEmailAuth({
  path: "/api/auth/email",
  credentials, messages: PT_BR_MESSAGES, resolveUserId, onSignedUp,
});

Security model

  • consumeToken must be a conditional write (UPDATE … WHERE consumed_at IS NULL) reporting whether it affected a row. That return value is the single-use guarantee.
  • Sign-up and password-reset answers are identical whether or not the address exists, and whether or not the mail left. That is why the mailer resolves rather than throws.
  • A deployment with no mail provider refuses loudly instead of falling back to logging: a reset link in a log aggregator is a credential in a log aggregator.
  • requireEmailVerification changes the sign-up contract, not just its security — see ADOPTING.md.
  • createInProcessRateLimiter() returns an InProcessRateLimiter: the AuthRateLimiter port (check(key)) plus retryAfterMs(key), the milliseconds until a key at its limit gets a fresh window (0 when it is not limited, its window expired, or it was never seen). Read-only, on the injected now. A host answering 429 turns it into Retry-After with Math.ceil(ms / 1000). The port itself is unchanged, so a shared-store limiter owes nothing new.

Conventions

  • Every surface is a create* factory taking one config object.
  • Nothing here reads an environment variable.
  • The database client is duck-typed (AuthDb, AuthSettingsDb); this package never resolves a generated Prisma client.
  • Developer-facing text is English; the shipped copy packs are pt-BR.

Full wiring, endpoint list and the decisions that are not obvious: ADOPTING.md. Journeys and harness notes: E2E.md.

Stable login-session binding

A verified session exposes optional loginSessionId, a random per-login nonce. It is minted on genuine sign-in (and encodeSessionToken), retained through ordinary Auth.js JWT renewal, and replaced on the next login. It is useful for binding an OAuth consent request to one cookie-authenticated login: account ids are too broad, while encrypted cookie bytes and Auth.js iat/jti change during normal refresh. Always read it from the verified server session, never from a client request field. Legacy cookies without the claim remain authenticated but have no binding until a fresh sign-in; session reads never invent one.

版本与发布记录

@12-apps/auth 共发布 87 个版本,首次发布于 2026/07/18;过去 12 个月发布了 87 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
2.35.0latest2026/11/051 天前
2.34.12026/10/2710 天前
2.34.02026/10/2710 天前
2.33.02026/10/2412 天前
2.32.02026/10/2116 天前
2.31.02026/10/2116 天前

关于 @12-apps/auth 的常见问题

@12-apps/auth 是什么?

Plug-and-play authentication plugin: Auth.js wiring and the e-mail + password flow (.), host-mounted backend surface (./server: routes, messages, mail templates, the duck-typed credential and settings store seams), auth UI (./react: screens, whole login/s

如何安装 @12-apps/auth?

在项目目录运行 npm install @12-apps/auth,或使用 pnpm add / yarn add / bun add。

@12-apps/auth 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@12-apps/auth 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@12-apps/auth 可以商用吗?

@12-apps/auth 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@12-apps/auth 的下载量有多少?

根据 npm 官方下载统计,@12-apps/auth 在 2026/10/04 – 2026/11/02 这 30 天内被下载 35,253 次,日均约 1,175 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@12-apps/auth 还在维护吗?

最新版本 2.35.0 发布于 2026/11/05(1 天前);至今共发布 87 个版本,其中过去 12 个月发布了 87 个;首次发布于 2026/07/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。