Installation

Install the required dependencies:

npm install convex @rownd/react

Configuration

  1. Get your Rownd App Key:

    • Sign up or log in at Rownd Dashboard
    • Create/select your application
    • Copy your App Key (e.g., key_xxxxxxxx)
    • Copy your App ID This is used to verify the audience.
  2. Set up Convex:


RowndProvider Setup

Wrap your app with RowndProvider from @rownd/react/convex and pass both your Convex client and Rownd app key:

import { createRoot } from "react-dom/client";
import { ConvexReactClient } from "convex/react";
import { RowndProvider } from "@rownd/react/convex";
import App from "./App";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);

createRoot(document.getElementById("root")!).render(
  <RowndProvider client={convex} appKey="your_app_key_here">
    <App />
  </RowndProvider>
);

Server-side Usage

Add Rownd to your auth.config.js file.

Add these to your convex/auth.config.js file:

//convex/auth.config.js
export default {
  providers: [
    {
      domain: "https://api.rownd.io",
      applicationID: "app:your-rownd-app-id"
    },
  ],

[! NOTE] Ensure you have the app: in addition to your app-id. For example, applicationID: “app:app_alksdjflakjvlkeja”

Mapping Rownd IDs in Convex

It is critical to keep a mapping of the Rownd user ID in your Convex users table.
This allows you to associate Rownd-authenticated users with your app’s data.

Schema example:

// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    rowndId: v.string(),
    email: v.optional(v.string()),
  })
    .index("by_rowndId", ["rowndId"]),
  // ...other tables
});

Storing and looking up users by Rownd ID:

This example mutation ensures the existence of a user record in your database for the currently authenticated user. ctx.auth.getUserIdentity() will return all of the claim data included in the user’s JWT. You can use the subject value to map your internal users to Rownd user’s.

// convex/users.ts
import { mutation } from "./_generated/server";

export const store = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Called storeUser without authentication present");
    }

    // Check if we've already stored this identity before.
    const user = await ctx.db
      .query("users")
      .withIndex("by_rowndId", (q) =>
        q.eq("rowndId", identity.subject),
      )
      .unique();
    if (user !== null) {
      return user._id;
    }

    // If it's a new identity, create a new `User`.
    const dbUser = await ctx.db.insert("users", {
      rowndId: identity.subject,
      email: identity.email,
    });

    return dbUser;
  },
});

Accessing the Current User

To get the current authenticated user in any Convex function, always look up by the Rownd ID:

// convex/auth.ts
import { query } from "./_generated/server";

export const loggedInUser = query({
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      return null;
    }
    const user = await ctx.db
      .query("users")
      .withIndex("by_rowndId", (q) =>
        q.eq("rowndId", identity.subject),
      )
      .unique();
    return user;
  },
});

Protecting Convex Functions

Always check for authentication and use the Rownd ID mapping to associate data with users:

import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const createPost = mutation({
  args: {
    title: v.string(),
    content: v.string(),
    category: v.string(),
  },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Please log in");
    const user = await ctx.db
      .query("users")
      .withIndex("by_rowndId", (q) =>
        q.eq("rowndId", identity.subject),
      )
      .unique();
    if (!user?._id) throw new Error("User not found");

    return await ctx.db.insert("posts", {
      authorId: user._id,
      title: args.title,
      content: args.content,
      category: args.category,
      likes: 0,
    });
  },
});

Client-side Usage

Using Rownd React SDK Features

After setting up the RowndProvider, you can use all other features of the @rownd/react SDK in your app as documented in the official Rownd docs.

The Rownd React SDK only handles client-side authentication state. For proper integration with Convex, you should use Convex’s authentication hooks and utilities to ensure server-side state is properly synchronized.

Create a custom hook like useStoreUserEffect to handle the authentication flow as outlined in these Convex docs

Example:

import { useRownd } from "@rownd/react";
import { useStoreUserEffect } from "./useStoreUserEffect.js";

function Profile() {
  const { requestSignIn, signOut, user: rowndUser } = useRownd();
  const { isAuthenticated } = useStoreUserEffect();

  if (!isAuthenticated) {
    return <button onClick={() => requestSignIn()}>Sign in</button>;
  }

  return (
    <div>
      <p>Welcome, {rowndUser?.first_name}!</p>
      <button onClick={() => signOut()}>Sign out</button>
    </div>
  );
}

For more details and advanced usage, see the Rownd React SDK documentation.


Best Practices & Tips

  • Always map Rownd IDs:
    Store the Rownd user ID (identity.subject) in your users table and use it as the primary lookup for all user-specific data.
  • Use indexes for efficient lookups:
    Index your users table by rowndId for fast queries.
  • Optionally include additional profile data in JWT claims You can use the Rownd Platform to setup profile data fields for inclusion in additional JWT claims. This makes them avaiable on the ctx.auth.getUserIdentity()