Next.js 14: Cách Sử Dụng Next Auth cơ bản để xác thực

 


Next.js 14: Cách Sử Dụng Next Auth cơ bản để xác thực

Bạn đã bao giờ gặp khó khăn trong việc xác thực người dùng trên ứng dụng web của mình? Bạn có muốn tìm hiểu về cách sử dụng Next Auth trong Next.js 14 để giải quyết vấn đề này một cách dễ dàng và nhanh chóng? Nếu câu trả lời là có, thì bạn đã đến đúng nơi!

Trong bài viết này, chúng tôi sẽ giới thiệu về Next Auth và cách sử dụng nó trong Next.js 14 để xác thực người dùng. Bạn sẽ được tìm hiểu về các khổ đau và vấn đề liên quan đến việc xác thực, cũng như những lợi ích mà Next Auth có thể mang lại cho ứng dụng web của bạn.

Hãy tiếp tục đọc để khám phá thêm về Next.js 14 và Next Auth, và cách chúng có thể giúp bạn giải quyết các vấn đề xác thực người dùng một cách hiệu quả.

Takeaways:

  • Next.js 14 là một phiên bản mới nhất của framework Next.js, mang lại nhiều cải tiến và tính năng mới.
  • Next Auth là một thư viện xác thực mạnh mẽ, giúp bạn xác thực người dùng một cách đơn giản và linh hoạt.
  • Việc sử dụng Next Auth trong Next.js 14 sẽ giúp bạn tiết kiệm thời gian và công sức trong việc xây dựng hệ thống xác thực trong ứng dụng web của mình.

Hãy truy cập nextjs 14 next auth để tìm hiểu thêm về cách sử dụng Next Auth trong Next.js 14 và làm cho việc xác thực người dùng trở nên dễ dàng hơn bao giờ hết.

Giới thiệu về Next.js 14 và Next Auth

Next.js 14 - Framework Next.js tiên tiến hơn

Next.js 14 là phiên bản mới nhất của framework Next.js, được phát triển để cung cấp một trải nghiệm phát triển web tiên tiến hơn cho các nhà phát triển. Với Next.js 14, nhà phát triển có thể tận dụng các tính năng như tạo trang tĩnh, tối ưu hóa SEO, tải trang nhanh, và nhiều hơn nữa. Với việc hỗ trợ TypeScript và API Routes, Next.js 14 là một lựa chọn mạnh mẽ cho việc phát triển ứng dụng web hiệu suất cao.

Next Auth - Thư viện xác thực người dùng cho Next.js

Next Auth là một thư viện xác thực người dùng dễ sử dụng và mạnh mẽ cho Next.js. Với Next Auth, việc xây dựng hệ thống xác thực và đăng nhập cho ứng dụng web trở nên đơn giản hơn bao giờ hết. Thư viện này hỗ trợ nhiều phương thức xác thực như đăng nhập bằng email và mật khẩu, đăng nhập bằng mạng xã hội, và nhiều hơn nữa. Next Auth cung cấp tích hợp dễ dàng với các nhà cung cấp xác thực phổ biến như Google, Facebook, và Twitter.

Tóm lại, Next.js 14 và Next Auth cung cấp cho nhà phát triển một bộ công cụ mạnh mẽ để phát triển ứng dụng web hiệu suất cao với khả năng xác thực người dùng dễ dàng.

Cài đặt Next.js 14 và Next Auth

Cài đặt Next.js 14 bằng npm

Next.js là một framework JavaScript phía máy chủ được sử dụng để xây dựng các ứng dụng web hiệu suất cao. Phiên bản Next.js 14 là phiên bản mới nhất và đi kèm với nhiều cải tiến và tính năng mới. Để cài đặt Next.js 14, bạn có thể sử dụng npm.

npm install next@14

Sau khi cài đặt thành công, bạn có thể bắt đầu sử dụng Next.js 14 để xây dựng ứng dụng web của mình.

Tạo ứng dụng Next.js 14

Mở terminal và chạy lệnh sau để tạo một ứng dụng Next.js 14 mới.

npx create-next-app@latest my-nextjs-app

