Mike Cann's avatar
Mike Cann
5 hours ago

Convex Auth v2 is finally here

I can't believe it, but it's finally here! You know, nothing big, just the thing everyone has been complaining about Convex lacking for like... forever, and the thing I have yet to do a proper video or post on. That's right, it's auth!

People asking about auth on Convex over the years: GitHub issues, a Reddit comment and a post from our CEOPeople asking about auth on Convex over the years: GitHub issues, a Reddit comment and a post from our CEO

Convex Auth v2 is out, and it's our proper, official, non-beta-forever version that I can now recommend for everyone (well, with a little asterisk, which I'll get to at the end 😉).

The Convex Auth docs: "These are the docs for Convex Auth v2, the current stable release"The Convex Auth docs: "These are the docs for Convex Auth v2, the current stable release"

The team have been working on it for many months now and rebuilt the whole thing from scratch on the latest Convex goodness like components and end to end type safety. Let's take a look.

What is Convex Auth?

If you aren't familiar, Convex Auth is auth that lives right inside your Convex backend. So there's no additional third party service to sign up for, and no per user billing like you might get with Clerk or WorkOS (more on those a little later).

Clerk's pricing page: 50,000 monthly retained users included, then $0.02 per user per monthClerk's pricing page: 50,000 monthly retained users included, then $0.02 per user per month

Out of the box it does passwords, passkeys and the usual social logins like Google, Apple and GitHub:

The sign-in methods in the Convex Auth docsThe sign-in methods in the Convex Auth docs

The old Convex Auth (v1) sat in beta for about two years, with this warning on it the whole time:

The old docs: "Convex Auth is in beta"The old docs: "Convex Auth is in beta"

So it's really nice to finally see that go.

Having my agent add it

Actually you know what, I think the best way to show it off is to just have an agent add it to an existing app. So here's a little notes app I had my agent cook up for me. Very simple, very ugly, but you can add a note in one window and it shows up in the other:

0:00 / 0:00

There's no auth in here at all, so anybody can go onto the site and add a note at any time, which is obviously not ideal.

