To Reproduce To reproduce it, you simply have to create a Honojs + nextjs project and have to deploy it Current vs. Expected behavior import { emailOTPClient } from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_AUTH_BACKEND_URL, // Dynamic base URL based on environment plugins: [emailOTPClient()], }); I first tried retrieving the session using authClient.api.getSession, but it always returned null. After researching, I found that I should use a server-side approach and implemented the method below. However, even with this approach, I am still getting null as the session. "use server"; import { cookies } from "next/headers"; import type { UserSession } from "@/types/auth"; export const getServerSession = async (): Promise => { try { const cookieHeader = (await cookies()).toString(); console.log("Cookie header:", cookieHeader); const res = await fetch( `${process.env.NEXT_PUBLIC_AUTH_BACKEND_URL}/api/auth/get-session`, { headers: { Cookie: cookieHeader }, credentials: "include", } ); if (!res.ok) return null; const data = await res.json(); console.log("Session retrieved successfully:", data); return data as UserSession; } catch (err) { const error = err as Error; console.error("Exception in getServerSession:", { message: error.message, stack: error.stack, cause: error.cause, }); return null; } }; What version of Better Auth are you using? ^1.4.10 System info Nextjs (Frontend) { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26200", "cpuCount": 12, "cpuModel": "AMD Ryzen 5 7600 6-Core Processor ", "totalMemory": "15.19 GB", "freeMemory": "1.76 GB" }, "node": { "version": "v22.17.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.7.0" }, "frameworks": [ { "name": "next", "version": "^16.1.1" }, { "name": "react", "version": "^19.2.3" } ], "databases": null, "betterAuth": { "version": "^1.4.9", "config": null } } Honojs(Backend) { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26200", "cpuCount": 12, "cpuModel": "AMD Ryzen 5 7600 6-Core Processor ", "totalMemory": "15.19 GB", "freeMemory": "1.76 GB" }, "node": { "version": "v22.17.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": null, "databases": [ { "name": "@prisma/client", "version": "6.18.0" } ], "betterAuth": { "version": "Unknown", "config": null } } Which area(s) are affected? (Select all that apply) Client Auth config (if applicable) /* eslint-disable @typescript-eslint/no-explicit-any */ import { SendOTP } from '@backend/aws'; import { prisma } from '@backend/db'; import { loadEnv } from '@backend/runtime'; import type { BetterAuthOptions } from 'better-auth'; import { betterAuth } from 'better-auth'; import { prismaAdapter } from 'better-auth/adapters/prisma'; import { APIError, createAuthMiddleware } from 'better-auth/api'; import { openAPI, organization, jwt, admin, customSession, emailOTP, username, } from 'better-auth/plugins'; import { UserProfileInputSchema } from './schema'; loadEnv(); /** * Alias for the Better Auth configuration interface inferred from `betterAuth`. */ // type AuthInterface = ReturnType; /** * Better Auth server configuration for the auth microservice. * * Environment variables used: * - BETTER_AUTH_URL: Public base URL of the auth service (e.g., https://auth.example.com) * - GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET: Google OAuth credentials * - GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET: GitHub OAuth credentials * - BETTER_AUTH_TRUSTED_ORIGIN: Allowed origin for cross-origin requests to auth endpoints */ const options = { /** * Public base URL that Better Auth uses to generate callback URLs. */ baseURL: process.env.BETTER_AUTH_URL, /** * Database adapter configuration. Uses Prisma connected to a MongoDB provider. */ database: prismaAdapter(prisma, { provider: 'mongodb', }), /** * OAuth social provider configuration. */ socialProviders: { /** Google OAuth provider settings. */ google: { clientId: process.env.GOOGLE_CLIENT_ID as string, clientSecret: process.env.GOOGLE_CLIENT_SECRET, display: 'popup', }, /** GitHub OAuth provider settings. */ github: { clientId: process.env.GITHUB_CLIENT_ID as string, clientSecret: process.env.GITHUB_CLIENT_SECRET, }, }, /** * Disable username/password flow; using federated login and OTP instead. */ emailAndPassword: { enabled: false, // requireEmailVerification:true, }, plugins: [ openAPI({ theme: 'bluePlanet', }), username({ minUsernameLength: 4, maxUsernameLength: 12, }), emailOTP({ /** * Send a verification OTP to the user for the specified action type. * * @param params.email - Recipient email address * @param params.otp - One-time password to be delivered * @param params.type - OTP flow type (e.g., "sign-in", "email-verification") */ sendVerificationOTP: async ({ email, otp, type }) => { if (type === 'sign-in') { console.log(`sending otp to ${email} : ${otp}`); // try { // const response = await SendOTP(email,otp); // if(!response.success){ // throw new APIError('BAD_REQUEST',{ message: response.message , code: response.code }); // } // } catch (error:any) { // throw new APIError('BAD_REQUEST', { // message: error.message || "Failed to send email.", // code: error.body.code || error.code || "EMAIL_SERVICE_ERROR" // }) // } } // if(type === 'email-verification'){ // console.log(`sending verification otp to ${email} : ${otp}`); // } }, // sendVerificationOnSignUp:true, // overrideDefaultEmailVerification:true }), admin({ /** * Role-based admin configuration. * Users default to the "user" role; "admin" and "superAdmin" are elevated. */ adminUserIds: ['BH4XFH0reiAv05INPlm62DKJkRRpuZG9'], adminRoles: ['admin', 'superAdmin'], defaultRole: 'user', }), jwt({ jwt: { /** * Define additional fields to embed into the JWT payload for the user. * * @param params.user - The authenticated user record * @returns Minimal payload used downstream for authorization decisions */ definePayload: ({ user }) => { return { id: user.id, name: user.name, role: user.role, }; }, }, }), organization({ /** Enable team/organization features for multi-tenant scenarios. */ teams: { enabled: true }, }), ], /** * Database hooks for reacting to lifecycle events. * Here we create a companion `userProfile` when a new user is created. */ databaseHooks: { user: { create: { async before(user) { const username = user.email.split('@')[0] + Date.now().toString() + Math.floor(Math.random() * 1000).toString(); const name = user.name === '' ? user.email.split('@')[0] : user.name; const image = user.image ? user.image : `https://api.dicebear.com/7.x/initials/svg?seed=${name}`; return { data: { ...user, name, username, image, }, }; }, /** * Runs after a user record is created. * * @param user - The newly created user entity * @param context - Hook context from Better Auth */ async after(user) { // *Format 2025-11-14 (IST) not const today = new Date(new Date().setHours(24, 0, 0, 0)).toISOString().split('T')[0]; await prisma.user.update({ where: { id: user.id, }, data: { userProfile: { create: {}, }, editorSettings: { create: {}, }, userOverallProgress: { create: {}, }, userGamificationProfile: { create: { coinBalance: 100, // TODO: give coins to user when first sign-up }, }, userStreak: { create: { streak: 1, longestStreak: 1, streakDates: { set: [today] }, }, }, }, }); }, }, update: { async before(user) { const image = user.image ? user.image : `https://api.dicebear.com/7.x/initials/svg?seed=${user.name}`; return { data: { ...user, image, }, }; }, }, }, }, hooks: { after: createAuthMiddleware(async (ctx) => { if (ctx.path === '/update-user') { if (ctx?.body) { try { const userId = ctx.context.newSession?.user.id; const data = UserProfileInputSchema.parse(ctx.body); await prisma.userProfile.update({ where: { userId, }, data: data, }); } catch (error: any) { if (error.name === 'ZodError') { const zodError = JSON.parse(error); console.log(zodError); throw new APIError('BAD_REQUEST', { message: `${zodError[0].path[0]} : ${zodError[0].message}`, code: zodError[0].code, }); } else { throw new APIError('INTERNAL_SERVER_ERROR', { message: 'something went wrong.', code: 'internal_server_error', cause: error, }); } } } } }), }, session: { cookieCache: { /** * Cache sessions in a signed cookie for faster reads. */ enabled: false, // Disable cookie cache to prevent UI mismatch issues maxAge: 10 * 60, // Cache duration in seconds }, }, user: { additionalFields: { role: { type: 'string' }, }, changeEmail: { enabled: false, // async sendChangeEmailVerification({ user, newEmail ,url, token }, request){ // console.log(`change email url for ${newEmail} : ${url}`); // } }, }, /** * Allowed cross-origin request origin(s) for Better Auth endpoints. */ trustedOrigins: [process.env.BETTER_AUTH_TRUSTED_ORIGIN as string, 'http://localhost:3000'], secret: process.env.BETTER_AUTH_SECRET, advanced: { defaultCookieAttributes: { sameSite: 'none', secure: true, partitioned: true, }, cookies: { state: { attributes: { sameSite: 'none', secure: true, }, }, }, }, } satisfies BetterAuthOptions; const auth = betterAuth({ ...options, plugins: [ ...(options.plugins ?? []), /** * Augment the session payload with additional user data hydrated from the database. * Fetches the user's profile and returns it alongside the standard user object. * * @param params.session - The computed session object * @param params.user - The authenticated user * @returns The updated session and user object including `profile` */ customSession(async ({ user, session }) => { // now both user and session will infer the fields added by plugins and your custom fields const profile = await prisma.user.findUnique({ where: { id: user.id, // user.role should now be available }, include: { userProfile: true, userStreak: true, userGamificationProfile: true, subscription: true, bookmarks: true, }, }); let isActiveSubscription = false; const subscription = profile?.subscription; if (!subscription) { isActiveSubscription = false; } else { const current_time = Date.now() / 1000; if (subscription.accessUntil === null) { isActiveSubscription = false; } else { isActiveSubscription = subscription.accessUntil > current_time ? true : false; } // switch (subscription.status) { // case 'ACTIVE': // isActiveSubscription = true; // break; // case 'CANCELLED': // case 'COMPLETED': // if (subscription.accessUntil === null) { // isActiveSubscription = false; // } else { // isActiveSubscription = subscription.accessUntil > current_time ? true : false; // } // break; // case 'CREATED': // case 'HALTED': // case 'PAUSED': // case 'PENDING': // isActiveSubscription = false; // break; // default: // isActiveSubscription = false; // break; // } } return { user: { ...user, ...profile, isActiveSubscription, }, session, }; }, options), // pass options here ], }); export { auth }; Additional context Everything is working fine in local environment but not in production environment.