SyntaxError: "[object Object]" is not valid JSON on Google oauth callback

I'm trying to setup google oauth login and I'm receiving a strange error when the provider calls the callback URL. What I get in my server logs (I've added some logging in the library code):
state o2c5WeQomqhNbjem-NPwSEbeDk3Eg_J6
data {
identifier: 'o2c5WeQomqhNbjem-NPwSEbeDk3Eg_J6',
value: {
callbackURL: 'http://localhost:3000',
codeVerifier: 'jLuPd26AbFA5HtvW9mgzMZxNN28W8sK7tciEeHdQjmN_Ifqx6oUziJnZfzLUs73gE9aDq5zIOnXziatPevZ5c18qsvHeSTa25PombgxlxuNz9J30IcrLv97SL3SeZT-O',
expiresAt: 1748903080836,
requestSignUp: true
},
expiresAt: 2025-06-02T22:24:40.836Z,
createdAt: 2025-06-02T22:14:40.836Z,
updatedAt: 2025-06-02T22:14:40.836Z,
id: '6m6lvQhyPJ4SctcRINSSFoK7ijIcdD3Y'
}
# SERVER_ERROR: SyntaxError: "[object Object]" is not valid JSON
at JSON.parse (<anonymous>)
at parseState (file:///Users/nick/Workspace/portal/cms-frontend/node_modules/.pnpm/better-auth@1.2.8/node_modules/better-auth/dist/shared/better-auth.dn8_oqOu.mjs:83:17)
...
state o2c5WeQomqhNbjem-NPwSEbeDk3Eg_J6
data {
identifier: 'o2c5WeQomqhNbjem-NPwSEbeDk3Eg_J6',
value: {
callbackURL: 'http://localhost:3000',
codeVerifier: 'jLuPd26AbFA5HtvW9mgzMZxNN28W8sK7tciEeHdQjmN_Ifqx6oUziJnZfzLUs73gE9aDq5zIOnXziatPevZ5c18qsvHeSTa25PombgxlxuNz9J30IcrLv97SL3SeZT-O',
expiresAt: 1748903080836,
requestSignUp: true
},
expiresAt: 2025-06-02T22:24:40.836Z,
createdAt: 2025-06-02T22:14:40.836Z,
updatedAt: 2025-06-02T22:14:40.836Z,
id: '6m6lvQhyPJ4SctcRINSSFoK7ijIcdD3Y'
}
# SERVER_ERROR: SyntaxError: "[object Object]" is not valid JSON
at JSON.parse (<anonymous>)
at parseState (file:///Users/nick/Workspace/portal/cms-frontend/node_modules/.pnpm/better-auth@1.2.8/node_modules/better-auth/dist/shared/better-auth.dn8_oqOu.mjs:83:17)
...
the library code at the top of the stack is this function:
async function parseState(c) {
const state = c.query.state || c.body.state;
const data = await c.context.internalAdapter.findVerificationValue(state);
console.log("state", state);
console.log("data", data);
if (!data) {
c.context.logger.error("State Mismatch. Verification not found", {
state
});
throw c.redirect(
`${c.context.baseURL}/error?error=please_restart_the_process`
);
}
const parsedData = z.object({
....
}).parse(JSON.parse(data.value));
...
}
async function parseState(c) {
const state = c.query.state || c.body.state;
const data = await c.context.internalAdapter.findVerificationValue(state);
console.log("state", state);
console.log("data", data);
if (!data) {
c.context.logger.error("State Mismatch. Verification not found", {
state
});
throw c.redirect(
`${c.context.baseURL}/error?error=please_restart_the_process`
);
}
const parsedData = z.object({
....
}).parse(JSON.parse(data.value));
...
}
when I remove the JSON.parse call in that function I am able to complete the oauth sign in sucessfully. Info about my environment: better-auth 1.2.8 (had the same issue on 1.2.7 and tried upgrading) hono 4.7.8 Is this a bug in the library or in my setup? Confused how the library expects a json string there but its already an object. Thanks in advance!
Solution:
Sorry, this was a problem on my side. I had the kysely plugin ParseJSONResultsPlugin enabled on the adapter
Jump to solution
5 Replies
Nick
NickOP5mo ago
auth config:
import { betterAuth } from "better-auth";
import { captcha, emailOTP } from "better-auth/plugins";

import { kysely } from "@lib/database/kysely";

import { sendSignInOTPEmail, sendVerificationEmail } from "./authn/emails";

export const auth = betterAuth({
trustedOrigins: ["*"],
database: {
db: kysely,
type: "postgres",
casing: "snake",
},
socialProviders: {
google: {
prompt: "select_account",
clientId: process.env.GOOGLE_OAUTH_CLIENT_ID!,
clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!,
},
},
emailAndPassword: {
enabled: true,
},
emailVerification: {
sendVerificationEmail: async ({ user, url }, _request) => {
await sendVerificationEmail(user.email, url);
},
sendOnSignUp: true,
},
plugins: [
emailOTP({
async sendVerificationOTP({ email, otp }, _request) {
await sendSignInOTPEmail(email, otp);
},
}),
captcha({
provider: "cloudflare-turnstile",
secretKey: process.env.TURNSTILE_SECRET_KEY!,
}),
],
advanced: {
cookiePrefix: "portal-authn",
},
user: {
modelName: "authn_users",
},
session: {
modelName: "authn_sessions",
},
account: {
modelName: "authn_accounts",
},
verification: {
modelName: "authn_verifications",
},
});
import { betterAuth } from "better-auth";
import { captcha, emailOTP } from "better-auth/plugins";

import { kysely } from "@lib/database/kysely";

import { sendSignInOTPEmail, sendVerificationEmail } from "./authn/emails";

export const auth = betterAuth({
trustedOrigins: ["*"],
database: {
db: kysely,
type: "postgres",
casing: "snake",
},
socialProviders: {
google: {
prompt: "select_account",
clientId: process.env.GOOGLE_OAUTH_CLIENT_ID!,
clientSecret: process.env.GOOGLE_OAUTH_CLIENT_SECRET!,
},
},
emailAndPassword: {
enabled: true,
},
emailVerification: {
sendVerificationEmail: async ({ user, url }, _request) => {
await sendVerificationEmail(user.email, url);
},
sendOnSignUp: true,
},
plugins: [
emailOTP({
async sendVerificationOTP({ email, otp }, _request) {
await sendSignInOTPEmail(email, otp);
},
}),
captcha({
provider: "cloudflare-turnstile",
secretKey: process.env.TURNSTILE_SECRET_KEY!,
}),
],
advanced: {
cookiePrefix: "portal-authn",
},
user: {
modelName: "authn_users",
},
session: {
modelName: "authn_sessions",
},
account: {
modelName: "authn_accounts",
},
verification: {
modelName: "authn_verifications",
},
});
the callsite:
const handleSignInWithGoogle = async () => {
try {
const response = await authClient.signIn.social({ provider: "google", requestSignUp: true, fetchOptions: { headers: { "x-captcha-response": formState.turnstileToken } } });
console.log(response);
} catch (error) {
console.error("Error during form submission:", error);
setAuthError(error instanceof Error ? error.message : "An unexpected error occurred");
}
};
const handleSignInWithGoogle = async () => {
try {
const response = await authClient.signIn.social({ provider: "google", requestSignUp: true, fetchOptions: { headers: { "x-captcha-response": formState.turnstileToken } } });
console.log(response);
} catch (error) {
console.error("Error during form submission:", error);
setAuthError(error instanceof Error ? error.message : "An unexpected error occurred");
}
};
Solution
Nick
Nick4mo ago
Sorry, this was a problem on my side. I had the kysely plugin ParseJSONResultsPlugin enabled on the adapter
andreasb
andreasb4mo ago
@Nick How did you end up dealing with this? Not having ParseJSONResultsPlugin will cause all sorts of issue with non-Better Auth queries, but leaving it in causes the "[object Object]" issue.
Nick
NickOP4mo ago
@andreasb My app didn't need ParseJSONResultsPlugin so I just removed it, I added it accidentally while migrating from supabase-js to kysely for a SSR app. Sorry that's not a great answer, maybe you could consider removing it and manually doing the JSON parsing yourself with a library like zod Another idea could be to create a different db adapter for better-auth than your primary app
andreasb
andreasb4mo ago
@Nick Thank you! Both very good suggestions, very much appreciated! I also see that ParseJSONResultsPlugin can be added on a per-query basis, but the dual db adapter approach must be tried, sounds a lot easier. For future reference, ended up with a variant of Nick's suggestion:
export const betterAuthDBClient = kyselyClient.withoutPlugins();
export const betterAuthDBClient = kyselyClient.withoutPlugins();
Had no idea withoutPlugins() was a thing, solved it immediately!

Did you find this page helpful?