Back to Convex Backend

Storing Users in the Convex Database

npm-packages/docs/docs/auth/database-auth.mdx

latest8.8 KB
Original Source

import Schema from "!!raw-loader!@site/../demos/users-and-clerk/convex/schema.ts"; import useStoreUserEffectTS from "!!raw-loader!@site/../demos/users-and-clerk/src/useStoreUserEffect.ts"; import useStoreUserEffectJS from "!!raw-loader!@site/../private-demos/snippets/users-and-clerk/useStoreUserEffect.js"; import MessagesTS from "!!raw-loader!@site/../demos/users-and-clerk/convex/messages.ts"; import UsersTS from "!!raw-loader!@site/../demos/users-and-clerk/convex/users.ts"; import App from "!!raw-loader!@site/../private-demos/snippets/src/clerkStoreUserApp.tsx"; import WebhooksSchema from "!!raw-loader!@site/../demos/users-and-clerk-webhooks/convex/schema.ts"; import WebhookMutations from "!!raw-loader!@site/../demos/users-and-clerk-webhooks/convex/users.ts"; import WebhookEndpoint from "!!raw-loader!@site/../demos/users-and-clerk-webhooks/convex/http.ts"; import WebhookMessages from "!!raw-loader!@site/../demos/users-and-clerk-webhooks/convex/messages.ts"; import WebhookHook from "!!raw-loader!@site/../demos/users-and-clerk-webhooks/src/useCurrentUser.ts"; import WebhookClient from "!!raw-loader!@site/../demos/users-and-clerk-webhooks/src/App.tsx";

If you're using Convex Auth the user information is already stored in your database. There's nothing else you need to implement.

You might want to store user information directly in your Convex database, for the following reasons:

  • Your functions need information about other users, not just about the currently logged-in user
  • Your functions need access to information other than the fields available in the Open ID Connect JWT

There are two ways you can choose from for storing user information in your database (but only the second one allows storing information not contained in the JWT):

  1. Have your app's client call a mutation that stores the information from the JWT available on ctx.auth
  2. Implement a webhook and have your identity provider call it whenever user information changes

Call a mutation from the client

Example: Convex Authentication with Clerk

(optional) Users table schema

You can define a "users" table, optionally with an index for efficient looking up the users in the database.

In the examples below we will use the tokenIdentifier from the ctx.auth.getUserIdentity() to identify the user, but you could use the subject field (which is usually set to the unique user ID from your auth provider) or even email, if your authentication provider provides email verification and you have it enabled.

Which field you use will determine how multiple providers interact, and how hard it will be to migrate to a different provider.

<Snippet source={Schema} snippet="user" title="convex/schema.ts" />

Mutation for storing current user

This is an example of a mutation that stores the user's name and tokenIdentifier:

<TSAndJSSnippet sourceTS={UsersTS} sourceJS={UsersTS} title="convex/users.js" />

Calling the store user mutation from React

You can call this mutation when the user logs in from a useEffect hook. After the mutation succeeds you can update local state to reflect that the user has been stored.

This helper hook that does the job:

<TSAndJSSnippet sourceTS={useStoreUserEffectTS} sourceJS={useStoreUserEffectJS} title="src/useStoreUserEffect.ts" />

You can use this hook in your top-level component. If your queries need the user document to be present, make sure that you only render the components that call them after the user has been stored:

<TSAndJSSnippet sourceTS={App} sourceJS={App} title="src/App.tsx" />

In this way the useStoreUserEffect hook replaces the useConvexAuth hook.

Using the current user's document ID

Similarly to the store user mutation, you can retrieve the current user's ID, or throw an error if the user hasn't been stored.

Now that you have users stored as documents in your Convex database, you can use their IDs as foreign keys in other documents:

<TSAndJSSnippet sourceTS={MessagesTS} sourceJS={MessagesTS} snippet="load-user" title="convex/messages.ts" suffix={ // do something with \user`... } });`} />

Loading users by their ID

The information about other users can be retrieved via their IDs:

<TSAndJSSnippet sourceTS={MessagesTS} sourceJS={MessagesTS} snippet="use-users" title="convex/messages.ts" prefix={import { query } from "./_generated/server"; } />

Set up webhooks

This guide will use Clerk, but Auth0 can be set up similarly via Auth0 Actions.

With this implementation Clerk will call your Convex backend via an HTTP endpoint any time a user signs up, updates or deletes their account.

Example: Convex Authentication with Clerk and Webhooks

Configure the webhook endpoint in Clerk

On your Clerk dashboard, go to Webhooks, click on + Add Endpoint.

Set Endpoint URL to https://<your deployment name>.convex.site/clerk-users-webhook (note the domain ends in .site, not .cloud). You can see your deployment name in the .env.local file in your project directory, or on your Convex dashboard as part of the Deployment URL. For example, the endpoint URL could be: https://happy-horse-123.convex.site/clerk-users-webhook.

In Message Filtering, select user for all user events (scroll down or use the search input).

Click on Create.

After the endpoint is saved, copy the Signing Secret (on the right side of the UI), it should start with whsec_. Set it as the value of the CLERK_WEBHOOK_SECRET environment variable in your Convex dashboard.

(optional) Users table schema

You can define a "users" table, optionally with an index for efficient looking up the users in the database.

In the examples below we will use the subject from the ctx.auth.getUserIdentity() to identify the user, which should be set to the Clerk user ID.

<Snippet source={WebhooksSchema} snippet="table" title="convex/schema.ts" />

Mutations for upserting and deleting users

This is an example of mutations that handle the updates received via the webhook:

<TSAndJSSnippet sourceTS={WebhookMutations} sourceJS={WebhookMutations} title="convex/users.ts" />

There are also a few helpers in this file:

  • current exposes the user information to the client, which will helps the client determine whether the webhook already succeeded
  • upsertFromClerk will be called when a user signs up or when they update their account
  • deleteFromClerk will be called when a user deletes their account via Clerk UI from your app
  • getCurrentUserOrThrow retrieves the currently logged-in user or throws an error
  • getCurrentUser retrieves the currently logged-in user or returns null
  • userByExternalId retrieves a user given the Clerk ID, and is used only for retrieving the current user or when updating an existing user via the webhook

Webhook endpoint implementation

This how the actual HTTP endpoint can be implemented:

<TSAndJSSnippet sourceTS={WebhookEndpoint} sourceJS={WebhookEndpoint} title="convex/http.ts" />

If you deploy your code now and sign in, you should see the user being created in your Convex database.

Using the current user's document

You can use the helpers defined before to retrieve the current user's document.

Now that you have users stored as documents in your Convex database, you can use their IDs as foreign keys in other documents:

<TSAndJSSnippet sourceTS={WebhookMessages} sourceJS={WebhookMessages} snippet="current-user" title="convex/messages.ts" />

Loading users by their ID

The information about other users can be retrieved via their IDs:

<TSAndJSSnippet sourceTS={MessagesTS} sourceJS={MessagesTS} snippet="use-users" title="convex/messages.ts" />

Waiting for current user to be stored

If you want to use the current user's document in a query, make sure that the user has already been stored. You can do this by explicitly checking for this condition before rendering the components that call the query, or before redirecting to the authenticated portion of your app.

For example you can define a hook that determines the current authentication state of the client, taking into account whether the current user has been stored:

<TSAndJSSnippet sourceTS={WebhookHook} sourceJS={WebhookHook} title="src/useCurrentUser.ts" />

And then you can use it to render the appropriate components:

<TSAndJSSnippet sourceTS={WebhookClient} sourceJS={WebhookClient} snippet="client-blocking" title="src/App.tsx" />