-
Notifications
You must be signed in to change notification settings - Fork 697
(convex) guide overhaul #2119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(convex) guide overhaul #2119
Changes from 6 commits
afe7c5c
dd4ba8b
2dad910
2b3d148
145816d
b899299
7650808
f93e662
b075674
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -11,165 +11,168 @@ description: Learn how to integrate Clerk into your Convex application. | |||||
icon: "clerk", | ||||||
}, | ||||||
{ | ||||||
title: "Create a React + Convex application", | ||||||
link: "https://docs.convex.dev/quickstart/react", | ||||||
icon: "react", | ||||||
title: "Integrate a Clerk SDK into your app", | ||||||
link: "/docs/quickstarts/overview", | ||||||
icon: "code-bracket", | ||||||
}, | ||||||
]} | ||||||
exampleRepo={[ | ||||||
{ | ||||||
title: "Convex's Next.js + Clerk Template", | ||||||
link: "https://github.com/get-convex/template-nextjs-clerk" | ||||||
}, | ||||||
{ | ||||||
title: "Convex's React + Clerk Template", | ||||||
link: "https://github.com/get-convex/template-react-vite-clerk" | ||||||
} | ||||||
]} | ||||||
> | ||||||
- Create a JWT template based on Convex | ||||||
- Configure Convex with the Clerk issuer domain | ||||||
- Install and configure Clerk's React SDK | ||||||
- Configure the Clerk and Convex providers | ||||||
- Access user identity in Convex queries and mutations | ||||||
- Create a JWT template in Clerk to generate Convex JWTs | ||||||
- Configure Convex to accept JWTs from Clerk | ||||||
- Configure the Clerk and Convex providers to work together | ||||||
</TutorialHero> | ||||||
|
||||||
Convex is the full-stack TypeScript development platform. With Convex you get to build a backend with a provided realtime database, file storage, text search, scheduling and more. Paired with Clerk's user authentication and management features, you can build a powerful application with minimal effort. | ||||||
|
||||||
This tutorial assumes that you have already [set up a Clerk application](/docs/quickstarts/setup-clerk) and a [React + Convex application](https://docs.convex.dev/quickstart/react){{ target: '_blank' }}. This tutorial will also assume that you have not added Clerk to your application yet. | ||||||
With [Convex](https://www.convex.dev/), you can build a backend with a provided realtime database, file storage, text search, scheduling and more. Paired with Clerk's user authentication and management features, you can build a powerful application with minimal effort. This tutorial will show you how to integrate Clerk into your Convex application. It assumes that you have already integrated one of Clerk's SDKs into your application. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I don't feel its clear that this guide doesn't cover setting up convex There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ahh it does say it in the sentence before "This tutorial will show you how to integrate Clerk into your Convex application.", but I can say it twice and have it say "This tutorial will show you how to integrate Clerk into your Convex application. It assumes that you have already integrated one of Clerk's SDKs into your Convex app." if that wasn't clear There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea I get what your saying, I definitely just glossed over 'your Convex application', but yea to me it wasn't clear that I already needed both Clerk and Convex setup, this is simply just wiring them together. |
||||||
|
||||||
<Steps> | ||||||
## Create a JWT template based on Convex | ||||||
|
||||||
In the Clerk Dashboard, navigate to the [**JWT templates**](https://dashboard.clerk.com/last-active?path=jwt-templates) page. Select the **New template** button to create a new template based on Convex. | ||||||
1. In the Clerk Dashboard, navigate to the [**JWT templates**](https://dashboard.clerk.com/last-active?path=jwt-templates) page. | ||||||
1. Select **New template** and then from the list of templates, select **Convex**. You'll be redirected to the template's settings page. | ||||||
1. Copy and save the **Issuer** URL somewhere secure. This URL is the issuer domain for Clerk's JWT templates, which is your application's **Frontend API URL**. In development, it's format will be `https://verb-noun-00.clerk.accounts.dev`. In production, it's format will be `https://clerk.<your-domain>.com`. | ||||||
|
||||||
 | ||||||
## Map additional claims (optional) | ||||||
|
||||||
Once the Convex template is created, you will be redirected to the template's page. You can now configure the template to your needs. | ||||||
|
||||||
 | ||||||
|
||||||
The Convex template will pre-populate the default audience (`aud`) claim required by Convex. You can include additional claims as necessary. [Shortcodes](/docs/backend-requests/jwt-templates#shortcodes) are available to make adding dynamic user values easy. | ||||||
|
||||||
 | ||||||
|
||||||
By default, Clerk will sign the JWT with a private key automatically generated for your application, which is what most developers use for Convex. If you so choose, you can customize this key. | ||||||
In the **Claims** section, the default audience (`aud`) claim required by Convex is pre-mapped, as well as some other helpful claims like Convex's `name` claim to Clerk's `user.full_name` claim. You can include additional claims as necessary. [Shortcodes](/docs/backend-requests/jwt-templates#shortcodes) are available to make adding dynamic user values easy. | ||||||
|
||||||
## Configure Convex with the Clerk issuer domain | ||||||
|
||||||
The next step is to configure Convex with the issuer domain provided by Clerk. From your Clerk **JWT template** screen, find the **Issuer** input and click to **Copy** the URL. | ||||||
|
||||||
 | ||||||
|
||||||
In your `convex` folder, add an `auth.config.js` file with the following configuration: | ||||||
|
||||||
```ts {{ filename: 'convex/auth.config.js' }} | ||||||
export default { | ||||||
providers: [ | ||||||
{ | ||||||
domain: 'https://your-issuer-url.clerk.accounts.dev/', | ||||||
applicationID: 'convex', | ||||||
}, | ||||||
], | ||||||
} | ||||||
``` | ||||||
|
||||||
Replace the `domain` string with the **Issuer** URL you copied. | ||||||
1. In your `env` file, add your **Issuer** URL as the `CLERK_FRONTEND_API_URL` environment variable. If you already have it set, great! | ||||||
```env {{ filename: '.env' }} | ||||||
CLERK_FRONTEND_API_URL={{fapi_url}} | ||||||
``` | ||||||
1. In your app's `convex` folder, create a `auth.config.js` file with the following configuration: | ||||||
```ts {{ filename: 'convex/auth.config.js' }} | ||||||
export default { | ||||||
providers: [ | ||||||
{ | ||||||
domain: process.env.CLERK_FRONTEND_API_URL, | ||||||
applicationID: 'convex', | ||||||
}, | ||||||
], | ||||||
} | ||||||
``` | ||||||
|
||||||
## Deploy your changes to Convex | ||||||
|
||||||
Run `npx convex dev` to automatically sync your configuration to your backend. | ||||||
|
||||||
## Install `@clerk/clerk-react` | ||||||
|
||||||
Run the following command to install Clerk's React SDK: | ||||||
|
||||||
<CodeBlockTabs options={["npm", "yarn", "pnpm" ]}> | ||||||
```bash {{ filename: 'terminal' }} | ||||||
npm install @clerk/clerk-react | ||||||
``` | ||||||
|
||||||
```bash {{ filename: 'terminal' }} | ||||||
yarn add @clerk/clerk-react | ||||||
``` | ||||||
|
||||||
```bash {{ filename: 'terminal' }} | ||||||
pnpm add @clerk/clerk-react | ||||||
``` | ||||||
</CodeBlockTabs> | ||||||
|
||||||
## Set environment variables | ||||||
|
||||||
In your React project's root folder, you may have an `.env` file alongside `package.json` and other configuration files. If you don't see it, create it. | ||||||
|
||||||
<SignedIn> | ||||||
Add your Clerk Publishable Key to your `.env` file. It can always be retrieved from the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page in the Clerk Dashboard. | ||||||
</SignedIn> | ||||||
|
||||||
<SignedOut> | ||||||
1. In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page. | ||||||
1. In the **Quick Copy** section, copy your Clerk Publishable Key. | ||||||
1. Paste your key into your `.env` file. | ||||||
|
||||||
The final result should resemble the following: | ||||||
</SignedOut> | ||||||
|
||||||
```env {{ filename: '.env' }} | ||||||
VITE_CLERK_PUBLISHABLE_KEY={{pub_key}} | ||||||
``` | ||||||
|
||||||
## Configure the Clerk and Convex providers | ||||||
|
||||||
Both Clerk and Convex have provider components that are required to provide authentication and client context. | ||||||
|
||||||
Clerk's provider component is `<ClerkProvider>`, which should wrap your entire app at the entry point to make authentication globally accessible. See the [reference docs](/docs/components/clerk-provider) for other configuration options. | ||||||
|
||||||
Convex offers a provider that is specifically for integrating with Clerk called [`<ConvexProviderWithClerk>`](https://docs.convex.dev/auth/clerk). | ||||||
|
||||||
The following example demonstrates how to configure Clerk and Convex's providers. Clerk's `useAuth()` hook must be passed to `<ConvexProviderWithClerk>` and Clerk's `<ClerkProvider>` must be wrapped around it. | ||||||
|
||||||
```ts {{ filename: 'src/main.tsx' }} | ||||||
import React from 'react' | ||||||
import ReactDOM from 'react-dom/client' | ||||||
import App from './App' | ||||||
import './index.css' | ||||||
import { ClerkProvider, useAuth } from '@clerk/clerk-react' | ||||||
import { ConvexProviderWithClerk } from 'convex/react-clerk' | ||||||
import { ConvexReactClient } from 'convex/react' | ||||||
|
||||||
// Import your Publishable Key | ||||||
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY | ||||||
|
||||||
if (!PUBLISHABLE_KEY) { | ||||||
throw new Error('Missing Publishable Key') | ||||||
} | ||||||
|
||||||
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string) | ||||||
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render( | ||||||
<React.StrictMode> | ||||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY}> | ||||||
<ConvexProviderWithClerk client={convex} useAuth={useAuth}> | ||||||
<App /> | ||||||
</ConvexProviderWithClerk> | ||||||
</ClerkProvider> | ||||||
</React.StrictMode>, | ||||||
) | ||||||
``` | ||||||
|
||||||
## Access user identity in Convex queries and mutations | ||||||
|
||||||
You can access the user information from the JWT in Convex queries and mutations. | ||||||
Use the `ctx.auth.getUserIdentity()` which returns the parsed information from the JWT, or `null` if the client isn't authenticated. | ||||||
|
||||||
```ts | ||||||
import type { UserIdentity } from 'convex/server' | ||||||
import { query } from './_generated/server' | ||||||
|
||||||
export default query(async (ctx) => { | ||||||
const user = await ctx.auth.getUserIdentity() | ||||||
|
||||||
if (user === null) { | ||||||
return null | ||||||
} | ||||||
|
||||||
return user.tokenIdentifier | ||||||
}) | ||||||
``` | ||||||
|
||||||
You can customize the information in the JWT by navigating to the [**JWT templates**](https://dashboard.clerk.com/last-active?path=jwt-templates) page in the Clerk Dashboard. Previously, Convex explicitly listed fields derived from [OpenID standard claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). Now, Convex allows keys to accept [custom claims](https://docs.convex.dev/api/interfaces/server.UserIdentity). | ||||||
Both Clerk and Convex have provider components that are required to provide authentication and client context. You should already have Clerk's provider component, `<ClerkProvider>`, in your app. Convex offers a provider that is specifically for integrating with Clerk called `<ConvexProviderWithClerk>`. | ||||||
|
||||||
<Tabs items={["Next.js", "React"]}> | ||||||
<Tab> | ||||||
`<ConvexProviderWithClerk>` must be a Client Component. Your `app/layout.tsx` where you would import `<ConvexProviderWithClerk>` is a Server Component, and a Server Component cannot contain a Client Component. To solve this, you must first create a _wrapper_ Client Component around `<ConvexProviderWithClerk>`. | ||||||
|
||||||
```tsx {{ filename: 'components/ConvexClientProvider.tsx' }} | ||||||
'use client' | ||||||
|
||||||
import { ReactNode } from 'react' | ||||||
import { ConvexReactClient } from 'convex/react' | ||||||
import { ConvexProviderWithClerk } from 'convex/react-clerk' | ||||||
import { useAuth } from '@clerk/nextjs' | ||||||
|
||||||
if (!process.env.NEXT_PUBLIC_CONVEX_URL) { | ||||||
throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file') | ||||||
} | ||||||
|
||||||
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL) | ||||||
|
||||||
export default function ConvexClientProvider({ children }: { children: ReactNode }) { | ||||||
return ( | ||||||
<ConvexProviderWithClerk client={convex} useAuth={useAuth}> | ||||||
{children} | ||||||
</ConvexProviderWithClerk> | ||||||
) | ||||||
} | ||||||
``` | ||||||
|
||||||
Now, your Server Component, `app/layout.tsx`, can render `<ConvexClientProvider>` instead of rendering `<ConvexProviderWithClerk>` directly. It's important that `<ClerkProvider>` wraps `<ConvexClientProvider>`, and not the other way around, as Convex needs to be able to access the Clerk context. | ||||||
|
||||||
```tsx {{ filename: 'app/layout.tsx', mark: [5, 31] }} | ||||||
import type { Metadata } from 'next' | ||||||
import { Geist, Geist_Mono } from 'next/font/google' | ||||||
import './globals.css' | ||||||
import { ClerkProvider } from '@clerk/nextjs' | ||||||
import ConvexClientProvider from '@/components/ConvexClientProvider' | ||||||
|
||||||
const geistSans = Geist({ | ||||||
variable: '--font-geist-sans', | ||||||
subsets: ['latin'], | ||||||
}) | ||||||
|
||||||
const geistMono = Geist_Mono({ | ||||||
variable: '--font-geist-mono', | ||||||
subsets: ['latin'], | ||||||
}) | ||||||
|
||||||
export const metadata: Metadata = { | ||||||
title: 'Clerk Next.js Quickstart', | ||||||
description: 'Generated by create next app', | ||||||
} | ||||||
|
||||||
export default function RootLayout({ | ||||||
children, | ||||||
}: Readonly<{ | ||||||
children: React.ReactNode | ||||||
}>) { | ||||||
return ( | ||||||
<html lang="en"> | ||||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}> | ||||||
<ClerkProvider> | ||||||
<ConvexClientProvider>{children}</ConvexClientProvider> | ||||||
</ClerkProvider> | ||||||
</body> | ||||||
</html> | ||||||
) | ||||||
} | ||||||
``` | ||||||
</Tab> | ||||||
|
||||||
<Tab> | ||||||
The following example demonstrates how to configure Clerk and Convex's providers. Clerk's [`useAuth()`](/docs/hooks/use-auth) hook must be passed to Convex's `<ConvexProviderWithClerk>` and Clerk's `<ClerkProvider>` must be wrapped around it. | ||||||
|
||||||
```ts {{ filename: 'src/main.tsx', mark: [[5, 7], 19, 21] }} | ||||||
import React from 'react' | ||||||
import ReactDOM from 'react-dom/client' | ||||||
import App from './App.tsx' | ||||||
import './index.css' | ||||||
import { ClerkProvider, useAuth } from '@clerk/clerk-react' | ||||||
import { ConvexProviderWithClerk } from 'convex/react-clerk' | ||||||
import { ConvexReactClient } from 'convex/react' | ||||||
|
||||||
// Import your Publishable Key | ||||||
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY | ||||||
|
||||||
if (!PUBLISHABLE_KEY) { | ||||||
throw new Error('Add your Clerk Publishable Key to the .env file') | ||||||
} | ||||||
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render( | ||||||
<React.StrictMode> | ||||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/"> | ||||||
<ConvexProviderWithClerk client={convex} useAuth={useAuth}> | ||||||
<App /> | ||||||
</ConvexProviderWithClerk> | ||||||
</ClerkProvider> | ||||||
</React.StrictMode>, | ||||||
) | ||||||
``` | ||||||
</Tab> | ||||||
</Tabs> | ||||||
</Steps> | ||||||
|
||||||
## Finished! | ||||||
## Next steps | ||||||
NWylynko marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
You now have a fully functioning React and Convex application with Clerk authentication. Be aware that Convex may require usage of their custom hooks and methods rather than Clerk's, such as using Convex's `useConvexAuth()` hook instead of Clerk's `useAuth()` hook in some cases. For more information on how to use Convex with Clerk, see the [Convex docs](https://docs.convex.dev/auth/clerk). | ||||||
</Steps> | ||||||
Be aware that Convex may require usage of their custom hooks and methods rather than Clerk's, such as using Convex's `useConvexAuth()` hook instead of Clerk's `useAuth()` hook in some cases. For more information on how to use Convex with Clerk, see the [Convex docs](https://docs.convex.dev/auth/clerk). |
Uh oh!
There was an error while loading. Please reload this page.