diff --git a/demo/nextjs/app/admin/page.tsx b/demo/nextjs/app/admin/page.tsx
index d8b645df..6365a543 100644
--- a/demo/nextjs/app/admin/page.tsx
+++ b/demo/nextjs/app/admin/page.tsx
@@ -76,16 +76,21 @@ export default function AdminDashboard() {
const { data: users, isLoading: isUsersLoading } = useQuery({
queryKey: ["users"],
- queryFn: () =>
- client.admin
- .listUsers({
+ queryFn: async () => {
+ const data = await client.admin.listUsers(
+ {
query: {
limit: 10,
sortBy: "createdAt",
sortDirection: "desc",
},
- })
- .then((res) => res.data?.users ?? []),
+ },
+ {
+ throw: true,
+ },
+ );
+ return data?.users || [];
+ },
});
const handleCreateUser = async (e: React.FormEvent) => {
diff --git a/demo/nextjs/app/dashboard/user-card.tsx b/demo/nextjs/app/dashboard/user-card.tsx
index 2c3180a9..b81e0b5a 100644
--- a/demo/nextjs/app/dashboard/user-card.tsx
+++ b/demo/nextjs/app/dashboard/user-card.tsx
@@ -42,10 +42,9 @@ import {
} from "lucide-react";
import Image from "next/image";
import { useRouter } from "next/navigation";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import { toast } from "sonner";
import { UAParser } from "ua-parser-js";
-import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
Table,
TableBody,
@@ -63,14 +62,7 @@ export default function UserCard(props: {
}) {
const router = useRouter();
const { data, isPending, error } = useSession();
- const [ua, setUa] = useState
();
-
const session = data || props.session;
-
- useEffect(() => {
- setUa(new UAParser(session?.session.userAgent));
- }, [session?.session.userAgent]);
-
const [isTerminating, setIsTerminating] = useState();
const [isPendingTwoFa, setIsPendingTwoFa] = useState(false);
const [twoFaPassword, setTwoFaPassword] = useState("");
@@ -668,7 +660,6 @@ function EditUserDialog(props: { session: Session | null }) {
function AddPasskey() {
const [isOpen, setIsOpen] = useState(false);
const [passkeyName, setPasskeyName] = useState("");
- const queryClient = useQueryClient();
const [isLoading, setIsLoading] = useState(false);
const handleAddPasskey = async () => {
diff --git a/demo/nextjs/components/account-swtich.tsx b/demo/nextjs/components/account-swtich.tsx
index 9044d9ee..98991f92 100644
--- a/demo/nextjs/components/account-swtich.tsx
+++ b/demo/nextjs/components/account-swtich.tsx
@@ -26,25 +26,8 @@ export default function AccountSwitcher({
}: {
sessions: Session[];
}) {
- const { data: users } = useQuery({
- queryKey: ["users"],
- queryFn: async () => {
- return;
- },
- });
const { data: currentUser } = useSession();
const [open, setOpen] = useState(false);
-
- const handleUserSelect = (user: Session) => {
- // setCurrentUser(user);
- setOpen(false);
- };
-
- const handleAddAccount = () => {
- // Implement add account logic here
- console.log("Add account clicked");
- setOpen(false);
- };
const router = useRouter();
return (
diff --git a/demo/nextjs/components/logo.tsx b/demo/nextjs/components/logo.tsx
index 21d2afc8..319fc6b2 100644
--- a/demo/nextjs/components/logo.tsx
+++ b/demo/nextjs/components/logo.tsx
@@ -11,8 +11,8 @@ export const Logo = (props: SVGProps) => {
xmlns="http://www.w3.org/2000/svg"
>
diff --git a/demo/nextjs/lib/auth-client.ts b/demo/nextjs/lib/auth-client.ts
index bc881960..69b7a87c 100644
--- a/demo/nextjs/lib/auth-client.ts
+++ b/demo/nextjs/lib/auth-client.ts
@@ -5,6 +5,7 @@ import {
twoFactorClient,
adminClient,
multiSessionClient,
+ oneTapClient,
} from "better-auth/client/plugins";
import { toast } from "sonner";
@@ -18,6 +19,9 @@ export const client = createAuthClient({
passkeyClient(),
adminClient(),
multiSessionClient(),
+ oneTapClient({
+ clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!,
+ }),
],
fetchOptions: {
onError(e) {
diff --git a/demo/nextjs/lib/auth.ts b/demo/nextjs/lib/auth.ts
index ebbcbda0..9c8c3a72 100644
--- a/demo/nextjs/lib/auth.ts
+++ b/demo/nextjs/lib/auth.ts
@@ -6,6 +6,7 @@ import {
organization,
passkey,
twoFactor,
+ oneTap,
} from "better-auth/plugins";
import { reactInvitationEmail } from "./email/invitation";
import { LibsqlDialect } from "@libsql/kysely-libsql";
@@ -120,5 +121,6 @@ export const auth = betterAuth({
bearer(),
admin(),
multiSession(),
+ oneTap(),
],
});
diff --git a/docs/components/sidebar-content.tsx b/docs/components/sidebar-content.tsx
index d001d8a1..3de467e7 100644
--- a/docs/components/sidebar-content.tsx
+++ b/docs/components/sidebar-content.tsx
@@ -759,6 +759,26 @@ export const contents: Content[] = [
),
},
+ {
+ title: "One Tap",
+ href: "/docs/plugins/one-tap",
+ icon: () => (
+
+ ),
+ },
+
{
title: "Authorization",
group: true,
diff --git a/docs/content/docs/plugins/one-tap.mdx b/docs/content/docs/plugins/one-tap.mdx
new file mode 100644
index 00000000..52b261df
--- /dev/null
+++ b/docs/content/docs/plugins/one-tap.mdx
@@ -0,0 +1,89 @@
+---
+title: One Tap
+description: One Tap plugin for BetterAuth
+---
+
+The One Tap plugin allows users to login with a single tap using Google's One Tap API.
+
+## Installation
+
+
+
+### Add the server Plugin
+
+Add the One Tap plugin to your auth config.
+
+```ts title="auth.ts"
+import { betterAuth } from "better-auth";
+import { oneTap } from "better-auth/plugins";
+
+export const auth = betterAuth({
+ plugins: [ // [!code highlight]
+ oneTap(), // [!code highlight]
+ ] // [!code highlight]
+})
+```
+
+
+
+ ### Add the client Plugin
+
+ Add the client plugin and Specify where the user should be redirected if they need to verify 2nd factor
+
+ ```ts title="client.ts"
+ import { createAuthClient } from "better-auth/client"
+ import { oneTapClient } from "better-auth/client/plugins"
+
+ const client = createAuthClient({
+ plugins: [
+ oneTapClient({
+ clientId: "YOUR_CLIENT_ID"
+ })
+ ]
+ })
+ ```
+
+
+
+## Usage
+
+To make the one tap pop up appear, you can call the `oneTap` method.
+
+```ts
+await authClient.oneTap()
+```
+
+By default, the plugin will automatically redirect the user to "/" after the user has successfully logged in. It does a hard redirect, so the page will be reloaded. If you want to
+avoid this, you can pass `fetchOptions` to the `oneTap` method.
+
+```tsx
+authClient.oneTap({
+ fetchOptions: {
+ onSuccess: () => {
+ router.push("/dashboard")
+ }
+ }
+})
+```
+
+If you want it to hard redirect to a different page, you can pass the `callbackURL` option to the `oneTap` method.
+
+```tsx
+authClient.oneTap({
+ callbackURL: "/dashboard"
+})
+```
+
+## Client Options
+
+**clientId**: The client ID of your Google One Tap API
+
+**autoSelect**: Automatically select the first account in the list. Default is `false`
+
+**context**: The context in which the One Tap API should be used. Default is `signin`
+
+**cancelOnTapOutside**: Cancel the One Tap popup when the user taps outside of the popup. Default is `true`.
+
+## Server Options
+
+**disableSignUp**: Disable the sign up option. Default is `false`. If set to `true`, the user will only be able to sign in with an existing account.
\ No newline at end of file
diff --git a/packages/better-auth/src/api/routes/session.ts b/packages/better-auth/src/api/routes/session.ts
index 55df8581..030a4888 100644
--- a/packages/better-auth/src/api/routes/session.ts
+++ b/packages/better-auth/src/api/routes/session.ts
@@ -67,7 +67,6 @@ export const getSession =