diff --git a/demo/nextjs/app/(auth)/sign-in/page.tsx b/demo/nextjs/app/(auth)/sign-in/page.tsx index 278e24f8..b2779a6f 100644 --- a/demo/nextjs/app/(auth)/sign-in/page.tsx +++ b/demo/nextjs/app/(auth)/sign-in/page.tsx @@ -3,8 +3,14 @@ import SignIn from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; import { Tabs } from "@/components/ui/tabs2"; +import { client } from "@/lib/auth-client"; +import { useEffect } from "react"; export default function Page() { + useEffect(() => { + client.oneTap(); + }, []); + return (
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 =