Open rrsartneoai opened 3 months ago
To allow users to sign up and log in using their Google account, we need to integrate Google OAuth into our authentication system. This involves updating the UI components to include Google login/signup buttons and configuring the authentication logic to handle Google OAuth tokens and sessions.
components/login-button.tsx
Add a Google login button to the LoginButton
component and handle the Google OAuth flow.
components/login-button.tsx
'use client'
import * as React from 'react'
import { signIn } from 'next-auth/react'
import { cn } from '@/lib/utils'
import { Button, type ButtonProps } from '@/components/ui/button'
import { IconGitHub, IconGoogle, IconSpinner } from '@/components/ui/icons'
interface LoginButtonProps extends ButtonProps {
provider: 'github' | 'google'
text?: string
}
export function LoginButton({
provider,
text,
className,
...props
}: LoginButtonProps) {
const [isLoading, setIsLoading] = React.useState(false)
const handleSignIn = () => {
setIsLoading(true)
signIn(provider, { callbackUrl: `/` })
}
const renderIcon = () => {
if (isLoading) {
return <IconSpinner className="mr-2 animate-spin" />
}
switch (provider) {
case 'github':
return <IconGitHub className="mr-2" />
case 'google':
return <IconGoogle className="mr-2" />
default:
return null
}
}
return (
<Button
variant="outline"
onClick={handleSignIn}
disabled={isLoading}
className={cn(className)}
{...props}
>
{renderIcon()}
{text || `Login with ${provider.charAt(0).toUpperCase() + provider.slice(1)}`}
</Button>
)
}
auth.config.ts
Configure the Google OAuth provider in the auth.config.ts
file.
auth.config.ts
import type { NextAuthConfig } from 'next-auth'
import GoogleProvider from 'next-auth/providers/google'
export const authConfig = {
secret: process.env.AUTH_SECRET,
pages: {
signIn: '/login',
newUser: '/signup'
},
callbacks: {
async authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user
const isOnLoginPage = nextUrl.pathname.startsWith('/login')
const isOnSignupPage = nextUrl.pathname.startsWith('/signup')
if (isLoggedIn) {
if (isOnLoginPage || isOnSignupPage) {
return Response.redirect(new URL('/', nextUrl))
}
}
return true
},
async jwt({ token, user }) {
if (user) {
token = { ...token, id: user.id }
}
return token
},
async session({ session, token }) {
if (token) {
const { id } = token as { id: string }
const { user } = session
session = { ...session, user: { ...user, id } }
}
return session
}
},
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
})
]
} satisfies NextAuthConfig
package.json
Add the necessary dependencies for Google OAuth.
package.json
{
"dependencies": {
"next-auth": "5.0.0-beta.4",
"@next-auth/google": "^1.0.0"
}
}
Run the following command to install the new dependencies:
pnpm install
components/login-form.tsx
Add a Google login button to the login form.
components/login-form.tsx
'use client'
import { useFormState, useFormStatus } from 'react-dom'
import { authenticate } from '@/app/login/actions'
import Link from 'next/link'
import { useEffect } from 'react'
import { toast } from 'sonner'
import { IconSpinner } from './ui/icons'
import { getMessageFromCode } from '@/lib/utils'
import { useRouter } from 'next/navigation'
import { signIn } from 'next-auth/react'
export default function LoginForm() {
const router = useRouter()
const [result, dispatch] = useFormState(authenticate, undefined)
useEffect(() => {
if (result) {
if (result.type === 'error') {
toast.error(getMessageFromCode(result.resultCode))
} else {
toast.success(getMessageFromCode(result.resultCode))
router.refresh()
}
}
}, [result, router])
return (
<form
action={dispatch}
className="flex flex-col items-center gap-4 space-y-3"
>
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md md:w-96 dark:bg-zinc-950">
<h1 className="mb-3 text-2xl font-bold">Please log in to continue.</h1>
<div className="w-full">
<div>
<label
className="mb-3 mt-5 block text-xs font-medium text-zinc-400"
htmlFor="email"
>
Email
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border bg-zinc-50 px-2 py-[9px] text-sm outline-none placeholder:text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950"
id="email"
type="email"
name="email"
placeholder="Enter your email address"
required
/>
</div>
</div>
<div className="mt-4">
<label
className="mb-3 mt-5 block text-xs font-medium text-zinc-400"
htmlFor="password"
>
Password
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border bg-zinc-50 px-2 py-[9px] text-sm outline-none placeholder:text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950"
id="password"
type="password"
name="password"
placeholder="Enter password"
required
minLength={6}
/>
</div>
</div>
</div>
<LoginButton />
<GoogleLoginButton />
</div>
<Link
href="/signup"
className="flex flex-row gap-1 text-sm text-zinc-400"
>
No account yet? <div className="font-semibold underline">Sign up</div>
</Link>
</form>
)
}
function LoginButton() {
const { pending } = useFormStatus()
return (
<button
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
aria-disabled={pending}
>
{pending ? <IconSpinner /> : 'Log in'}
</button>
)
}
function GoogleLoginButton() {
return (
<button
type="button"
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-red-600 p-2 text-sm font-semibold text-white hover:bg-red-500"
onClick={() => signIn('google')}
>
Log in with Google
</button>
)
}
components/signup-form.tsx
Add a Google signup button to the signup form.
components/signup-form.tsx
'use client'
import { useFormState, useFormStatus } from 'react-dom'
import { signup } from '@/app/signup/actions'
import Link from 'next/link'
import { useEffect } from 'react'
import { toast } from 'sonner'
import { IconSpinner } from './ui/icons'
import { getMessageFromCode } from '@/lib/utils'
import { useRouter } from 'next/navigation'
import { signIn } from 'next-auth/react'
export default function SignupForm() {
const router = useRouter()
const [result, dispatch] = useFormState(signup, undefined)
useEffect(() => {
if (result) {
if (result.type === 'error') {
toast.error(getMessageFromCode(result.resultCode))
} else {
toast.success(getMessageFromCode(result.resultCode))
router.refresh()
}
}
}, [result, router])
return (
<form
action={dispatch}
className="flex flex-col items-center gap-4 space-y-3"
>
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md md:w-96 dark:bg-zinc-950">
<h1 className="mb-3 text-2xl font-bold">Sign up for an account!</h1>
<div className="w-full">
<div>
<label
className="mb-3 mt-5 block text-xs font-medium text-zinc-400"
htmlFor="email"
>
Email
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border bg-zinc-50 px-2 py-[9px] text-sm outline-none placeholder:text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950"
id="email"
type="email"
name="email"
placeholder="Enter your email address"
required
/>
</div>
</div>
<div className="mt-4">
<label
className="mb-3 mt-5 block text-xs font-medium text-zinc-400"
htmlFor="password"
>
Password
</label>
<div className="relative">
<input
className="peer block w-full rounded-md border bg-zinc-50 px-2 py-[9px] text-sm outline-none placeholder:text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950"
id="password"
type="password"
name="password"
placeholder="Enter password"
required
minLength={6}
/>
</div>
</div>
</div>
<LoginButton />
<GoogleSignupButton />
</div>
<Link href="/login" className="flex flex-row gap-1 text-sm text-zinc-400">
Already have an account?
<div className="font-semibold underline">Log in</div>
</Link>
</form>
)
}
function LoginButton() {
const { pending } = useFormStatus()
return (
<button
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
aria-disabled={pending}
>
{pending ? <IconSpinner /> : 'Create account'}
</button>
)
}
function GoogleSignupButton() {
return (
<button
type="button"
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-red-600 p-2 text-sm font-semibold text-white hover:bg-red-500"
onClick={() => signIn('google')}
>
Sign up with Google
</button>
)
}
auth.ts
Integrate the Google OAuth provider into the authentication logic.
auth.ts
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import GoogleProvider from 'next-auth/providers/google'
import { authConfig } from './auth.config'
import { z } from 'zod'
import { getStringFromBuffer } from './lib/utils'
import { getUser } from './app/login/actions'
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
const parsedCredentials = z
.object({
email: z.string().email(),
password: z.string().min(6)
})
.safeParse(credentials)
if (parsedCredentials.success) {
const { email, password } = parsedCredentials.data
const user = await getUser(email)
if (!user) return null
const encoder = new TextEncoder()
const saltedPassword = encoder.encode(password + user.salt)
const hashedPasswordBuffer = await crypto.subtle.digest(
'SHA-256',
saltedPassword
)
const hashedPassword = getStringFromBuffer(hashedPasswordBuffer)
if (hashedPassword === user.password) {
return user
} else {
return null
}
}
return null
}
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET
})
],
callbacks: {
async session({ session, token, user }) {
return session
},
async jwt({ token, user, account, profile, isNewUser }) {
return token
}
}
})
Ensure you have the following environment variables set in your .env
file:
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
By following these steps, you will have integrated Google OAuth into your authentication system, allowing users to sign up and log in using their Google accounts.
Click here to create a Pull Request with the proposed solution
Files used for this task:
want to allow users to signup and login using their Google account