Better Auth 1.6
We're excited to announce the release of Better Auth 1.6 🎉
Better Auth 1.6 serves as a bridge version as we transition to our more structured workflow.
Rather than packing in new features, this release is focused on setting the foundation for a more solid project. That said, this release brings new features, improvements, and a number of meaningful fixes.
Highlights
NewOpenTelemetry Instrumentation
Better Auth now emits OpenTelemetry spans for distributed tracing. This gives you visibility into every auth API call, including endpoint execution, hook lifecycle, and database operations.
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import { SimpleSpanProcessor, ConsoleSpanExporter } from "@opentelemetry/sdk-trace-base";
const provider = new NodeTracerProvider({
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())],
});
provider.register();Once a TracerProvider is registered, Better Auth automatically emits spans via the better-auth tracer. No additional configuration is needed in your auth setup.
Supported spans include:
- Endpoint spans:
GET /get-session,POST /sign-in/email, etc. - Database spans:
db create user,db findOne session, etc. - Hook spans: before/after hooks, middleware, onRequest/onResponse
Each span carries attributes like http.route, db.operation.name, db.collection.name, and better_auth.operation_id for filtering and grouping in your observability tool.
This feature is experimental and the span structure may change in future releases. For more information, see the instrumentation documentation.
New
Passkey Pre-Auth Registration
The passkey plugin now supports registering passkeys before the user has a session. This enables passkey-first onboarding flows where the passkey itself is the initial authentication method.
import { betterAuth } from "better-auth";
import { passkey } from "@better-auth/passkey";
export const auth = betterAuth({
plugins: [
passkey({
registration: {
requireSession: false,
resolveUser: async ({ ctx, context }) => {
// Validate context (e.g., a signed token), then create or load a user.
return { id: "user-id", name: "user@example.com" };
},
},
}),
],
});The plugin also adds support for WebAuthn extensions on both registration and authentication, letting you pass server-defined extensions like credProps.
For more information, see the passkey documentation.
New
Case Insensitive Queries
Database queries now support case-insensitive string comparisons across all adapters (Drizzle, Prisma, Kysely, MongoDB, Memory).
This is an internal API available to plugin authors and used by core where needed. Queries can specify mode: "insensitive" on individual where clauses:
adapter.findOne({
model: "user",
where: [{
field: "email",
value: "user@example.com",
mode: "insensitive",
}],
});Under the hood, each adapter picks the most efficient case-insensitive strategy its database supports.
Breaking
Align Fresh Age with Session Creation Time
The freshAge check now uses createdAt instead of updatedAt. Previously, any session refresh would reset the freshness window, meaning a session could stay "fresh" indefinitely through regular access. Now, freshness is tied to when the session was created, so sensitive operations like password changes or payment confirmations require recent sign-in.
For more information, see the session freshness documentation.
Breaking
Enable InResponseTo Validation by Default for SAML Flows
SAML InResponseTo validation is now enabled by default for SP-initiated flows, preventing replay attacks where a captured assertion is reused. IdP-initiated SSO is not affected. If you need to opt out, you can disable it explicitly:
sso({
saml: {
enableInResponseToValidation: false,
},
});For more information, see the SSO documentation.
Performance
Faster and Lighter
Two changes that affect every Better Auth user:
-
Non-blocking scrypt
Password hashing now uses the native
node:cryptoscrypt implementation, which runs on the libuv thread pool instead of blocking the main thread. On runtimes withoutnode:cryptosupport, it falls back to a pure JavaScript implementation. Existing password hashes remain fully compatible. -
Reduced package sizes
Published packages are now significantly smaller, which means faster installs and less disk usage for every project using Better Auth.
Package v1.5.6 v1.6.0 Reduction better-auth4,236 KB 2,265 KB 46%
Important Changes
Breaking Changes
| Change | Description |
|---|---|
| Align fresh age with session creation time | freshAge now uses createdAt instead of updatedAt. Sessions may require re-authentication more frequently for sensitive operations. Details |
| Enable InResponseTo validation by default for SAML flows | SAML InResponseTo validation is now on by default. Set enableInResponseToValidation: false to disable. Details |
Behavior Changes
| Change | Description |
|---|---|
| SCIM endpoint authorization enforcement | SCIM management endpoints now require proper authentication. Passkey ownership normalized to prevent cross-user access. |
| Stateless cookie cache lifetime alignment | Cookie cache maxAge now matches session expiresIn to prevent stale cached data. |
Deprecations
| Change | Description |
|---|---|
| OIDC Provider plugin | Deprecated in favor of @better-auth/oauth-provider. Will be removed in the next minor version. |
For the full list of changes, see the changelog.
Migrating to v1.6
For most users, upgrading is straightforward:
npx auth upgradeReview the Important Changes section to check if any of the breaking changes, behavior changes, or deprecations affect your setup.
How We Run Better Auth Going Forward
For a long time, every kind of change competed for the same release window. Bug fixes, additive improvements, large features, refactors, and breaking changes all shared the same release path, which meant a small fix could be ready now and still wait behind unrelated work.
The principle we keep coming back to is straightforward. We want important fixes to ship quickly without giving up predictability or backward compatibility. There should be no surprises, and when an upgrade does require action, that action should be easy to understand and simple to execute.
v1.6 is the bridge release built around that principle. Here is what that looks like in practice.
Two release tracks
Important fixes shouldn't wait behind unrelated work.
Better Auth releases now flow through two tracks instead of one. A change ships through beta first whenever adopting it would require existing users to update code, configuration, or schema.
mainis the stable track. It ships bug fixes, security work, additive improvements, and behavior changes that do not require user action. New capabilities can also land here as long as they are well-tested, non-breaking, and safe to adopt immediately.nextis the beta track. It ships new features, refactors, and breaking changes, after a beta cycle that gives users a window to adapt.
The split exists so that important fixes can move fast without waiting for unrelated work, and so that breaking changes never land without warning. For the latest stable, npm install better-auth works as before. To try the next minor in progress, install with the @beta tag.
For contributors, release decisions now happen in the pull request itself. A changeset on each PR declares its release intent, and our automated workflow routes the PR to the right branch.
New feature requests
Discussion comes before code.
Going forward, every new feature request begins with the Feature request issue template on GitHub, not with a pull request opened directly.
Large PRs landing directly are difficult to review safely while keeping the existing system stable. Starting with an issue gives us room to discuss scope, align on API shape, and schedule the work against the release track it belongs on. It also gives us a cleaner way to communicate the roadmap, so contributors and users can see where a given feature is heading and when it might land.
Bug fixes, documentation improvements, and small additive changes continue to go straight to pull requests as before.
Restructured release notes
Clarity matters as much as completeness.
Release notes are now grouped by package instead of being presented as one flat changelog. That makes it much faster to find the packages you actually use and check whether a change affects you.
This is generated by Better Release, a piece of automation we built for our own release process. On the maintainer side it reduces the kind of small human errors that used to creep into release preparation. On the user side it produces release communication that is more consistent and easier to read.
Trusted publishing
Trust matters as much as speed.
Every package we publish to npm now uses OIDC trusted publishing through GitHub Actions. There are no long-lived publish tokens sitting in CI waiting to be stolen, and every published artifact carries provenance linking it back to the exact source commit and workflow run that produced it.
This matters because most of the major incidents in the npm ecosystem have come from credential theft and supply chain compromise, not from malicious code in reviewed commits. Commit signing and contributor identity still matter, but for consumers of a package, verifiable provenance over the published artifact is the stronger guarantee.
You can verify a Better Auth package on your machine with:
npm audit signaturesContributors
Thanks to all the contributors for making this release possible!