Help in resolving CORS issue with Better Auth

This is my first time using Better Auth and I am pairing it with Elysia and a Solid SPA. The Elysia backend is a dedicated backend-for-frontend that will serve the frontend in production so they will share the same origin url.

I am currently running into CORS issues when I attempt to sign in using the username plug-in. Below are my configurations:

For my frontend SPA, here is my vite.config.ts. The thing to note about it is the server.proxy configuration I have defined. When my frontend makes a request to http://localhost:3000/api/* it'll route it to port 8000 instead as that is the port where my ElysiaJS backend runs on in a sidecar shell.

import { defineConfig as defineViteConfig, mergeConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

const viteConfig = defineViteConfig({
  plugins: [solidPlugin()],
  server: {
    port: 3000,
    proxy: {
      // Proxy API requests to the backend port in development
      "/api": "http://localhost:8000",
    },
  },
  build: {
    target: "esnext",
  },
  resolve: {
    conditions: ["development", "browser"],
  },
});

// ...other stuff


My frontend Better Auth client config is the following:

import { usernameClient } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/solid";

export const authClient = createAuthClient({
  baseURL: "http://localhost:3000",
  plugins: [usernameClient()],
});


Moving on to the backend side of things, here is my Better Auth server config:

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { username } from "better-auth/plugins";

import { db } from "../drizzle/db";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  plugins: [username()],
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60 // Cache duration in seconds
    }
  },
});


And here is my Elysia setup for Better Auth:

import type { Session, User } from "better-auth";
import { type Context, Elysia } from "elysia";

import { auth } from "../lib/auth";

// user middleware (compute user and session and pass to routes)
const userMiddleware = async (request: Request) => {
  const session = (await auth.api.getSession({ headers: request.headers })) || {
    user: null,
    session: null,
  };

  return {
    user: session.user,
    session: session.session,
  };
};

// https://www.better-auth.com/docs/integrations/elysia
const betterAuthView = async (context: Context) => {
  const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"];

  // validate request method
  if (BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) {
    return auth.handler(context.request);
  } else {
    context.error(405);
  }
};

// user info view
// type User can be export from `typeof auth.$Infer.Session.user`
// type Session can be export from `typeof auth.$Infer.Session.session`
const userInfo = (user: User | null, session: Session | null) => {
  return {
    user: user,
    session: session,
  };
};

const api = new Elysia({ prefix: "/api" })
  .derive(({ request }) => userMiddleware(request))
  .all("/auth/*", betterAuthView)
  .get("/user", ({ user, session }) => userInfo(user, session))
  .get("/hello", () => ({ message: "Hello World API!" }));

export default api;


On the frontend, when I attempt to execute authClient.signIn.username(...) I get the following error:

ERROR [Better Auth]: Invalid origin: http://localhost:3000
INFO [Better Auth]: If it's a valid URL, please add http://localhost:3000 to trustedOrigins in your auth config
 Current list of trustedOrigins: http://localhost:3000/api/auth,http://localhost:8000


Clearly, http://localhost:3000 origin is not on that list which is why I'm getting hit with CORS, but I guess I'm wondering the question why since http://localhost:3000/api/auth is there. And what is the recommended way to resolve this? Hoping to avoid explicit origins..
Was this page helpful?