#BetterAuth MISSING_OR_NULL_ORIGIN or Invalid Base URL

17 messages · Page 1 of 1 (latest)

regal grotto
#

I migrated BetterAuth from 0.7 to 0.10 and now am unable to authenticate. After the upgrade I was getting a⁨MISSING_OR_NULL_ORIGIN⁩ error, and after adding ⁨baseUrl: siteUrl⁩ I am getting ⁨Invalid base URL: exp://192.168.68.66:8081. URL must include 'http://' or 'https://'⁩. My trustedOrigins is setill setup with all of the URLs including the ⁨exp://⁩ one, so what else could be going wrong.

glad umbraBOT
#

Hi @regal grotto. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

The knowledge sources don’t mention this exact error string, but they do cover the relevant pieces for Better Auth 0.10 + Expo + Convex. Based on those:

  1. baseURL must be an HTTP(S) Convex URL, not your Expo exp:// URL

    In 0.10, the Better Auth options expect baseURL (or baseUrl) to be an HTTP(S) URL – typically your Convex site URL (ends in .convex.site), not the Expo dev URL. [Expo guide]

    For Expo, the docs show the client configured like this:

    // src/lib/auth-client.ts
    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(),
      ],
    });
    

    Note that baseURL is EXPO_PUBLIC_CONVEX_SITE_URL (e.g. <https://adjective-animal-123.convex.site>), not exp://…. [Expo guide]

    So if you set baseUrl: siteUrl where siteUrl is exp://…, Better Auth will reject it with the “must include http/https” error.

  2. Where to use exp:// vs HTTP(S)

    For Expo native:

#
  • Better Auth server (convex/auth.ts)
    Use HTTP(S) URLs (Convex site URL) for anything like baseURL or baseUrl.
    For Expo, the docs don’t set baseURL on the server at all; they only configure plugins and trustedOrigins. [Expo guide]

    Example from the Expo guide:

    // convex/auth.ts
    import { createClient, type GenericCtx } from "@convex-dev/better-auth";
    import { convex } from "@convex-dev/better-auth/plugins";
    import { betterAuth } from "better-auth/minimal";
    import { expo } from '@better-auth/expo'
    import { components } from "./_generated/api";
    import { DataModel } from "./_generated/dataModel";
    import { query } from "./_generated/server";
    import authConfig from "./auth.config";
    
    export const authComponent = createClient<DataModel>(components.betterAuth);
    
    export const createAuth = (ctx: GenericCtx<DataModel>) => {
      return betterAuth({
        trustedOrigins: ["your-scheme://"],
        database: authComponent.adapter(ctx),
        emailAndPassword: {
          enabled: true,
          requireEmailVerification: false,
        },
        plugins: [
          expo(),
          convex({ authConfig }),
        ],
      })
    }
    

    Here, trustedOrigins includes the scheme ("your-scheme://"), which is where your exp://-style origin belongs. [Expo guide]

    • Expo plugin / client
      The exp:// / custom scheme is handled via the Expo plugin (expo() on the server, expoClient() on the client) and trustedOrigins, not via baseURL. [Expo guide]
  1. Why you saw MISSING_OR_NULL_ORIGIN
#

The docs don’t spell out that error, but in 0.10 the configuration became stricter and more explicit about URLs and origins. [Migration 0.10] If you were missing:

  • a proper baseURL on the client (Convex site URL), or
  • correct trustedOrigins including your scheme,

Better Auth could not validate the origin and would complain.

The fix pattern in the docs is:

  • Client: baseURL = EXPO_PUBLIC_CONVEX_SITE_URL (HTTPS Convex site URL). [Expo guide]
  • Server: trustedOrigins: ["your-scheme://"] (and, for Expo Web, also siteUrl + crossDomain plugin). [Expo Web support]
  1. What to change in your setup

    Based on the docs:

    • Remove baseUrl: siteUrl if siteUrl is exp://… from your Better Auth server config.

    • Ensure your client uses the Convex site URL:

      // .env for Expo
      EXPO_PUBLIC_CONVEX_SITE_URL=<https://your-project.convex.site>
      

      and in the client:

      baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
      
    • Keep trustedOrigins including your scheme (e.g. "your-scheme://" or whatever you use for deep linking). [Expo guide]

    • If you’re also using Expo Web, follow the Expo Web section: set SITE_URL to your web URL, add crossDomain({ siteUrl }) and crossDomainClient() for web only. [Expo Web support]

If you can share your current convex/auth.ts and client auth-client config, you can compare them line‑by‑line against the Expo guide linked above; your goal is to have:

#
  • Only HTTP(S) URLs in baseURL / baseUrl.
  • Your exp:// / custom scheme only in trustedOrigins and the Expo plugin configuration.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@regal grotto Kapa.ai is still learning and improving, please let me know how I did by reacting below.

regal grotto
#

@glad umbra even when setting it to the ⁨.convex.site⁩ value directly, I am still logging out an error like this: ⁨ LOG Sign in response: {"data": null, "error": {"code": "MISSING_OR_NULL_ORIGIN", "message": "Missing or null Origin", "status": 403, "statusText": ""}}

⁨```
export const createAuth = (ctx: GenericCtx<DataModel>) => {
return betterAuth({
baseURL: 'https://lovable-fox-123.convex.site',
session: {
expiresIn: 60 * 60 * 24 * 365, // 1 year
updateAge: 60 * 60 * 24 * 30, // 30 days
},
trustedOrigins: [
'my.app://',
'https://lovable-fox-123.convex.cloud',
'exp://192.168.68.66:8081',
'http://localhost:8081',
'exp+my-app://expo-development-client/?url=http%3A%2F%2F192.168.68.66%3A8081',
'https://appleid.apple.com',
],
database: authComponent.adapter(ctx),

emailAndPassword: {
  enabled: true,
  requireEmailVerification: false,
},

socialProviders: {
  apple: {
    appBundleIdentifier: appleClientId,
    clientId: appleClientId,
    clientSecret: appleClientSecret,
  },

  google: {
    clientId: googleClientId,
    clientSecret: googleClientSecret,
  },
},

plugins: [
  convex({
    authConfig,
    jwksRotateOnTokenGenerationError: true,
  }),
  expo(),
  emailOTP({
    async sendVerificationOTP({ email, otp, type }) {
      if (type === 'sign-in') {
        await sendOtpSignIn(requireMutationCtx(ctx), {
          to: email,
          code: otp,
        })
      }
      // TODO: support others for 2FA
    },
  }),
],

})
}


I'm not really sure why this error would still occur. I am running ⁨`bunx convex dev`⁩ as well and waiting for the changes to apply
glad umbraBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The knowledge sources don’t mention MISSING_OR_NULL_ORIGIN explicitly, but they do show the intended Expo + Better Auth 0.10 setup. Comparing your config to those, there are two likely issues:

  1. Server config: don’t set baseURL to the Convex site URL for Expo

    In the official Expo guide, the Convex-side Better Auth instance does not set baseURL at all. It only sets trustedOrigins, database, and plugins. [Expo guide]

    // convex/auth.ts (from docs)
    export const createAuth = (ctx: GenericCtx<DataModel>) => {
      return betterAuth({
        trustedOrigins: ["your-scheme://"],
        database: authComponent.adapter(ctx),
        emailAndPassword: {
          enabled: true,
          requireEmailVerification: false,
        },
        plugins: [
          expo(),
          convex({ authConfig }),
        ],
      })
    }
    

    Your config:

    return betterAuth({
      baseURL: '<https://lovable-fox-123.convex.site',>  // <-- extra
      trustedOrigins: [ ... ],
      ...
    })
    

    For Expo, the client is what should use the Convex site URL as baseURL, not the server instance. The server-side baseURL is not required in the Expo guide and may be interacting badly with origin checks.

    I’d try removing baseURL entirely from createAuth and matching the docs more closely.

  2. Client config: ensure baseURL is the .convex.site URL and CORS is enabled

    The Expo guide shows the client like this: [Expo guide; Expo Web support]

#
   // src/lib/auth-client.ts
   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, // <https://...convex.site>
     plugins: [
       expoClient({
         scheme: Constants.expoConfig?.scheme as string,
         storagePrefix: Constants.expoConfig?.scheme as string,
         storage: SecureStore,
       }),
       convexClient(),
     ],
   });

And the Convex HTTP routes:

// convex/http.ts
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;

Double‑check:

  • EXPO_PUBLIC_CONVEX_SITE_URL is set to <https://lovable-fox-123.convex.site> in your .env used by Expo. [Expo guide]
  • Your authClient uses that env var as baseURL.
  • authComponent.registerRoutes(http, createAuth, { cors: true }) is present. [Expo Web support]
  1. Trusted origins: keep only actual origins

    In the Expo guide, trustedOrigins is just the scheme (and, for web, the site URL): [Expo guide; Expo Web support]

    const siteUrl = process.env.SITE_URL!;
#

export const createAuthOptions = (ctx: GenericCtx<DataModel>) =>
({
trustedOrigins: [siteUrl, "your-scheme://"],
...
}) satisfies BetterAuthOptions;


Your list includes full URLs like `exp://192.168.68.66:8081` and `exp+my-app://expo-development-client/?url=...`. The docs don’t show using those; they just use the **scheme** (`"your-scheme://"`). I can’t say for sure from the docs that this is invalid, but to align with them I’d simplify to:

```ts
trustedOrigins: [
  'my.app://',
  '<https://lovable-fox-123.convex.site',> // if you also use Expo Web
],

and drop the exp://... and <http://localhost:8081> entries.


Concrete changes to try, based on docs:

  1. In convex/auth.ts:

    • Remove baseURL from betterAuth({ ... }).
    • Reduce trustedOrigins to:
    trustedOrigins: ['my.app://'],
    // or ['my.app://', process.env.SITE_URL!] if you also support Expo Web
    
    • Keep expo() and convex({ authConfig, jwksRotateOnTokenGenerationError: true }) plugins.
  2. In convex/http.ts:

    authComponent.registerRoutes(http, createAuth, { cors: true });
    
  3. In your Expo client:

    • Ensure:

      EXPO_PUBLIC_CONVEX_SITE_URL=<https://lovable-fox-123.convex.site>
      
    • And:

      baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
      

If, after matching the Expo guide exactly (minus your extra OTP/social config), you still see MISSING_OR_NULL_ORIGIN, then the knowledge sources don’t give more detail on that specific error, and you may need to share the exact client request (headers, URL) in Discord’s #better-auth channel for deeper debugging.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@regal grotto Kapa.ai is still learning and improving, please let me know how I did by reacting below.

regal grotto
#

@glad umbra My code seems to follow the betterauth expo guide, the only issue I am seeing is a typescript error in my auth-client.ts
⁨```
const _authClient = createAuthClient({
baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE!,
plugins: [
convexClient(),
expoClient({
scheme: 'my.app',
storagePrefix: 'my.app',
storage: SecureStore,
}),
emailOTPClient(),
],
})


The typescript error seems to be around the ⁨`expoClient()`⁩ plugin. The error is attached.
glad umbraBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

The knowledge sources don’t show the exact TypeScript error you’re seeing, but they do show the expected shape of auth-client.ts for Expo + Convex + Better Auth, which you can compare against.

From the Expo guide, the recommended client looks like this (note the order and set of plugins): [Expo guide]

// src/lib/auth-client.ts
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(),
  ],
});

