← Back to list

A Complete Guide to Google Auth & One-Tap in Next.js Using Better Auth + MongoDB

Learn how to integrate Better Auth with Google Login and One Tap to create a smooth, modern authentication flow in your Next.js…

Amir · 2025-12-03 23:05 · 0 claps · 3.1 min read
#nextjs #nodejs #better-auth #code
Open on Medium ↗
Wiki topics: 🌐 · Web Development

A Complete Guide to Google Auth & One-Tap in Next.js Using Better Auth + MongoDB

Learn how to integrate Better Auth with Google Login and One Tap to create a smooth, modern authentication flow in your Next.js application.

Lets get straight to the point.

1.Install the better auth package using Bun , Npm or what ever you want.

bun add better-auth

2.Create an auth object

// src/lib/auth.js

import mongoose from "mongoose";
import connectDB from "@/server/db";

import { betterAuth } from "better-auth";
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import { oneTap } from "better-auth/plugins";

let dbInstance;
let clientInstance;

// Lazily initialize the MongoDB connection (prevents multiple connections)
const getMongoDBInstances = async () => {
  if (!dbInstance || !clientInstance) {
    await connectDB(); // ensures mongoose connection is created
    clientInstance = mongoose.connection.getClient(); // get raw Mongo client
    dbInstance = clientInstance.db(); // get DB instance
  }
  return { db: dbInstance, client: clientInstance };
};

const { db, client } = await getMongoDBInstances();

export const auth = betterAuth({
  // MongoDB adapter — handles user, session, and account storage
  database: mongodbAdapter(db, {
    client,
    collectionNames: {
      user: "users",
      session: "sessions",
      account: "accounts",
    },
  }),

  // Enable email + password login
  emailAndPassword: {
    enabled: true, // optional, can remove if only social auth is used
  },

  // Social provider: Google OAuth
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,

      // Normalize Google profile structure → Better Auth user fields
      async profile(profile) {
        return {
          id: profile.id,
          email: profile.email,
          name: profile.name,
          image: profile.picture,
        };
      },
    },
  },

  // Session handling (7 days expiry)
  session: {
    expiresIn: 60 * 60 * 24 * 7, // refresh window
    updateAge: 60 * 60 * 24, // extend session every 24h
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // tiny optimization for fewer DB reads
    },
  },

  // How user fields map to your MongoDB schema
  user: {
    fields: {
      email: "email",
      name: "fullName", // maps to your schema field
      image: "image",
    },
    modelName: "users", // points to the "users" collection
  },

  // Allowed frontend origins (required for One Tap)
  trustedOrigins: [
    process.env.NEXT_PUBLIC_BASE_URL,
    "http://localhost",
  ],

  // Public base URL of your app
  baseURL: process.env.NEXT_PUBLIC_BASE_URL,

  // Secret key for signing sessions/JWTs
  secret: process.env.BETTER_AUTH_SECRET || process.env.JWT_SECRET,

  appName: "YOUR APP NAME",

  // Enable Google One Tap
  plugins: [oneTap()],
});

3.Now Configure the API route

// src/app/api/auth/[...all]/route.js

import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { GET, POST } = toNextJsHandler(auth);

4.Connect DB function + model design

// Simple function to connect to the databasae

import mongoose from "mongoose";

const connectDB = async () => {
  if (mongoose.connection.readyState >= 1) return;

  try {
    await mongoose.connect(process.env.MONGODB_URI);
    console.log("Connected to DB.");
  } catch (error) {
    console.error("Error connecting to DB:", error);
  }
};

export default connectDB;
// User model 

import mongoose from "mongoose";

const schema = new mongoose.Schema(
  {
    fullName: {
      type: String,
      required: true,
      trim: true,
    },
    email: {
      type: String,
      unique: true,
      required: true,
      lowercase: true,
      trim: true,
    },
    password: {
      type: String,
      default: "",
    },
    image: {
      type: String,
    },
    googleId: {
      type: String,
      unique: true,
      sparse: true,
    },
  },
  {
    timestamps: true,
  }
);

export default mongoose.models.User || mongoose.model("User", schema);

5. Create Auth client

// src/lib/auth-client.js

import { createAuthClient } from "better-auth/react";
import { oneTapClient } from "better-auth/client/plugins";

const GOOGLE_CLIENT_ID = "YOUR_GOOGLE_CLIENT_ID";

export const authClient = createAuthClient({
  baseURL: process.env.NEXT_PUBLIC_BASE_URL // your website url,
  plugins: [
    oneTapClient({
      clientId: GOOGLE_CLIENT_ID,
      autoSelect: true,
      cancelOnTapOutside: false,
      context: "signin",
      uxMode: "popup",
      promptOptions: {
        baseDelay: 3000,
        maxAttempts: 5,
      },
    }),
  ],
});

export const {
  signIn,
  signUp,
  signOut,
  useSession,
  forgetPassword,
  resetPassword,
  verifyEmail,
  requestPasswordReset,
} = authClient;

6. Use auth client to sign in or sign up users

await signIn.email({
        email: "EMAIL@gmail.com",
        password: "PASSWORD",
        callbackURL: "/dashboard" , // after login
});
await signUp.email({
        email: "EMAIL@gmail.com",
        password: "PASSWORD",
        callbackURL: "/dashboard" , // after login
});
// to login with google use 

await signIn.social({
        provider: "google",
        callbackURL: "/dashboard" ,
});

MAKE SURE YOU HAVE THESE IN YOUR .env FILE

BETTER_AUTH_SECRET=mysecret -> Use some strong secret key
BETTER_AUTH_URL=http://localhost:3000 -> Your Website url

IMPLEMENT THE ONE TAP POPUP

"use client";

import { authClient, useSession } from "@/lib/auth-client";
import { useEffect, useRef } from "react";

const OneTap = () => {
  const oneTapInitialized = useRef(false);

  const { data, isPending } = useSession();

  useEffect(() => {
    // do not show the popup if the user is already logged in
    if (data?.user || isPending || oneTapInitialized.current) return;

    const triggerOneTap = async () => {
      oneTapInitialized.current = true;

      try {
        await authClient.oneTap({
          fetchOptions: {
            onSuccess: (context) => {
              if (context?.data?.user) {
                window.location.reload();
              }
            },
            onError: (context) => {
              console.error("OneTap authentication failed:", context.error);
              oneTapInitialized.current = false;
            },
          },
        });
      } catch (error) {
        console.error("OneTap initialization failed:", error);
        oneTapInitialized.current = false;
      }
    };

    triggerOneTap();
  }, [data?.user, isPending]);

  return null;
};

export default OneTap;

Now use this OneTap component in your root layout and Thats it.


메타데이터
post_id
ebe2de45c691
slug
a-complete-guide-to-google-auth-one-tap-in-next-js-using-better-auth-mongodb-ebe2de45c691
url
https://medium.com/@amirjld/a-complete-guide-to-google-auth-one-tap-in-next-js-using-better-auth-mongodb-ebe2de45c691
canonical_url
https://medium.com/@amirjld/a-complete-guide-to-google-auth-one-tap-in-next-js-using-better-auth-mongodb-ebe2de45c691
author_url
https://medium.com/@amirjld
status
ok
fetched_at
2026-06-12 18:14:10