paint-brush
Next.js 14'te Kimlik Doğrulaması NextAuth.js, Shadcn/ui, react-hook-form ve Zod ile Nasıl Uygulanır?ile@ljaviertovar
2,035 okumalar
2,035 okumalar

Next.js 14'te Kimlik Doğrulaması NextAuth.js, Shadcn/ui, react-hook-form ve Zod ile Nasıl Uygulanır?

ile L Javier Tovar19m2024/03/01
Read on Terminal Reader

Çok uzun; Okumak

JavaScript ekosisteminde ve daha spesifik olarak Next.js ile geliştirilen uygulamalarda, kimlik doğrulamayı yönetmeye yönelik en öne çıkan kitaplıklardan biri NextAuth.js'dir. Bu araç, uygulamalarımıza kimlik doğrulama eklemek için basit ve uygulaması kolay bir çözüm sunar. En iyi şey esnekliğidir; klasik e-posta ve şifre gibi kimlik bilgilerine dayalı kimlik doğrulamanın yanı sıra Google, Facebook ve Twitter gibi farklı kimlik doğrulama sağlayıcılarının entegrasyonuna olanak tanır.
featured image - Next.js 14'te Kimlik Doğrulaması NextAuth.js, Shadcn/ui, react-hook-form ve Zod ile Nasıl Uygulanır?
L Javier Tovar HackerNoon profile picture
0-item
1-item

Kapsamlı Bir Kılavuz: Kimlik Bilgileri (E-posta ve Şifre)

Kullanıcıların güvenliğini ve gizliliğini sağlamak her zamankinden daha önemli. Web kimlik doğrulaması bu açıdan çok önemli bir rol oynar ve kullanıcıların bilgilerini ve verilerini korumak için ilk savunma hattı olarak hizmet eder.


Bugün elimizde NextAuth.js gibi işimizi çok kolaylaştıran, Next.js uygulamalarımızda farklı kimlik doğrulama türlerini kolaylıkla uygulamamıza olanak tanıyan araçlara sahibiz.


Bu eğitim serisinde, Next.js 14'te temel bilgilerden başlayarak eksiksiz bir kimlik doğrulama sistemi oluşturacağız: e-posta ve parola ile kimlik doğrulama.

NextAuth.js (Auth.js) Nedir?

JavaScript ekosisteminde ve daha spesifik olarak Next.js ile geliştirilen uygulamalarda, kimlik doğrulamayı yönetmeye yönelik en öne çıkan kitaplıklardan biri NextAuth.js'dir.


Bu araç, uygulamalarımıza kimlik doğrulama eklemek için basit ve uygulaması kolay bir çözüm sunar. En iyi şey esnekliğidir; klasik e-posta ve şifre gibi kimlik bilgilerine dayalı kimlik doğrulamanın yanı sıra Google, Facebook ve Twitter gibi farklı kimlik doğrulama sağlayıcılarının entegrasyonuna olanak tanır.

Kimlik Bilgileri Kimlik Doğrulamasını Uygulama

Kimlik bilgisi kimlik doğrulaması, özellikle kimlik doğrulama süreci ve kullanıcı kimlik bilgilerinin saklanması üzerinde tam kontrole ihtiyaç duyduğunuz veya harici kimlik doğrulama sağlayıcılarına güvenmek istemediğiniz uygulamalarda kullanışlıdır.

Kurulum

  1. Aşağıdaki komutla yeni bir Next.js projesi oluşturun ve belirtilen adımları izleyin. TypeScript'i ve src/ klasörünü kullanacağız.


 npx create-next-app@latest


2. Projede ihtiyaç duyulan bağımlılıkları yükleyin. Bu sefer pnpm kullanacağız; İstediğiniz paket yöneticisini kullanabilirsiniz.


 pnpm install next-auth prisma react-hook-form zod, bcrypt


