convex better auth 2
Convex-native authentication server and React UI with OAuth, password, TOTP, organizations, API keys, and webhooks.
git clone https://github.com/shlomokabareti/convex-better-auth-2.0.git
cd convex-better-auth-2.0This upstream package has not been adapted or qualified for Bijection. Read the original documentation before adopting it.
Description
Convex-native authentication server and React UI with OAuth, password, TOTP, organizations, API keys, and webhooks.
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: shlomokabareti/convex-better-auth-2.0. License: See repository. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
Convex-native auth server, component, and integration.
Explore the demo ↗Upstream package FAQ
Does convex-auth require an external auth service like Clerk or Auth0?
No. convex-auth is a fully native Convex auth runtime. Users, sessions, identities, organizations, and API keys are stored directly in your Convex database and all auth logic runs inside the Convex isolate. No external auth service or long-lived server process is required.
What authentication methods does convex-auth support?
convex-auth supports email and password authentication with email verification and password reset, OAuth via Google, GitHub, and Discord, TOTP two-factor authentication with backup codes and trusted devices, organizations, API keys, webhooks, MCP OAuth helpers, and agent auth. All of these run inside the Convex native runtime.
How do I set up convex-auth in a new Convex project?
Install the package with npm install convex-auth or pnpm add @vortex-api/convex-auth convex. Generate an RS256 keypair and set JWT_PRIVATE_KEY and JWKS as Convex environment variables. Mount the auth component in convex/convex.config.ts, configure providers in convex/auth.ts using convexAuth(), register HTTP routes in convex/http.ts, then wrap your React app with ConvexAuthClientProvider from @vortex-api/convex-auth/react.
Is convex-auth an official Convex product?
No. convex-auth is an independent, community-driven project built by Vortex and published under the Apache-2.0 license. It is not affiliated with or endorsed by Convex Inc. The package is available on npm as @vortex-api/convex-auth.
How can I verify that my convex-auth installation is configured correctly?
The @vortex-api/convex-auth CLI provides two commands for this. Run pnpm dlx @vortex-api/convex-auth check to validate that your convex/ files are not importing internal package exports. Run pnpm dlx @vortex-api/convex-auth preflight to verify that VITE_CONVEX_URL, CONVEX_SITE_URL, and the component mount are configured correctly against your live deployment.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate convex better auth 2 with Bijection.
Read: https://bijection.com/components/convex-better-auth-2/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from shlomokabareti/convex-better-auth-2.0, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
@vortex-api/convex-auth
A public, Convex-native auth platform for Convex. New projects use the native runtime; a one-time Better Auth migration bridge is available for existing users.
Built by Vortex — Shlomo Kabareti.
Disclaimer: This is an independent, community-driven project. It is not affiliated with or endorsed by Convex Inc.
npm · Docs · How we got here · Packages · Convex-native auth · Architecture
Status
Public — @vortex-api/convex-auth is at 2.3.0 on npm. The Convex-native runtime (email/password, Google/GitHub/Discord OAuth, TOTP 2FA, backup codes, trusted devices, sessions, refresh tokens, organizations, API keys, webhooks, MCP auth, and agent auth) is passing full conformance. A one-time Better Auth data migration helper is in packages/auth/scripts/migrate-better-auth.ts for existing users.
How we got here
1. Better Auth was the pragmatic first rail
Convex did not yet ship a first-party auth product with the full Clerk/WorkOS surface. Better Auth did, so we started by wiring it into Convex. The community @convex-dev/better-auth adapter proved the runtime could live inside the Convex isolate.
The problem: Better Auth is built for a long-lived Node.js/Edge process. Convex functions are stateless V8 isolates with a 32 MiB source-code limit, 64 MB heap, and strict determinism for queries. The plugin model assumes it owns the request lifecycle and the tables. That mismatch produced bundle-size pressure, memory bloat, and operational issues that could not be patched away.
2. Convex Auth 2.0 showed the second rail
Convex Auth 2.0 announced the architecture this codebase was waiting for: auth as Convex tables, non-deterministic work in actions, Web Crypto, providers as metadata, and a single convexAuth({ providers }) seam. It validated that a native Convex auth runtime was possible and gave us the design vocabulary to finish the job.
3. The end state is convex-auth
convex-auth is now a fully native Convex auth runtime. It implements email/password, OAuth, 2FA, sessions, organizations, API keys, webhooks, MCP, and agent auth inside the Convex isolate. No Better Auth runtime is used. The convex-better-auth-adapter and convex-better-auth packages remain only as a one-time migration bridge for existing Better Auth users.
Read the full rationale in docs/(get-started)/how-we-got-here.md and the migration guide in docs/(clients-and-migration)/migrating-from-better-auth.md.
Packages
| Package | npm | Path | Description |
|---|---|---|---|
@vortex-api/convex-auth | @vortex-api/convex-auth | packages/auth | The only public package. Convex-native auth component, control plane, native server integration, React/React Native clients. |
Subpaths:
@vortex-api/convex-auth— server-side auth API and configuration@vortex-api/convex-auth/convex— Convex native runtime entrypoints@vortex-api/convex-auth/react— React hooks and providers@vortex-api/convex-auth/react-native— Expo / React Native client@vortex-api/convex-auth/mcp— MCP OAuth helpers@vortex-api/convex-auth/preflight— deployment readiness checks@vortex-api/convex-auth/testing— test helpers
Published under the Apache-2.0 license.
Convex-native auth
@vortex-api/convex-auth ships a Convex-native auth runtime that stores users, sessions, and identities in your Convex database and runs in the default Convex isolate. It supports email/password, Google/GitHub/Discord OAuth, 2FA, email verification, password reset, sessions, and refresh tokens. No Better Auth server is required.
This is the intended way to use the package. The Better Auth bridge below is a one-time migration tool for existing Better Auth users.
1. Install
pnpm add @vortex-api/convex-auth convex
2. Set environment variables
Generate an RS256 keypair and set it on your Convex deployment:
node --input-type=module -e '
import { randomUUID } from "node:crypto";
import { generateKeyPair, exportJWK } from "jose";
const { publicKey, privateKey } = await generateKeyPair("RS256", { extractable: true });
const pub = await exportJWK(publicKey);
const priv = await exportJWK(privateKey);
const kid = randomUUID();
pub.kid = kid;
priv.kid = kid;
console.log("JWT_PRIVATE_KEY=" + JSON.stringify(priv));
console.log("JWKS=" + JSON.stringify({ keys: [pub] }));
'
convex env set JWT_PRIVATE_KEY '<private-key-json>'
convex env set JWKS '<jwks-json>'
For email and OAuth, also set:
convex env set CONVEX_SITE_URL 'https://your-site.convex.site'
convex env set EMAIL_FROM_ADDRESS 'auth@yourdomain.com'
convex env set GITHUB_CLIENT_ID '...'
convex env set GITHUB_CLIENT_SECRET '...'
convex env set GOOGLE_CLIENT_ID '...'
convex env set GOOGLE_CLIENT_SECRET '...'
convex env set DISCORD_CLIENT_ID '...'
convex env set DISCORD_CLIENT_SECRET '...'
3. Create convex/auth.config.ts
// convex/auth.config.ts
import { createConvexAuthProvider } from "@vortex-api/convex-auth/convex";
export default {
providers: [createConvexAuthProvider()],
};
4. Mount the component
// convex/convex.config.ts
import { defineApp } from "convex/server";
import { v } from "convex/values";
import auth from "@vortex-api/convex-auth/convex.config";
const app = defineApp({
env: {
JWT_PRIVATE_KEY: v.string(),
JWKS: v.string(),
},
});
app.use(auth, {
env: {
JWT_PRIVATE_KEY: app.env.JWT_PRIVATE_KEY,
JWKS: app.env.JWKS,
},
});
export default app;
5. Configure auth in convex/auth.ts
// convex/auth.ts
import { components } from "./_generated/api";
import { convexAuth, type EmailDraft } from "@vortex-api/convex-auth/convex";
const siteUrl = process.env.CONVEX_SITE_URL?.replace(/\/$/, "");
export const auth = convexAuth({
component: components.convexAuth,
emailAndPassword: {
enabled: true,
email: {
from: process.env.EMAIL_FROM_ADDRESS ?? "auth@example.com",
appOrigin: siteUrl,
sendEmail: async (draft: EmailDraft) => {
// Send via Resend/Postmark/SES in production.
// For local dev without a provider, set ALLOW_EMAIL_TOKEN_FALLBACK=true
// so the demo can display the token; otherwise throw here.
throw new Error("Email provider not configured");
},
sendOnSignUp: true,
sendOnSignIn: false,
},
},
oauth: {
github: {
clientId: process.env.GITHUB_CLIENT_ID ?? "",
clientSecret: process.env.GITHUB_CLIENT_SECRET ?? "",
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
},
discord: {
clientId: process.env.DISCORD_CLIENT_ID ?? "",
clientSecret: process.env.DISCORD_CLIENT_SECRET ?? "",
},
},
});
export const {
signUp,
signIn,
signOut,
updateSession,
sendEmailVerification,
verifyEmail,
sendPasswordReset,
resetPassword,
verifyPassword,
twoFactorEnable,
twoFactorVerifyTOTP,
twoFactorVerifyBackupCode,
twoFactorDisable,
twoFactorGenerateBackupCodes,
} = auth;
6. Wire HTTP routes in convex/http.ts
// convex/http.ts
import { httpRouter } from "convex/server";
import { auth } from "./auth";
const http = httpRouter();
auth.addHttpRoutes(http);
export default http;
7. Wrap the React app
// src/main.tsx
import { ConvexReactClient, ConvexProvider } from "convex/react";
import { ConvexAuthClientProvider } from "@vortex-api/convex-auth/react";
import { api } from "../convex/_generated/api";
import App from "./App";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
function Root() {
return (
<ConvexProvider client={convex}>
<ConvexAuthClientProvider actions={api.auth}>
<App />
</ConvexAuthClientProvider>
</ConvexProvider>
);
}
8. Use the actions in components
// src/SignIn.tsx
import { useAuthActions } from "@vortex-api/convex-auth/react";
export function SignIn() {
const { signIn, isLoading, isAuthenticated } = useAuthActions();
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
await signIn({
email: formData.get("email") as string,
password: formData.get("password") as string,
});
}
if (isAuthenticated) {
return <p>Already signed in.</p>;
}
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit" disabled={isLoading}>
{isLoading ? "Signing in..." : "Sign in"}
</button>
</form>
);
}
9. Validate your setup
The @vortex-api/convex-auth CLI ships with check (consumer contract) and preflight (live install verification):
pnpm dlx @vortex-api/convex-auth check
pnpm dlx @vortex-api/convex-auth preflight
check validates that your convex/ files do not accidentally import internal exports. preflight verifies that VITE_CONVEX_URL / CONVEX_URL, CONVEX_SITE_URL, and the component mount are set up correctly.
Email verification and password reset are one-click via the /api/auth/verify-email and /api/auth/reset-password/:token HTTP routes. The user clicks the link, the route validates the token, and the browser is redirected to callbackURL with the token (reset only) or success state (verification). In production sendEmail should call Resend/Postmark/SES/etc.
See examples/ for working consumers and the Architecture page for the long-term design.
Better Auth bridge (for migration)
If you are already using Better Auth and want to migrate to Convex tables and the native runtime in a single step, the Better Auth compatibility bridge is available. Run the migration, cut over the runtime, then remove convex-better-auth and convex-better-auth-adapter from your dependencies.
Convex component
During the migration, mount the legacy betterAuth adapter component and the native convexAuth component together. The exact convex/convex.config.ts wiring is in docs/(clients-and-migration)/migrating-from-better-auth.md. After the cutover, you remove the legacy adapter and keep only the convex-auth component shown in the native section above.
React client
While you are running both Better Auth and convex-auth side by side, use the adapter React client and provider:
import { createAuthClient } from "better-auth/react";
import { convexClient } from "convex-better-auth-adapter/client/plugins";
export const authClient = createAuthClient({
plugins: [convexClient()],
});
import { ConvexProvider, ConvexReactClient } from "convex/react";
import { ConvexBetterAuthProvider } from "convex-better-auth-adapter/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
export function App() {
return (
<ConvexProvider client={convex}>
<ConvexBetterAuthProvider client={convex} authClient={authClient}>
{/** your app */}
</ConvexBetterAuthProvider>
</ConvexProvider>
);
}
After the cutover to the native runtime, remove the provider and use the @vortex-api/convex-auth/react client and ConvexAuthClientProvider shown in the Convex-native auth section.
Compatibility and migration
- See
docs/(get-started)/installation.mdxfor supported versions of Convex, React, React Native / Expo, Node, and pnpm. - See
docs/(clients-and-migration)/migrating-from-better-auth.mdfor the one-time migration from a Better Auth setup to the nativeconvex-authruntime.
Development
This repo uses pnpm and Vite+ (vp) for building, linting, and formatting. Documentation is generated with Blume and hosted via @convex-dev/static-hosting from the site/ workspace.
pnpm install
pnpm run typecheck # TypeScript across all packages
pnpm run lint # Lint
pnpm run build # Build all packages
pnpm run test # Run all tests
pnpm run check # Format + lint check
pnpm run fix # Auto-fix lint and formatting
CI and security scanning
A GitHub Actions workflow is defined in .github/workflows/ci.yml. It runs the full proof (typecheck, check, build, test) on Node 20.12+ and Node 22.
The repo also includes repeatable security scanning:
pnpm run scan:security
This runs Semgrep, TruffleHog, Secretlint, and a dependency audit. The OAuth demo additionally validates authorization with live browser probes against the deployed demo.
Releasing
This repo uses Changesets. To publish a release:
- Add a changeset for the affected packages:
pnpm changeset
-
Merge the resulting release PR that Changesets opens against
main. -
The
release.ymlworkflow will version the packages, publish them to npm, and create GitHub releases. It needs anNPM_TOKENrepository secret.
Attribution
The convex-better-auth-adapter package started from the community work in get-convex/better-auth and includes the Better Auth 1.7 migration from get-convex/better-auth#430. It is vendored here so the Convex + Better Auth bridge can keep pace with Better Auth releases while Convex Auth 2.0 matures. All original code remains under the Apache-2.0 license.
License
Apache-2.0 — see LICENSE.