So I opened up my favourite agentic development environment (Threadbear, if you're interested) and asked it to add Convex Auth v2, something like this:

Add username + password and passkey sign-in to this app with Convex Auth v2,
following the docs at https://docs.convex.dev/auth/convex-auth. Each user's
notes should be private to them.

It goes off and reads the lovely new docs, and then starts wiring it all up. Here's a run of the same prompt, sped up:

0:00 / 0:00

A few minutes later, huzzah! We have auth. I created an account with a username and password in the left window, and a second account in the right one, and each of them only sees their own notes:

0:00 / 0:00

Very nice, it works.

How it works

So the way this works is it adds components to your project. There's one for the core of Convex Auth, and then one for each kind of sign-in you want, whether that's username and password, passkeys, or whatever:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import auth from "@convex-dev/auth/convex.config.js";
import authPasskey from "@convex-dev/auth/components/passkey/convex.config.js";
import authPassword from "@convex-dev/auth/components/password/convex.config.js";
import authUsername from "@convex-dev/auth/components/username/convex.config.js";

const app = defineApp();

app.use(auth);
app.use(authPassword);
app.use(authPasskey);
app.use(authUsername);

export default app;

It does this so each one keeps its own data in its own tables, nicely separated and inaccessible from the rest of your app, but still visible in the dashboard if you want to inspect it. So for example, the passwords live over here in the password component, and they're hashed:

The authPassword component's passwords table in the Convex dashboard, showing a hashed passwordThe authPassword component's passwords table in the Convex dashboard, showing a hashed password

And your own code can't actually read them. All it can do is ask the component to set a password or check one, so that's one less thing to worry about your application code getting its hands on.

No more keys

Then there's the auth config, and that's it, just one line:

// convex/auth.config.ts
import type { AuthConfig } from "convex/server";
import { convexAuthProvider } from "@convex-dev/auth/server";

export default {
  providers: [convexAuthProvider()],
} satisfies AuthConfig;

There are no keys to generate and paste into your environment variables or anything like that. With v1 you had to run a script to make a private key and a JWKS:

The v1 setup docs: a generateKeys.mjs script that prints JWT_PRIVATE_KEY and JWKSThe v1 setup docs: a generateKeys.mjs script that prints JWT_PRIVATE_KEY and JWKS

and then copy the whole output into your deployment's environment variables, which was a bit of a nightmare:

The v1 setup docs: "Copy the whole output and paste it into your Convex dashboard deployment's Environment Variables"The v1 setup docs: "Copy the whole output and paste it into your Convex dashboard deployment's Environment Variables"

Now your Convex deployment just signs the tokens itself, which is really nice if you ask me.

Your users, in your own table

Next up is the createUser mutation. I'm not going to go into the code too much, but I do want to point it out because it's a core thing that v2 does differently to the other options out there.

// convex/users.ts
export const createUser = internalMutation({
  args: {
    provider: v.object({
      name: v.union(v.literal("password"), v.literal("passkey")),
      accountId: v.string(),
      profile: v.object({}),
    }),
  },
  returns: v.id("users"),
  handler: async (ctx) => {
    return await ctx.db.insert("users", {});
  },
});

When someone signs up to your app, Convex Auth calls this so that you create the user in your own users table, in your own schema, and you get to decide what lives alongside each user in there. Right now there's nothing in it but the ids:

The users table in the Convex dashboard: two users with only _id and _creationTimeThe users table in the Convex dashboard: two users with only _id and _creationTime

But if you want something like a display name, a nickname or a birthday, you just add it to that table yourself.

And it's all done in the same transaction as the sign up itself. So if something goes wrong halfway through, you don't end up with half a user hanging around:

0:00 / 0:00

Which is great.

If you've used Clerk, WorkOS or another third party auth provider before, you might remember having to set up webhooks from their system into Convex, so you have a copy of your users in your own database. Well, we don't need that any more with v2, because the users are already in your database:

0:00 / 0:00

It's reactive

Oh, and because sessions are just Convex data, it's all reactive like everything else. If I sign out in one tab, the other tab signed into the same account signs out straight away:

0:00 / 0:00

Noice.

That asterisk

Right, so remember that little asterisk from the start? v2 doesn't do everything quite yet. Right now:

  • there's no multi-factor authentication
  • there's no magic links or one-time codes by email
  • it works with React and Next.js only

I'm told the extra good stuff is on its way, and if there's something you'd like to see sooner, let us know on Ship.

So which one should you use?

With Clerk, WorkOS, Better Auth, the old Convex Auth v1 and now v2, you might be wondering which one you should actually be using. Here's where I'm sitting:

A table comparing Convex Auth v2, Convex Auth v1, the Better Auth component, and Clerk and WorkOS, for a new app, an app already using it, and needing SSOA table comparing Convex Auth v2, Convex Auth v1, the Better Auth component, and Clerk and WorkOS, for a new app, an app already using it, and needing SSO

If you're starting a new Convex app, just go with Convex Auth v2. It's the one we'll be supporting going forward, so it's going to get all the new features and all the security goodness.

If you're already on Convex Auth v1, there's no migration tooling just yet, though your agent can probably work out how to upgrade you, and the docs have a guide for coming from v1. v1 is still getting security updates for now.

As for Better Auth and the Better Auth component: Better Auth was built for traditional databases rather than for Convex, so a bunch of things never quite fit the Convex model. I personally wouldn't start a new app on it, and the team are working on a way to help people move from it to Convex Auth v2.

And Clerk and WorkOS are both still great if you need the big enterprise-y features like SSO:

WorkOS's pricing page: free up to 1 million users, then $2,500 a month per extra millionWorkOS's pricing page: free up to 1 million users, then $2,500 a month per extra million

Conclusion

So just to recap: Convex Auth v2 lives right inside your backend as components, one per sign-in method. There are no keys to generate, your users are a normal table in your app, sign up happens in one transaction, and sessions are reactive like everything else in Convex.

I love this because it feels very Convex'y. It's all just code and data in your own deployment, which also makes it really easy for your agent to set up, like we saw above. For the vast majority of apps you're probably going to be building, I'd say Convex Auth v2 is where to go.

Until next time, cheerio! 👋

All gas, no breakages

Convex is the reactive backend platform that keeps up with you and your agents. Database, functions, workflow, sync, search, file storage, and more. All TypeScript, zero glue.

Get started