Kullanıcı arayüzü için Shadcn/ui kullanacağız.


 pnpm dlx shadcn-ui@latest init


  • prizma : açık kaynaklı bir veritabanı araç setidir. Kullanıcı kimlik bilgilerini depolamak için kullanacağız.

  • sonraki yetkilendirme : Next.js için kimlik doğrulama.

  • tepki-kanca-formu : React'taki formları doğrulamanıza yardımcı olan bir kütüphane.

  • Zod : bir veri doğrulayıcı.

  • şifreli şifre : şifreleri karma hale getirmek için.

  • shadcn/ui : Yeniden kullanılabilir bileşenlerden oluşan bir koleksiyon.


3. Proje için aşağıdaki yapıyı oluşturun:


 ... ├── prisma/ ... ├── src/ │ ├── actions/ │ │ └── auth-actions.tsx │ ├── app/ │ │ ├── api/auth/[...nextauth] │ │ │ └── route.ts │ │ ├── auth/ │ │ │ ├── signin │ │ │ │ └── page.tsx │ │ │ └── signup │ │ │ └── page.tsx │ │ │ ... │ ├── components/ │ │ ├── auth/ │ │ │ ├── auth-buttons.tsx │ │ │ ├── signin-form.tsx │ │ │ ├── signup-form.tsx │ │ │ └── user-nav.ts │ │ ├── ui/ │ │ │ ... │ │ ├── auth-provider.tsx │ │ ├── icons.tsx │ │ └── theme-provider.tsx │ ├── lib/ │ │ ├── prisma.ts │ │ ├── types.d.ts │ │ └── utils.ts │ ... ...


Prisma'nın Kurulumu

Veritabanındaki kullanıcıları depolamak ve almak için Prisma'yı kullanacağız. Prisma farklı veritabanı türlerinin entegrasyonuna izin verir, böylece ihtiyacınız olan herhangi bir veritabanını kullanabilirsiniz, biz SQLite kullanacağız.


  1. Prisma başlatılıyor .


 npx prisma init --datasource-provider sqlite


Bu, şemalarıyla birlikte prizma klasörünü oluşturur.


  1. Modellerin oluşturulması.

Modelleri oluşturmak için @auth/prisma-adapter tarafından sağlanan modelleri kullanacağız ve bunları aşağıdaki gibi biraz özelleştireceğiz:


prisma/schema.prisma :


 generator client { provider = "prisma-client-js" output = "../../node_modules/.prisma/client" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } ... model User { id String @id @default(cuid()) username String password String email String @unique emailVerified DateTime? phone String? image String? } ...


3. İlkini oluşturmak göç .


 npx prisma migrate dev --name first-migration


Bu komutla Prisma klasöründe daha fazla dosya oluşturuldu ve veritabanı modellerle senkronize edildi.


4. Prisma istemcisi

Son olarak bir Prisma Client oluşturuyoruz.


lib/prisma.ts :


 import { PrismaClient } from "@prisma/client"; const globalForPrisma = global as unknown as { prisma: PrismaClient; }; export const prisma = globalForPrisma.prisma || new PrismaClient(); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; export default prisma;


NextAuth.js'yi Kurma

  1. env değişkenleri oluşturma.


 # Secret key for NextAuth.js, used for encryption and session security. It should be a long, # random string unique to your application. NEXTAUTH_SECRET=XXX3B2CC28F123456C6934531CXXXXX # Base URL for your Next.js app, used by NextAuth.js for redirects and callbacks. NEXTAUTH_URL=http://localhost:3000/


  1. Kimlik doğrulama rotası oluşturma.

Bu yol, tüm kimlik doğrulama isteklerinin (oturum açma, oturum kapatma ve satıcı geri aramaları gibi) tek bir uç noktada işlenmesine olanak tanır.