my-nextjs-app là tên thư mục cho ứng dụng của bạn. Bạn có thể đổi tên nếu muốn.

Di chuyển vào thư mục ứng dụng

Di chuyển vào thư mục ứng dụng với lệnh:

cd my-nextjs-app

Cài đặt Next Auth bằng npm

Next Auth là một thư viện xác thực phổ biến cho Next.js. Nó cung cấp các phương pháp xác thực đơn giản và linh hoạt như xác thực qua email, xác thực qua mạng xã hội và xác thực qua API. Để cài đặt Next Auth, bạn có thể sử dụng npm.

npm install next-auth

Sau khi cài đặt thành công, bạn có thể sử dụng Next Auth để xác thực người dùng trong ứng dụng Next.js của mình.

Việc cài đặt Next.js 14 và Next Auth sẽ giúp bạn xây dựng các ứng dụng web hiệu suất cao và cung cấp các phương pháp xác thực linh hoạt cho người dùng. Với Next.js 14, bạn sẽ có các tính năng mới và cải tiến để tăng cường trải nghiệm người dùng. Next Auth sẽ giúp bạn quản lý việc xác thực và bảo mật trong ứng dụng của mình một cách dễ dàng và tiện lợi.

Chạy ứng dụng

Chạy ứng dụng Next.js với lệnh:

npm run dev

Ứng dụng sẽ chạy ở http://localhost:3000/. Bạn có thể mở trình duyệt và kiểm tra ứng dụng của mình.

Sửa lại trang

Bây giờ chúng ta sẽ sửa lại một chút giao diện trang chủ

/app/page.tsx
'use client'

import Link from "next/link";

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
        <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto  lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
          Hi, <code className="font-mono font-bold">Guest</code>
        </p>
        <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
          <Link
            className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
            href="/login"
          >
            Login
            
          </Link>
        </div>
      </div>      
    </main>
  );
}

Cấu hình Next Auth

Tạo một file cấu hình cho Next Auth

Để bắt đầu sử dụng Next Auth, chúng ta cần tạo một file cấu hình để thiết lập các thông tin cần thiết. Trong file này, chúng ta có thể thiết lập các cài đặt liên quan đến xác thực và quản lý người dùng.

Tại thư mục app, các bạn tạo file cấu hình theo đường dẫn /app/api/auth/[...nextauth]/route.ts

/app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"

const handler = NextAuth({})

export { handler as GET, handler as POST }

Thiết lập thông tin xác thực (providers)

Next Auth hỗ trợ nhiều nhà cung cấp thông tin xác thực như Google, Facebook, GitHub, và nhiều hơn nữa. Chúng ta có thể cấu hình các nhà cung cấp này trong file cấu hình bằng cách cung cấp các thông tin như clientId, clientSecret, và scope.

Trong bài viết này, chúng ta sẽ xác thực theo kiểu Credential, nghĩa là các bạn có thể xác thực tuỳ ý như là xác thực bằng email và password, đó có thể là một service backend dùng để login và có trả về access token hoặc các thông tin khác của user.

Trong file route.ts, sẽ thiết lập như sau

/app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"

const handler = NextAuth({
    providers: [
        Credentials({
            name: 'Credentials',
            credentials: {},
            async authorize(credentials, req) {
                const { email, password } = credentials as any

                if (email === 'user@lahotech.com' && password === '123456') {
                    return { id: 12345679, email, name: 'User', access_token: 'access_token' } as any
                }

                throw new Error("Invalid email or password")

                // Có thể gọi tới api login ở đây. 
                // Example:
                // const res = await fetch("/your/endpoint", {
                //     method: 'POST',
                //     body: JSON.stringify(credentials),
                //     headers: { "Content-Type": "application/json" }
                // })
                // const user = await res.json()
                // if (res.ok && user) {
                //     return user
                // }

            },
        })
    ],
})

export { handler as GET, handler as POST }

Ở đây chúng ta dùng provider là Credentials. Sau đây mình sẽ giải thích sơ qua đoạn code trên:

  • name: tên mô tả của provider Credentials
  • credentials: một đối tượng dùng để xác thực, ở đây mình để trống vì không cần. Mình sẽ tạo trang login riêng.
  • Hàm authorize: Xử lý xác minh thông tin đăng nhập và ủy quyền người dùng

