Expo (React Native)
Install and configure Convex + Better Auth for Expo.
Installation
Install packages
Install the component, a pinned version of Better Auth, and ensure the latest version of Convex.
1.25.0 or later.npm install convex@latest @convex-dev/better-auth
npm install better-auth@1.3.27 @better-auth/expo@1.3.27 --save-exactInstall additional Expo dependencies
expo-secure-store is used for secure cookie storage.
npx expo install expo-secure-storeRegister the component
Register the Better Auth component in your Convex project.
import { defineApp } from "convex/server";
import betterAuth from "@convex-dev/better-auth/convex.config";
const app = defineApp();
app.use(betterAuth);
export default app;Add Convex auth config
Add a convex/auth.config.ts file to configure Better Auth as an authentication provider.
export default {
providers: [
{
domain: process.env.CONVEX_SITE_URL,
applicationID: "convex",
},
],
};Set environment variables
Generate a secret for encryption and generating hashes. Use the command below if you have openssl installed, or use the button to generate a random value instead. Or generate your own however you like.
npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)Add environment variables to the .env.local file created by npx convex dev. It will be picked up by your framework dev server.
# Deployment used by \`npx convex dev\`
CONVEX_DEPLOYMENT=dev:adjective-animal-123 # team: team-name, project: project-name
EXPO_PUBLIC_CONVEX_URL=https://adjective-animal-123.convex.cloud
# Same as EXPO_PUBLIC_CONVEX_URL but ends in .site
EXPO_PUBLIC_CONVEX_SITE_URL=https://adjective-animal-123.convex.siteCreate a Better Auth instance
Create a Better Auth instance and initialize the component.
import { createClient, type GenericCtx } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { betterAuth } from "better-auth";
import { expo } from '@better-auth/expo'
import { components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
// The component client has methods needed for integrating Convex with Better Auth,
// as well as helper methods for general use.
export const authComponent = createClient<DataModel>(components.betterAuth);
export const createAuth = (
ctx: GenericCtx<DataModel>,
{ optionsOnly } = { optionsOnly: false },
) => {
return betterAuth({
// disable logging when createAuth is called just to generate options.
// this is not required, but there's a lot of noise in logs without it.
logger: {
disabled: optionsOnly,
},
trustedOrigins: ["your-scheme://"],
database: authComponent.adapter(ctx),
// Configure simple, non-verified email/password to get started
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
},
plugins: [
// The Expo and Convex plugins are required
expo(),
convex(),
],
});
};
// Example function for getting the current user
// Feel free to edit, omit, etc.
export const getCurrentUser = query({
args: {},
handler: async (ctx) => {
return authComponent.getAuthUser(ctx);
},
});Create a Better Auth client instance
Create a Better Auth client instance for interacting with the Better Auth server from your client.
import { createAuthClient } from "better-auth/react";
import { convexClient } from "@convex-dev/better-auth/client/plugins";
import { expoClient } from '@better-auth/expo/client'
import Constants from 'expo-constants'
import * as SecureStore from 'expo-secure-store'
export const authClient = createAuthClient({
baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
plugins: [
expoClient({
scheme: Constants.expoConfig?.scheme as string,
storagePrefix: Constants.expoConfig?.scheme as string,
storage: SecureStore,
}),
convexClient(),
],
});Mount handlers
Register Better Auth route handlers on your Convex deployment.
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./auth";
const http = httpRouter();
authComponent.registerRoutes(http, createAuth);
export default http;Set up Convex client provider
Wrap your app with the ConvexBetterAuthProvider component.
import { StrictMode } from "react";
import { Slot } from "expo-router";
import {
ConvexReactClient,
ConvexProvider,
} from "convex/react";
import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
import { authClient } from "@/lib/auth-client";
const convex = new ConvexReactClient(import.meta.env.EXPO_PUBLIC_CONVEX_URL as string, {
// Optionally pause queries until the user is authenticated
expectAuth: true,
unsavedChangesWarning: false,
});
return (
<StrictMode>
<ConvexProvider client={convex}>
<ConvexBetterAuthProvider client={convex} authClient={authClient}>
<Slot />
</ConvexBetterAuthProvider>
</ConvexProvider>
</StrictMode>
);Follow steps from the Better Auth Expo guide
The Expo integration for Better Auth requires a few involved steps that could change over time. Follow steps 3, 6, and 7 of their Expo integration guide.
You're done!
You're now ready to start using Better Auth with Convex.
Usage
Check out the Basic Usage guide for more information on general usage. Below are usage notes specific to Expo.
Social sign-in
Social sign-in for Expo works the same as with full stack frameworks, but the authorized origin and redirect URI are based on your Convex site URL instead of your application domain.
For example, with Google sign-in, the authorized origin and redirect URI would look like:
// authorized origin
https://adjective-animal-123.convex.site
// authorized redirect URI
https://adjective-animal-123.convex.site/api/auth/callback/googleExpo Web support
To use Expo Web along with Expo/Expo Go, you'll need to follow a few additional steps.
Update environment variables
To use Expo Web, follow step 4 of the React
guide
to add your Expo Web site URL as the SITE_URL environment variable.
Update Better Auth instance
Add the site URL and the cross-domain plugin to the Better Auth instance.
import { createClient, type GenericCtx } from "@convex-dev/better-auth";
import { convex } from "@convex-dev/better-auth/plugins";
import { betterAuth } from "better-auth";
import { expo } from "@better-auth/expo";
import { components } from "./_generated/api";
import { DataModel } from "./_generated/dataModel";
import { query } from "./_generated/server";
// The component client has methods needed for integrating Convex with Better Auth,
// as well as helper methods for general use.
export const authComponent = createClient<DataModel>(components.betterAuth);
const siteUrl = process.env.SITE_URL!;
export const createAuth = (
ctx: GenericCtx<DataModel>,
{ optionsOnly } = { optionsOnly: false }
) => {
return betterAuth({
logger: {
disabled: optionsOnly,
},
trustedOrigins: [siteUrl, "your-scheme://"],
database: authComponent.adapter(ctx),
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
},
plugins: [
expo(),
convex(),
crossDomain({ siteUrl }),
],
});
};Enable CORS
Add { cors: true } to the Better Auth route handlers:
import { httpRouter } from "convex/server";
import { authComponent, createAuth } from "./auth";
const http = httpRouter();
// CORS handling is required for client side frameworks
authComponent.registerRoutes(http, createAuth, { cors: true });
export default http;Update client instance
Finally, add the cross-domain client plugin to the Better Auth client instance.
Note that the expoClient and crossDomainClient plugins cannot both be
included in the client instance at the same time.
import { expoClient } from "@better-auth/expo/client";
import {
convexClient,
crossDomainClient,
} from "@convex-dev/better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
import Constants from "expo-constants";
import * as SecureStore from "expo-secure-store";
import { Platform } from "react-native";
export const authClient = createAuthClient({
baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
plugins: [
convexClient(),
...(Platform.OS === "web"
? [crossDomainClient()]
: [
expoClient({
scheme: Constants.expoConfig?.scheme as string,
storagePrefix: Constants.expoConfig?.scheme as string,
storage: SecureStore,
}),
]),
],
});