src/app/api/auth/[...nextauth]


  1. Sağlayıcıları oluşturmak.


 ... // Imports the Prisma User type for typing. import { User } from '@prisma/client' // Configuration of authentication options for NextAuth. export const authOptions: AuthOptions = { ... // Defines authentication providers, in this case, only credentials. providers: [ CredentialsProvider({ name: 'Credentials', // Defines the required fields for authentication. credentials: { email: { label: 'Email', type: 'text' }, password: { label: 'Password', type: 'password' }, }, // Function to authenticate the user with the provided credentials. async authorize(credentials) { // Searches for the user in the database by email. const user = await prisma.user.findUnique({ where: { email: credentials?.email, }, }) // Checks if the user exists and if the password is correct. if (!user) throw new Error('User name or password is not correct') if (!credentials?.password) throw new Error('Please Provide Your Password') const isPasswordCorrect = await bcrypt.compare(credentials.password, user.password) if (!isPasswordCorrect) throw new Error('User name or password is not correct') // Returns the user without including the password. const { password, ...userWithoutPass } = user return userWithoutPass }, }), ], } // Exports the configured NextAuth handler to handle GET and POST requests. const handler = NextAuth(authOptions) export { handler as GET, handler as POST }


4. Yetki Sağlayıcıyı Oluşturma.


src/components/auth-provider.tsx :


 'use client' import { SessionProvider } from 'next-auth/react' export default function AuthProvider({ children }: { children: React.ReactNode }) { return <SessionProvider>{children}</SessionProvider> }


Bu bileşen, kimlik doğrulama için NextAuth kullanan Next.js uygulamaları için oturum sağlayıcı görevi görür.


Bileşenlerin veya sayfaların bu sağlayıcıya sarılması, onlara oturum bağlamına erişim izni vererek alt bileşenlerin, kullanıcının geçerli oturumunun durumuna erişmek veya bu durumu değiştirmek için NextAuth kancalarını ve useSession gibi işlevleri kullanmasına olanak tanır.


src/app/layout.tsx :


 /* All imports */ export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang='en' suppressHydrationWarning > <body className={`${inter.className} relative`}> <AuthProvider> <main>{children}</main> </AuthProvider> </body> </html> ) }


Kullanıcı Arayüzünü Shadcn/ui ile Ayarlama

  1. Aşağıdakileri takip ederek shadcn/ui kurulumu belgeler (adım 2) .


 Would you like to use TypeScript (recommended)? yes Which style would you like to use? › Default Which color would you like to use as base color? › Slate Where is your global CSS file? › src/app/globals.css Do you want to use CSS variables for colors? › yes Are you using a custom tailwind prefix eg. tw-? Leave blank Where is your tailwind.config.js located? › tailwind.config.js Configure the import alias for components: › @/components Configure the import alias for utils: › @/lib/utils Are you using React Server Components? › yes


Dilediğiniz temayı kullanabilirsiniz.


2. Uygulama Karanlık mod


src/app/layout.tsx :


 /* All imports */ export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang='en' suppressHydrationWarning > <body className={`${inter.className} relative`}> <AuthProvider> <ThemeProvider attribute='class' defaultTheme='dark' enableSystem disableTransitionOnChange > <main>{children}</main> <Toaster /> </ThemeProvider> </AuthProvider> </body> </html> ) }


2. Aşağıdaki shadcn/ui bileşenlerinin kurulması:


 pnpm dlx shadcn-ui@latest add avatar button dropdown-menu form input label tabs toast


Kimlik Doğrulama Bileşenleri Oluşturma