Trong hàm authorize mình lấy thông tin email, password từ đối tượng credentials. Sau đó mình giả định nếu email, password đúng như thông tin mình hardcode thì sẽ trả về một đối tượng có các thuộc tính như code ở trên. Các bạn có thể gọi api login tới backend service ở phần này, mình có để comment ví dụ ở dưới.

Thiết lập các tùy chọn xác thực khác (options)

Ngoài các thông tin cơ bản về xác thực, chúng ta còn có thể thiết lập các tùy chọn khác như callbackspages, session. Các callbacks cho phép chúng ta xử lý các sự kiện liên quan đến xác thực, trong khi pages cho phép tùy chỉnh các trang liên quan đến xác thực, còn session sẽ giúp chúng ta thiết lập khoảng thời gian mà phiên đăng nhập đó tồn tại cũng như phương thức để lưu user session (mặc định là jwt)

Các bạn thiết lập như dưới đây:

/app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"

const handler = NextAuth({
    providers: [
        Credentials({
            name: 'Credentials',
            credentials: {},
            async authorize(credentials, req) {
                const { email, password } = credentials as any

                if (email === 'user@lahotech.com' && password === '123456') {
                    return { id: 12345679, email, name: 'User', access_token: 'access_token' } as any
                }

                throw new Error("Invalid email or password")

                // Có thể gọi tới api login ở đây. 
                // Example:
                // const res = await fetch("/your/endpoint", {
                //     method: 'POST',
                //     body: JSON.stringify(credentials),
                //     headers: { "Content-Type": "application/json" }
                // })
                // const user = await res.json()
                // if (res.ok && user) {
                //     return user
                // }

            },
        })
    ],
    callbacks: {
        async session({ session, token }) {
            session.user = token as any

            return session
        },
        async jwt({ token, user}) {
            // Kiểm tra nếu tồn tại user thì gán đối tượng user vô token, để qua callback session gán đối tượng session.user
            if (user) {
                token = { ...token, ...user }
            }
            
            return token
        }
    },
    session: {
        strategy: "jwt", // Phương thức để lưu user session
        maxAge: 7 * 24 * 60 * 60, // 7 days - neu khong khai bao o day thi no se lay maxAge trong jwt.maxAge
    },
    secret: process.env.NEXT_PUBLIC_NEXTAUTH_SECRET, // Mã secret để generate jwt token, có thể để mặc định không cần khai báo cũng được.
    pages: {
        signIn: '/login', // Đây là trang login của mình, chúng ta sẽ custom trang này ở phần tiếp theo
    },
})

export { handler as GET, handler as POST }

Callback jwt sẽ được gọi trước callback session. Và ở trong callback jwt, đối tượng user sẽ tồn tại trong lần đầu tiên khi đăng nhập session mới, trong những lần gọi tiếp theo thì chỉ còn đối tượng token tồn tại. Và đối tượng token sẽ được truyền qua callback session, nghĩa là những gì bạn thêm vào đối tượng token jwt callback sẽ tồn tại trong đối tượng tokensession callback

Next Auth cung cấp một cách dễ dàng để xây dựng chức năng xác thực trong ứng dụng Next.js của bạn. Bằng cách sử dụng cấu hình Next Auth, chúng ta có thể nhanh chóng thiết lập các nhà cung cấp xác thực và tùy chỉnh các tùy chọn xác thực khác một cách linh hoạt. Điều này giúp tiết kiệm thời gian và công sức trong việc xây dựng tính năng xác thực trong ứng dụng của chúng ta.

Đăng nhập người dùng

Tạo trang đăng nhập người dùng

Đăng nhập người dùng là quá trình cho phép người dùng truy cập vào tài khoản đã đăng ký trên hệ thống. Trang đăng nhập người dùng cung cấp một giao diện đơn giản và an toàn để người dùng nhập thông tin đăng nhập như email và mật khẩu. Sau khi nhập thông tin đúng, người dùng sẽ được chuyển hướng đến trang chính và có quyền truy cập vào các tính năng và dịch vụ của ứng dụng.

