Product Decisions This Supports
- Build vs. buy for authentication. Auth is high-risk, low-differentiation infrastructure. This package lets engineering buy the commodity surface (registration, login, refresh rotation, OAuth callbacks, TOTP, passwordless magic links, email verification) and spend product capacity on differentiators. A PM should treat it as a candidate to replace a bespoke auth module on the roadmap, not to add alongside it.
- Roadmap sequencing for the auth epic. The bundle ships API, session, and hybrid modes plus a
better-auth:switch-mode command, so the same implementation can serve a server-rendered Symfony app today and a token-based/API Platform backend later. That removes the "we'll have to rebuild auth when we go headless" dependency from the roadmap and lets the API-first migration proceed on its own schedule.
- Scope decision on enterprise/2FA/SSO asks. TOTP 2FA, encrypted secrets at rest, refresh-token rotation, and OAuth provider support are in-scope for a first release rather than deferred. This directly affects sales-cycle answers to "do you support 2FA and social login?" — a common procurement blocker.
- Multi-tenant SaaS strategy. Optional multi-tenant features mean tenant isolation on the auth path doesn't need a separate design track before launch — relevant if the product plan includes workspace/org segmentation in the next 2–3 quarters.
- Security posture as a launch gate. The 0.0.20 release fixed unbounded login/TOTP brute-force, an open-redirect/token-exfiltration path in magic links, plaintext session tokens at rest, and unencrypted TOTP seeds. For a PM this reframes auth security as a release-readiness input: a security review must be a scheduled workstream, not a post-launch hardening item.
- Data-ownership / vendor-lock decision. "Application-owned persistence" (Doctrine entities in your schema, MIT license) means auth data stays in the product database with no per-MAU vendor fee and no migration off a third-party identity provider if the relationship ends. This is the strongest argument for this category over Auth0/Clerk/WorkOS, and it should be presented as an explicit trade: control and cost predictability in exchange for owning the operational burden.
- Laravel-stack caveat for portfolio decisions. This specific package is a Symfony bundle, not a Laravel package — despite the brief framing. The underlying core is shared (
betterauth/multimodal-php) and the maintainer keeps a synchronized Laravel counterpart, so the same roadmap decision applies to a Laravel team, but the artifact to evaluate is the sibling package. Do not let "we can drop it into the Laravel app" leak into a decision record; verify the Laravel package's version parity and changelog before assuming feature equivalence.
When to Consider This Package
Consider adoption when most of these are true:
- The stack is Symfony 6.4/7.x/8.x on PHP 8.4 with Doctrine ORM 3 and Doctrine Migrations 4; API Platform 4 if the API mode is needed. (PHP <8.4 or Symfony <6.4 is a hard stop.)
- You need several of: PASETO v4.local tokens, refresh rotation, TOTP 2FA, passwordless/magic-link, email verification, OAuth providers — and you need them soon. The value collapses if you need only email+password.
- You accept application-owned persistence: your team owns entities, migrations, and cleanup crons (
better-auth:cleanup:sessions, better-auth:cleanup:tokens). That's a feature if you want data residency and no lock-in; a cost if you have no one to own schema changes.
- You can pin an exact pre-1.0 version (
v0.0.20), read changelogs before every bump, and run a dedicated security spike — including a review of the 0.0.20 fixes and of QueryParameterTokenExtractor staying off by default.
- You can commit to operational hygiene: stable
BETTER_AUTH_SECRET (rotating it makes encrypted TOTP seeds unreadable), and a documented key-rotation runbook.
- The blast radius is bounded: internal tools, an MVP, a greenfield product still pre-product-market-fit, or a secondary service. This is the zone where a 4-star, zero-dependent package is a reasonable bet.
Look elsewhere when any of these are true:
- You need vendor accountability. Four stars, zero dependents, no commercial backing, MIT-only support. If procurement, a security questionnaire, or an enterprise contract requires an SLA, SOC 2, or a named support contact, this is disqualified — go to a managed IdP or a Symfony-native alternative with a support contract.
- Maturity risk is unacceptable. Pre-1.0 with an explicit "API may change before v1.0.0" status, plus a rapid 0.0.2 → 0.0.20 churn including CI-only and config-validation patch releases, means upgrade cost is real. If your release cadence is quarterly and change-averse, budget for that friction or skip it.
- Your auth requirements are unusual. SAML, niche SSO providers, legacy protocols, fine-grained delegated authorization, or heavily customized flows will fight the bundle rather than benefit from it.
- You want minimal auth only. For JWT-only or session-only needs,
LexikJWTAuthenticationBundle or Symfony's own security component is less surface area to audit.
- You need edge/serverless auth. The bundle depends on the Symfony request lifecycle and Doctrine; it does not fit a stateless edge runtime.
- The stack is Laravel. Evaluate the Laravel equivalent (Fortify/Breeze/Passport/Sanctum or the maintainer's Laravel port) rather than trying to fit a Symfony bundle.
- You have no one to own security. If no engineer can review an auth dependency, the honest answer is a managed provider — not a pre-1.0 OSS bundle.
Adoption gate I'd set: timeboxed spike (≤1 week) proving install → migrations → login/refresh/2FA → logout against a copy of the real schema; a written security review of the 0.0.20 fixes and secrets handling; a version-pinning and upgrade policy; and a signed-off exit plan (what we do if we abandon it). Ship only after all four pass.
How to Pitch It (Stakeholders)
To executives (90 seconds): We can either build authentication ourselves — roughly a quarter of engineering for OAuth, 2FA, passwordless login, and token rotation, plus ongoing security upkeep — or adopt an MIT-licensed bundle that already implements that surface and keeps user data inside our own database, with no per-user fees and no vendor lock-in. The trade-off is maturity: it's a pre-1.0 package with a small community and no commercial support, so we would pin the version, run a one-week validation spike, and do a security review before it touches production. Recommendation: fund the spike, don't commit the roadmap yet. If it passes, it removes a quarter of work from the auth epic and de-risks the later API-first migration; if it fails, we've spent a week and learned what to build.
To engineering (the honest version):
- What you get: PASETO v4.local tokens, hashed-at-rest session tokens, AES-256-GCM TOTP secrets, refresh rotation with hashed
replaced_by, rate-limited login and 2FA, validated callbackUrl on magic links, OAuth (incl. Google v2 verified_email), API/session/hybrid modes, Doctrine ORM storage, API Platform integration, install/setup/cleanup console commands, and docs covering security, testing, migration, and entity customization.
- What worries me: every one of those security properties arrived in 0.0.20. The pattern is "capable but not yet adversarially tested," and the pre-1.0 API can move under us. Zero dependents means no field evidence.
- What we require before merge: pinned version and changelog-reviewed upgrades; a
BETTER_AUTH_SECRET management and rotation runbook (rotation makes existing TOTP seeds unreadable — this must be documented for on-call); the magic-link callbackUrl allow-list configured to our real frontend origin; cleanup jobs scheduled; test coverage for login/refresh/2FA/logout in our app, not just theirs; and a named owner for the auth dependency.
- Exit cost if we walk away: low. MIT license, entities in our own schema, no proprietary data format — we can fork or replace without a data migration.
Framing to use: this is a time-boxed bet on a promising but unproven dependency, not a strategic platform commitment. Success criteria and an abandon trigger should be written down before the spike starts.