Google oauth redirecting to /api/auth

Hi, I'm setting up Google oauth in my app but when I login through google I get redirected to /api/auth (which 404s) and I can't figure out why its redirecting here. These are my network requests when logging in:
POST /api/auth/sign-in/social 200 in 514ms
GET /api/auth/callback/google?state=blGZzt7ykTo8iY1RAmeXfAKb9Yf6h6-u&code=XXXXXXXXXXXXXXXXXXXXXXXX&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none 302 in 1235ms
GET /api/auth 404 in 228ms
POST /api/auth/sign-in/social 200 in 514ms
GET /api/auth/callback/google?state=blGZzt7ykTo8iY1RAmeXfAKb9Yf6h6-u&code=XXXXXXXXXXXXXXXXXXXXXXXX&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none 302 in 1235ms
GET /api/auth 404 in 228ms
My setup is:
// lib/auth.ts
import { betterAuth } from 'better-auth'
import { db } from './db'
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { nextCookies } from 'better-auth/next-js';

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg"
}),
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}
},
plugins: [nextCookies()]
})
// lib/auth.ts
import { betterAuth } from 'better-auth'
import { db } from './db'
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { nextCookies } from 'better-auth/next-js';

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg"
}),
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}
},
plugins: [nextCookies()]
})
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react"

export function getBaseURL() {
const baseURL =
process.env.SITE_URL || // Production
process.env.VERCEL_URL || // Preview (Vercel)
"http://localhost:3000"; // Development

return baseURL.includes("http") ? baseURL : `https://${baseURL}`;
}

export const authClient = createAuthClient({
baseURL: getBaseURL()
});
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react"

export function getBaseURL() {
const baseURL =
process.env.SITE_URL || // Production
process.env.VERCEL_URL || // Preview (Vercel)
"http://localhost:3000"; // Development

return baseURL.includes("http") ? baseURL : `https://${baseURL}`;
}

export const authClient = createAuthClient({
baseURL: getBaseURL()
});
And for my login onClick I'm doing
"use client";

import Link from "next/link";
import { Icons } from "@/components/icons";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { authClient } from "@/lib/auth-client";

export function LoginComponent({ className }: { className?: string }) {
return (
<Card
className={cn(
"w-full grid md:grid-cols-3 overflow-hidden place-items-center text-center",
className
)}
>
<video
src="https://assets.clip.studio/reddit_preview.webm"
autoPlay
muted
loop
playsInline
className="col-span-1 hidden md:block w-full h-full object-cover"
/>

<div className="md:col-span-2">
<CardHeader className="flex flex-col items-center">
<Avatar className="size-24">
<AvatarImage src="/logo.svg" />
<AvatarFallback>CS</AvatarFallback>
</Avatar>
<CardTitle className="text-2xl font-medium">
Get Started with Refract
</CardTitle>
<CardDescription>
Sign in or create an account to get started
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4">
<Button
variant="secondary"
className="w-full"
onClick={async () => {
await authClient.signIn.social({
provider: "google",
});
}}
>
<Icons.google className="mr-2 h-4 w-4 dark:invert" />
Login with Google
</Button>
</div>

<div className="text-[11px] text-center mt-6">
<span>By signing up, you agree to our</span>{" "}
<Link href="/terms" className="underline">
Terms
</Link>{" "}
<span>and</span>{" "}
<Link href="/privacy" className="underline">
Privacy Policy
</Link>
.
</div>
</CardContent>
</div>
</Card>
);
}
"use client";

import Link from "next/link";
import { Icons } from "@/components/icons";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { authClient } from "@/lib/auth-client";

export function LoginComponent({ className }: { className?: string }) {
return (
<Card
className={cn(
"w-full grid md:grid-cols-3 overflow-hidden place-items-center text-center",
className
)}
>
<video
src="https://assets.clip.studio/reddit_preview.webm"
autoPlay
muted
loop
playsInline
className="col-span-1 hidden md:block w-full h-full object-cover"
/>

<div className="md:col-span-2">
<CardHeader className="flex flex-col items-center">
<Avatar className="size-24">
<AvatarImage src="/logo.svg" />
<AvatarFallback>CS</AvatarFallback>
</Avatar>
<CardTitle className="text-2xl font-medium">
Get Started with Refract
</CardTitle>
<CardDescription>
Sign in or create an account to get started
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4">
<Button
variant="secondary"
className="w-full"
onClick={async () => {
await authClient.signIn.social({
provider: "google",
});
}}
>
<Icons.google className="mr-2 h-4 w-4 dark:invert" />
Login with Google
</Button>
</div>

<div className="text-[11px] text-center mt-6">
<span>By signing up, you agree to our</span>{" "}
<Link href="/terms" className="underline">
Terms
</Link>{" "}
<span>and</span>{" "}
<Link href="/privacy" className="underline">
Privacy Policy
</Link>
.
</div>
</CardContent>
</div>
</Card>
);
}
Solution:
Figured out auth flow was working but still dont know why it was redirecting there after, updated my callback url to an actual page and seems to work fine 🤷‍♀️
Jump to solution
9 Replies
Ho-kage
Ho-kage2mo ago
Did you configure the redirect URL in your google console?
No description
gursh
gurshOP2mo ago
Ye i have it under here
No description
Solution
gursh
gursh2mo ago
Figured out auth flow was working but still dont know why it was redirecting there after, updated my callback url to an actual page and seems to work fine 🤷‍♀️
Ho-kage
Ho-kage2mo ago
I was actually just about to say because I looked at my implementation Glad you got that sorted
AlmightyAsh
AlmightyAsh2w ago
Hey, I’m facing the same issue. It redirected me to the callback page with the authorization code. Am I supposed to call the access token from Google using the authorization code, or does BetterAuth handle that for me—like using the refresh token to get a new access token when it expires?
KiNFiSH
KiNFiSH2w ago
no better auth handles receiving the authorization code , exchanging the code for access tokens and also managing refresh tokens.
Omicrxn
Omicrxn2d ago
hey @KiNFiSH do you know if there is a way to exchange the code manually?
KiNFiSH
KiNFiSH2d ago
For getting oauth token ?
Omicrxn
Omicrxn2d ago
Like supabase supabase.auth.exchangeCodeForSession('oauth code')

Did you find this page helpful?