Tại thư mục app chúng ta tạo thêm một thư mục có tên là login và trong thư mục này tạo cho mình file page.tsx có nội dung như sau

/app/login/page.tsx
'use client'

import { SignInResponse, signIn } from 'next-auth/react'
import React, { useState } from 'react'

export default function Login() {
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')

    return (
        <div className="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
            <div className="sm:mx-auto sm:w-full sm:max-w-sm">

                <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-white">Sign in to your account</h2>
            </div>

            <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
                <div className="space-y-6">
                    <div>
                        <label htmlFor="email" className="block text-sm font-medium leading-6 text-white">Email address</label>
                        <div className="mt-2">
                            <input id="email" onChange={(e) => setEmail(e.target.value)} name="email" type="email" autoComplete="email" required className="p-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
                        </div>
                    </div>

                    <div>
                        <div className="flex items-center justify-between">
                            <label htmlFor="password" className="block text-sm font-medium leading-6 text-white">Password</label>

                        </div>
                        <div className="mt-2">
                            <input id="password" onChange={(e) => setPassword(e.target.value)} name="password" type="password" autoComplete="current-password" required className="p-2 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" />
                        </div>
                    </div>

                    <div>
                        <button onClick={onSubmitLogin} className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign in</button>
                    </div>
                </div>


            </div>
        </div>
    )
}

Trên trang chủ các bạn bấm vào login thì sẽ ra trang login như dưới đây:

Xử lý đăng nhập người dùng

Chúng ta sẽ xử lý đăng nhập bằng function signIn của Next Auth, nó là một Client API. Function này cần truyền vô tham số đầu tiên là tên của provider, ở đây chúng ta dùng credentials nên sẽ truyền vô tham số đầu tiên là credentials, tham số thứ 2 là options, là 2 field email và password mà chúng ta dùng để đăng nhập cùng với tuỳ chọn redirectfalse.

Async function này sẽ trả về một đối tượng SignInResponse hoặc undefined, nếu đối tượng này trả về thuộc tính oktrue thì nghĩa là đã đăng nhập thành công. Các bạn có thể Ctrl click vô đối tượng SignInResponse này để xem các thuộc tính còn lại của nó.

Dưới đây là đoạn code handle đăng nhập, nếu đăng nhập thành công thì sẽ chuyển về trang home

/app/login/page.tsx
'use client'

import { SignInResponse, signIn } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import React, { useState } from 'react'

export default function Login() {
    const router = useRouter()
    const [email, setEmail] = useState('')
    const [password, setPassword] = useState('')

    const onSubmitLogin = async () => {
        try {
            await signIn('credentials', {
                email,
                password,
                redirect: false,
            }).then((res: SignInResponse | undefined) => {
                if (res?.ok) {
                    router.push('/')
                }
            }).catch(error => console.log('error', error))
        } catch (error) {
            console.log('error - ', error)
        }
    }

    return (
        ...
    )
}

Hiển thị tên user đã đăng nhập trên trang chủ

Bây giờ, khi đã đăng nhập thành công rồi thì làm sao chúng ta có thể lấy thông tin user đã đăng nhập? Câu trả lời ở đây là chúng ta sẽ dùng một Client API mà Next Auth cũng cấp để thực hiện việc này. Client API này là useSession(), vì nó là một Client API nên chúng tra sẽ thêm 'use client' ở trên đầu file.

Các bạn mở lại file page.tsx trong app/page.tsx

/app/page.tsx
'use client'

import {signOut, useSession} from "next-auth/react";
import Link from "next/link";
import {useRouter} from "next/navigation";

export default function Home() {
  const router = useRouter()
  
  const {data: session} = useSession()

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
        <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto  lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
          Hi, <code className="font-mono font-bold">{!session?.user ? 'Guest' : session.user.name}</code>
        </p>
        <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
          {!session ? <Link
            className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
            href="/login"
          >
            Login
          </Link> : <button
            onClick={handleLogout}
            className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
          >
            Logout
          </button>}
        </div>
      </div>      
    </main>
  );
}