A few things to double‑check against your code:

  1. Import path and type of expoClient

    It should come from @better-auth/expo/client:

    import { expoClient } from "@better-auth/expo/client";
    

    If it’s imported from anywhere else, TypeScript may not see it as a valid Better Auth client plugin.

  2. Environment variable name

    The docs use EXPO_PUBLIC_CONVEX_SITE_URL, not EXPO_PUBLIC_CONVEX_SITE:

    baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
    

    While this is a runtime concern, mismatched env names can sometimes surface as type issues if you’ve added stricter typings around process.env.

  3. Plugin list shape

#

The docs only show expoClient() and convexClient() in the Expo client. [Expo guide]

You have:

plugins: [
  convexClient(),
  expoClient({ ... }),
  emailOTPClient(),
],

The knowledge sources don’t mention emailOTPClient() at all, so I can’t confirm its correct type. If it’s not typed as a valid Better Auth client plugin, it could cause a type error that appears “around” expoClient. To align with the docs, try temporarily removing emailOTPClient() and see if the error disappears.

  1. Order of plugins

    The guide shows expoClient() first, then convexClient(). [Expo guide]

    While order shouldn’t normally cause a type error, you can match the docs exactly:

    const _authClient = createAuthClient({
      baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
      plugins: [
        expoClient({
          scheme: "my.app",
          storagePrefix: "my.app",
          storage: SecureStore,
        }),
        convexClient(),
      ],
    });
    

If, after:

  • importing expoClient from @better-auth/expo/client,
  • using EXPO_PUBLIC_CONVEX_SITE_URL,
  • and removing emailOTPClient(),

you still see the TypeScript error, the knowledge sources don’t provide more detail on the typing of expoClient, so I can’t say exactly why TS is complaining. In that case, please paste the exact TS error text; with that plus the docs snippet above, you can compare the expected plugin type signature against what TypeScript reports.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: