From d63ded8c6a423cdf54a560a35f8a47b99fb11b34 Mon Sep 17 00:00:00 2001 From: Thomas Ruoff Date: Tue, 3 Mar 2026 21:07:23 +0100 Subject: [PATCH] migrate auth from next-auth to better-auth with magic link support Replace next-auth with better-auth, adding magic link email login as the primary auth method and GitHub OAuth as an optional social provider. Co-Authored-By: Claude Opus 4.6 --- components/auth.tsx | 47 +- components/denied.tsx | 9 +- components/user.tsx | 8 +- helpers/url.ts | 2 +- helpers/withAuth.tsx | 11 +- lib/auth-client.ts | 8 + lib/auth.ts | 92 ++ package-lock.json | 1640 +++++++++++++++++++++++++------ package.json | 6 +- pages/api/auth/[...all].ts | 37 + pages/api/auth/[...nextauth].ts | 73 -- 11 files changed, 1506 insertions(+), 427 deletions(-) create mode 100644 lib/auth-client.ts create mode 100644 lib/auth.ts create mode 100644 pages/api/auth/[...all].ts delete mode 100644 pages/api/auth/[...nextauth].ts diff --git a/components/auth.tsx b/components/auth.tsx index 7c8355b..faef7b6 100644 --- a/components/auth.tsx +++ b/components/auth.tsx @@ -1,21 +1,42 @@ -import { useEffect } from 'react' - -import { useSession, signIn } from 'next-auth/react' +import { useState } from 'react' +import { useSession, signIn } from '../lib/auth-client' +import Input from './input' +import Button from './button' export default function Auth({ children }) { - const { data: session, status } = useSession() + const { data: session, isPending } = useSession() const isUser = !!session?.user + const [email, setEmail] = useState('') + const [loading, setLoading] = useState(false) + const [sent, setSent] = useState(false) + const [error, setError] = useState('') - useEffect(() => { - if (status === 'loading') return // Do nothing while loading - if (!isUser) signIn() // If not authenticated, force log in - }, [isUser, status]) + if (isPending) return
Loading...
- if (isUser) { - return children + if (isUser) return children + + if (process.env.NEXT_PUBLIC_GITHUB_ENABLED) { + signIn.social({ provider: "github", callbackURL: window.location.href }) + return
Loading...
} - // Session is being fetched, or no user. - // If no user, useEffect() will redirect. - return
Loading...
+ if (sent) return
E-Mail verschickt — bitte prüfe dein Postfach.
+ + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setLoading(true) + setError('') + const result = await signIn.magicLink({ email, callbackURL: window.location.href }) + if (result.error) setError(result.error.message) + else setSent(true) + setLoading(false) + } + + return ( +
+ setEmail(e.target.value)} required /> + {error &&

{error}

} + +
+ ) } diff --git a/components/denied.tsx b/components/denied.tsx index 66472dc..61bd3fa 100644 --- a/components/denied.tsx +++ b/components/denied.tsx @@ -1,4 +1,4 @@ -import { signIn } from 'next-auth/react' +import { signIn } from '../lib/auth-client' export default function Denied() { return ( @@ -8,7 +8,12 @@ export default function Denied() { { e.preventDefault() - signIn() + if (process.env.NEXT_PUBLIC_GITHUB_ENABLED) { + signIn.social({ + provider: "github", + callbackURL: window.location.href, + }) + } }} > Melde dich an um diese Seite zu sehen. diff --git a/components/user.tsx b/components/user.tsx index db33ef2..f7f7180 100644 --- a/components/user.tsx +++ b/components/user.tsx @@ -1,10 +1,10 @@ -import { useSession, signOut } from 'next-auth/react' +import { useSession, signOut } from '../lib/auth-client' import Link from 'next/link' export default function User() { - const { data, status } = useSession() + const { data: session, isPending } = useSession() - if (status === 'loading' || !data?.user?.email) { + if (isPending || !session?.user?.email) { return null } @@ -17,7 +17,7 @@ export default function User() { Admin
- {data.user.email} + {session.user.email}