useSession() sẽ trả về một đối tượng có 2 thuộc tính là data và status. Ở đây chúng ta sẽ quan tâm tới thuộc tính data. Thuộc tính data này có thể nhận ba giá trị: Sessionundefined, hoặc null:

  • undefined: Khi chưa có session nào được đăng nhập, data sẽ là undefined.
  • null: Nếu việc lấy session thất bại, data sẽ là null.
  • Session: Nếu thành công, data sẽ là một đối tượng Session chứa thông tin về session hiện tại của user.

Ở dòng code thứ 16: {!session?.user ? 'Guest' : session.user.name} sẽ kiểm tra xem nếu như không có session thì sẽ hiển thị là Guest, còn ngược lại thì hiển thị tên user đã đăng nhập.

Xử lý đăng xuất

Để xử lý việc đăng xuất user thì cúng ta cũng sẽ dùng một Client API đó là signOut. Chúng ta sẽ tạop một function là handleLogout sau đó gắn cho sự kiện onClick cho button. Khi user click vào sẽ gọi tới nào này thì khi đăng xuất thành công sẽ chuyển về trang home.

Dưới đây là code xử lý:

TypeScript
'use client'

import {signOut, useSession} from "next-auth/react";
import Link from "next/link";
import {useRouter} from "next/navigation";

export default function Home() {
    const router = useRouter()

    const {data: session} = useSession()

    const handleLogout = () => {
        signOut({redirect: false})
            .then(() => router.push('/'))
            .catch((err) => console.log(err))
    }

    return (
        <main className="flex min-h-screen flex-col items-center justify-between p-24">
            <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
                <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto  lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
                    Hi, <code className="font-mono font-bold">{!session?.user ? 'Guest' : session.user.name}</code>
                </p>
                <div
                    className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
                    {!session ? <Link
                        className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
                        href="/login"
                    >
                        Login
                    </Link> : <button
                        onClick={handleLogout}
                        className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
                    >
                        Logout
                    </button>}
                </div>
            </div>
        </main>
    );
}

Tổng kết về việc sử dụng Next.js 14 và Next Auth

Các lợi ích của việc sử dụng Next.js 14 và Next Auth

Next.js 14 là một trong những phiên bản mới nhất của Next.js framework, mang đến những cải tiến vượt trội về hiệu suất và trải nghiệm người dùng. Next.js cung cấp một cách tiếp cận hiện đại và dễ dàng trong việc xây dựng ứng dụng web, với khả năng xử lý tốt trong việc render trên phía máy chủ và phía khách hàng.

Next Auth là một thư viện xác thực mạnh mẽ dựa trên Next.js, giúp việc xây dựng hệ thống xác thực và quản lý người dùng trở nên dễ dàng hơn. Với Next Auth, việc tích hợp các phương thức xác thực như OAuth, JWT, hoặc xác thực bằng email và mật khẩu (credentials) trở nên đơn giản và tiện lợi.

Các khó khăn khi sử dụng Next Auth

Tuy nhiên, việc sử dụng Next Auth cũng đôi khi gặp phải một số khó khăn. Đầu tiên, việc cấu hình và tùy chỉnh Next Auth có thể đòi hỏi một số kiến thức về xác thực và quản lý người dùng. Ngoài ra, việc xử lý lỗi và gỡ rối cũng có thể gặp phải một số thách thức.

Kết luận

Next.js 14 và Next Auth là hai công nghệ mạnh mẽ và hữu ích trong việc xây dựng ứng dụng web. Tuy nhiên, việc sử dụng Next Auth có thể gặp phải một số khó khăn, nhưng các lợi ích mà nó mang lại vẫn là đáng giá. Hi vọng những thông tin trên sẽ giúp bạn hiểu rõ hơn về việc sử dụng Next.js 14 và Next Auth trong phát triển ứng dụng của mình.

Bài viết có thể có sai sót trong quá trình soạn, nếu có góp ý về bài viết các bạn vui lòng bình luận ở bên dưới nhé. Cảm ơn các bạn đã theo dõi bài viết này.

A wibu guy who likes coding and creating blogs, YouTube, etc.