src/components/auth-buttons.tsx :


 'use client' import Link from 'next/link' import { signIn, useSession } from 'next-auth/react' import { Button } from '../ui/button' import { UserNav } from './user-nav' export default function AuthButtons() { // Use the useSession hook to access session data const { data: session } = useSession() return ( <div className='flex justify-end gap-4'> {session && session.user ? ( <UserNav user={session.user} /> ) : ( <> <Button size={'sm'} variant={'secondary'} onClick={() => signIn()} > Sign In </Button> <Button size={'sm'} asChild className='text-foreground' > <Link href='/auth/signup'>Sign Up</Link> </Button> </> )} </div> ) }


Bu bileşen, kullanıcının oturum durumuna göre kimlik doğrulama seçeneklerini dinamik olarak görüntüler. Kullanıcı oturum açmışsa kullanıcıya özel gezinme gösterilir. Aksi takdirde, sorunsuz bir kullanıcı deneyimi için Next.js'nin yönlendirmesinden ve NextAuth'un kimlik doğrulama özelliklerinden yararlanarak oturum açma veya kaydolma düğmeleri sunar.


Ana sayfayı değiştirin ve kimlik doğrulama düğmelerini ekleyin. Bu şekilde görünüyor:


Ana Sayfa

src/components/auth/signup-form.tsx :


 'use client' /* all imports */ // Function to register a new user import { registerUser } from '@/actions/auth-actions' // Define the validation schema for the signup form using Zod const formSchema = z .object({ username: z .string({ required_error: 'Username is required', }) .min(2, 'User name must have at least 2 characters') .max(12, 'Username must be up to 12 characters') .regex(new RegExp('^[a-zA-Z0-9]+$'), 'No special characters allowed!'), email: z.string({ required_error: 'Email is required' }).email('Please enter a valid email address'), password: z .string({ required_error: 'Password is required' }) .min(6, 'Password must have at least 6 characters') .max(20, 'Password must be up to 20 characters'), confirmPassword: z .string({ required_error: 'Confirm your password is required' }) .min(6, 'Password must have at least 6 characters') .max(20, 'Password must be up to 20 characters'), }) .refine(values => values.password === values.confirmPassword, { message: "Password and Confirm Password doesn't match!", path: ['confirmPassword'], }) // Type inference for form inputs based on the Zod schema type InputType = z.infer<typeof formSchema> export function SignUpForm() { const [isLoading, setIsLoading] = useState(false) const { toast } = useToast() // Hook to show toast notifications // Initialize form handling with React Hook Form and Zod for validation const form = useForm<InputType>({ resolver: zodResolver(formSchema), }) // Handles form submission async function onSubmit(values: InputType) { try { setIsLoading(true) const { confirmPassword, ...user } = values // Exclude confirmPassword from data to be sent const response = await registerUser(user) // Register the user if ('error' in response) { toast({ title: 'Something went wrong!', description: response.error, variant: 'success', }) } else { toast({ title: 'Account Created!', description: 'Your account has been created successfully! You can now login.', }) } } catch (error) { console.error(error) toast({ title: 'Something went wrong!', description: "We couldn't create your account. Please try again later!", variant: 'destructive', }) } finally { setIsLoading(false) } } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> <div className='grid gap-2'> // Each FormField validates and displays an input <FormField control={form.control} name='username' render={({ field }) => ( <FormItem> <FormControl> <div className='flex items-center gap-2'> <Icons.user className={`${form.formState.errors.username ? 'text-destructive' : 'text-muted-foreground'} `} /> <Input placeholder='Your Username' className={`${form.formState.errors.username && 'border-destructive bg-destructive/30'}`} {...field} /> </div> </FormControl> <FormMessage /> </FormItem> )} /> // Repeated structure for email, password, and confirmPassword with respective validations and icons <Button className='text-foreground mt-4' disabled={isLoading} // Disable button during form submission > {isLoading && <Icons.spinner className='mr-2 h-4 w-4 animate-spin' />} // Show loading icon if isLoading is true Sign Up </Button> </div> </form> </Form> ) }


Bu bileşen, form durumu yönetimi için react-hook-form'u ve şema doğrulaması için Zod'u kullanan bir kullanıcı kayıt formunu içerir.


Bu, bu teknolojiler hakkında bir eğitim olmasa da, yalnızca temellerini kullanıyoruz. Daha fazla sorunuz varsa belgelerine gidebilirsiniz.


Sayfaya birkaç stil daha ekledim ve şöyle görünüyor:


Kayıt Sayfası



src/actions/auth-action/ts :


 'use server' /* all imports */ export async function registerUser(user: Omit<User, 'id' | 'phone' | 'emailVerified' | 'image'>) { try { // Attempt to create a new user record in the database const result = await prisma.user.create({ data: { ...user, // Hash the password before storing it password: await bcrypt.hash(user.password, 10), }, }) return result } catch (error) { console.log(error) // Handle known request errors from Prisma if (error instanceof Prisma.PrismaClientKnownRequestError) { // Check for unique constraint failure (eg, email already exists) if (error.code === 'P2002') { return { error: 'Email already exists.' } } } // Return a generic error message for any other errors return { error: 'An unexpected error occurred.' } } }


registerUser işlevi, id , phone , emailVerified ve image gibi alanlar hariç olmak üzere, sağlanan kullanıcı bilgileriyle veritabanında bir kayıt oluşturarak yeni bir kullanıcıyı güvenli bir şekilde kaydetmek için tasarlanmıştır.


Güvenli depolama amacıyla kullanıcının şifresini karma hale getirmek için bcrypt'i kullanır.


Kaydolma işlemimizi test etmek ve kullanıcının doğru şekilde kaydolduğunu doğrulamak için bazı geri aramalar eklememiz gerekir; bunlar, kimlik doğrulama ve oturum yönetimi davranışını özelleştirmenize olanak tanıyan işlevlerdir.


src/app/api/auth/[...nextauth] :


 ... export const authOptions: AuthOptions = { // Define custom pages for authentication flow pages: { signIn: '/auth/signin', // Custom sign-in page }, // Configure session management to use JSON Web Tokens (JWT) session: { strategy: 'jwt', }, // JWT configuration, including secret for token signing jwt: { secret: process.env.NEXTAUTH_SECRET, // Secret used to sign the JWT, stored in environment variables }, ... // Callbacks for customizing JWT and session behaviors callbacks: { // Callback to modify the JWT content. Adds user information if available. async jwt({ token, user }) { if (user) token.user = user as User // Cast user object to User type and assign to token return token }, // Callback to modify session content. Adds user information to the session. async session({ token, session }) { session.user = token.user // Assign user information from token to session return session }, }, } ...


Geri arama jwt : Bu geri arama, kimlik doğrulama yaşam döngüsü sırasında bir JSON Web Token (JWT) oluşturulduğunda veya güncellendiğinde gerçekleştirilir. İmzalanmadan ve istemciye gönderilmeden veya sunucuda saklanmadan önce belirtecin içeriğini değiştirmenize olanak tanır.


Bu, uygulama mantığınızla alakalı olabilecek ek bilgileri tokena eklemek için kullanışlıdır.


Geri arama session : Bu geri arama, sunucu tarafı oluşturma veya korumalı API istekleri gibi oturum verileri her okunduğunda çağrılır. Oturum verilerinin istemciye gönderilmeden önce değiştirilmesine olanak tanır.


Bu, özellikle JWT'de veya diğer ölçütlerde depolanan bilgilere dayalı olarak oturum verilerini eklemek veya değiştirmek için kullanışlıdır.


Son olarak NextAuth Session ve JWT tür tanımlarını ek kullanıcı bilgilerini içerecek şekilde genişletmemiz gerekiyor.


src/lib/types.d.ts :


 import { User } from '@prisma/client' declare module 'next-auth' { interface Session { user: User } } declare module 'next-auth/jwt' { interface JWT { user: User } }


Şimdi formu doldurup gönderirsek başarı kadehini görebileceğiz. Kullanıcının veritabanına kaydedildiğini doğrulamak için aşağıdaki komutla Prisma'nın oluşturduğu tabloları grafiksel olarak görebiliriz:


 nxp prisma studio


Aşağıdaki rotayı kullanıma sunacağız http://localhost:5555


Prizma Stüdyo


src/components/auth/user-nav.tsx :


 /* all imports */ interface Props { user: User // Expect a user object of type User from Prisma client } export function UserNav({ user }: Props) { return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant='ghost' className='relative h-8 w-8 rounded-full' > <Avatar className='h-9 w-9'> <AvatarImage src='/img/avatars/01.png' alt='' /> <AvatarFallback>UU</AvatarFallback> </Avatar> </Button> </DropdownMenuTrigger> <DropdownMenuContent className='w-56' align='end' forceMount > <DropdownMenuLabel className='font-normal'> <div className='flex flex-col space-y-1'> <p className='text-sm font-medium leading-none'>{user.username}</p> <p className='text-xs leading-none text-muted-foreground'>{user.email}</p> </div> </DropdownMenuLabel> <DropdownMenuSeparator /> <DropdownMenuItem> <Link href={'/api/auth/signout'} // Link to the signout API route className='w-full' > Sign Out </Link> </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ) } 


Ana Sayfa



src/components/auth/signin-form.tsx :


 /* all imports */ // Schema definition for form validation using Zod const formSchema = z.object({ email: z.string({ required_error: 'Please enter your email' }).email('Please enter a valid email address'), password: z.string({ required_error: 'Please enter your password', }), }) // Type inference for form inputs based on the Zod schema type InputType = z.infer<typeof formSchema> // Props definition, optionally including a callback URL interface Props { callbackUrl?: string } export function SignInForm({ callbackUrl }: Props) { const [isLoading, setIsLoading] = useState(false) const { toast } = useToast() const router = useRouter() // Hook to control routing const form = useForm<InputType>({ resolver: zodResolver(formSchema), // Set up Zod as the form validation resolver }) // Function to handle form submission async function onSubmit(values: InputType) { try { setIsLoading(true) // Attempt to sign in using the 'credentials' provider const response = await signIn('credentials', { redirect: false, // Prevent automatic redirection email: values.email, password: values.password, }) // Handle unsuccessful sign in attempts if (!response?.ok) { toast({ title: 'Something went wrong!', description: response?.error, variant: 'destructive', }) return } toast({ title: 'Welcome back! ', description: 'Redirecting you to your dashboard!', }) router.push(callbackUrl ? callbackUrl : '/') // Redirect to the callback URL or home page } catch (error) { toast({ title: 'Something went wrong!', description: "We couldn't create your account. Please try again later!", variant: 'destructive', }) } finally { setIsLoading(false) } } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> <div className='grid gap-2'> <div className='grid gap-1'> <FormField control={form.control} name='email' render={({ field }) => ( <FormItem> <FormControl> <div className='flex items-center gap-2'> <Icons.email className={`${form.formState.errors.email ? 'text-destructive' : 'text-muted-foreground'} `}/> <Input type='email' placeholder='Your Email' className={`${form.formState.errors.email && 'border-destructive bg-destructive/30'}`} {...field} /> </div> </FormControl> <FormMessage /> </FormItem> )} /> {/* Password field */} {/* Similar structure to email field, customized for password input */} </div> <Button className='text-foreground mt-4' disabled={isLoading} // Disable button while loading > {isLoading && <Icons.spinner className='mr-2 h-4 w-4 animate-spin' />} // Show loading spinner when processing Sign In </Button> </div> </form> </Form> ) } 


Oturum Açma Sayfası


🎉Tamamlandı!


NextAuth.js ile temel kimlik doğrulamayı uygulamayı tamamladık. Eksiksiz bir kimlik doğrulama sistemine sahip olmak için hala yapılması gereken birçok şey var ve bunları sonraki eğitimlerde ele alacağız.


🔗 Buraya kopyala


Çözüm

Özetle, NextAuth kullanarak Next.js'de bir kimlik doğrulama sisteminin nasıl uygulanacağını ve özelleştirileceğini, kullanıcı yönetimini zenginleştirmek için oturumların ve JWT'lerin nasıl genişletileceğini ve react-hook-form ve Zod kullanarak formların etkili doğrulamayla nasıl yönetileceğini araştırdık.


Yazarla bağlantı kurmak ister misiniz?


Dünyanın her yerindeki arkadaşlarla bağlantı kurmayı seviyorum 𝕏 .


Burada da yayınlandı