• בלוג
  • מדריך Next.JS חלק 7 - הוספת משתמשים

מדריך Next.JS חלק 7 - הוספת משתמשים

16/12/2023

משתמשים הם חלק חשוב מכל אפליקציה מעניינת שנרצה לכתוב. השירות auth0 הוא דרך מעולה להוסיף תמיכה מהירה בחשבונות משתמשים לכל אפליקציה ויש להם אינטגרציה טובה גם עם next.js. בחלק זה נוסיף למערכת הפוסטים שכתבנו תמיכה במשתמשים כך שבאופן אוטומטי פוסטים יהיו שייכים רק למשתמשים שכתבו אותם.

1. יוצרים אפליקציה ב auth0

ניהול משתמשים דרך auth0 הוא קיצור דרך טוב כשרק מתחילים פיתוח. הם מספקים את כל מסכי החיבור והניתוק וגם חיבור דרך שירותי צד שלישי כמו "חיבור באמצעות גוגל". התוכנית החינמית מחזיקה עד 7,000 משתמשים ואחרי זה כבר תצטרכו לשלם.

נתחיל בפתיחת חשבון ב auth0 כאן: https://auth0.com

אחרי הרישום והכניסה נכנסים ל Applications ומוודאים שיש לכם אפליקציה בשם Default App. אם אין אפשר ליצור אחת. בלחיצה על שם האפליקציה אנחנו נכנסים למסך ההגדרות שלה ומשם אנחנו צריכים את: Domain, Client ID, Client Secret.

לאחר שהעתקנו את הערכים נגלול למטה לשדה Allowed Callback URLs ונוסיף שם את הערך:

http://localhost:3000/api/auth/callback

ומתחתיו בתיבה Allowed Logout URLs נוסיף את הערך:

http://localhost:3000

ממשיכים לגלול למטה ולוחצים על הכפתור Save Changes.

עכשיו נמשיך לאפליקציה ונתקן את הקוד כדי שאפשר יהיה להתחבר דרך auth0. תחילה נתקין את הספריה של auth0 עם:

npm install @auth0/nextjs-auth0

לאחר מכן בתיקיית src/app אני יוצר שלוש תיקיות חדשות אחת בתוך השניה בשם:

api/auth/[auth0]

וכן הסוגריים המרובעים הם חלק מהשם של התיקייה האחרונה. בתוך התיקיה הפנימית ביותר אני יוצר קובץ בשם route.ts עם התוכן הבא:

import { handleAuth } from '@auth0/nextjs-auth0';

export const GET = handleAuth();

לבסוף אני משנה את שם הקובץ .env שנוצר לי על ידי פריזמה באחד החלקים הקודמים של המדריך ל .env.local ומוסיף אליו 5 שורות:

AUTH0_SECRET='b3f5216b88d3740cf8457dd77e09eeeef5c31c6e678290155b4b75167ebdf537'
AUTH0_BASE_URL='http://localhost:3000'
AUTH0_ISSUER_BASE_URL='https://<paste here auth0 domain>'
AUTH0_CLIENT_ID='<paste here auth0 client id>'
AUTH0_CLIENT_SECRET='<paste here auth0 client secret>'

הערך של המשתנה הראשון הוא מחרוזת אקראית שיצרתי עם הפקודה:

openssl rand -hex 32

ובשלושת השורות האחרונות יש להחליף את מה שבתוך הסוגריים המשולשים בערכים האמיתיים שהופיעו אצלכם בהגדרות היישום ב auth0.

הקובץ האחרון שצריך לשנות הוא layout.tsx ובו עלינו לעטוף את כל הקוד שלנו בקומפוננטת UserProvider של auth0. אחרי השינוי הקובץ נראה כך:

import type { Metadata } from 'next'
import { UserProvider } from '@auth0/nextjs-auth0/client';

import { Inter } from 'next/font/google'
import TopMenu from './servermenu';
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <UserProvider>
        <body className={inter.className}>
          <TopMenu />
          {children}
        </body>
      </UserProvider>
    </html>
  )
}

2. קומפוננטת צד-לקוח עם פרטי משתמש

בשביל לוודא שהקוד עובד נרצה ליצור כמה קומפוננטות שישתמשו בפרטי המשתמש. תחילה אני יוצר תיקיה בשם src/app/users ובתוכה קובץ בשם page.tsx עם התוכן הבא:

import ClientUser from './client_user';
import ServerUser from './server_user';

export default () => {
  return (
    <div>
      <h1>User Page</h1>
      <p>
        <a href="/api/auth/login">Login</a>
      </p>
      <p>
        <a href="/api/auth/logout">Logout</a>
      </p>
      <hr />
      <ClientUser />
      <hr />
      <ServerUser />
    </div>
  )
}

לאחר מכן אני יוצר לידו באותה תיקיה קובץ בשם client_user.tsx עם התוכן הבא:

'use client';

import { useUser } from '@auth0/nextjs-auth0/client';

export default function ProfileClient() {
  const { user, error, isLoading } = useUser();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>{error.message}</div>;

  return (
    user && (
      <div>
        <h1>User Info from Client Component</h1>
        <img src={user.picture} alt={user.name} />
        <h2>{user.name}</h2>
        <p>{user.email}</p>
      </div>
    )
  );
}

וקובץ נוסף בשם server_user.tsx עם התוכן הבא:

import { getSession } from '@auth0/nextjs-auth0';

export default async function ProfileServer() {
  const session = await getSession();

  if (session) {
    const user = session.user;
    return (
      user && (
          <div>
            <h1>User Info from Server Component</h1>
            <img src={user.picture} alt={user.name} />
            <h2>{user.name}</h2>
            <p>{user.email}</p>
          </div>
      )
  )
  } else {
    return (
      <p>Please log in to see your info</p>
    )
  }
}

מאחר והתפריט העליון שלי נוצר דינמית המסך החדש אוטומטית יופיע ברשימת הדפים ואפשר להיכנס אליו כדי להתחבר, להתנתק ולראות את פרטי המשתמש.

3. איך זה עובד

הפונקציות החשובות ב API של auth0 הן:

  1. בצד השרת אני יכול לקרוא ל getSession כדי לקבל את פרטי המשתמש שכרגע מחובר, או null אם המשתמש אינו מחובר.

  2. בצד הלקוח אני יכול לקרוא ל useUser שהוא Hook שמחזיר את פרטי המשתמש שכרגע מחובר או שגיאה אם המשתמש אינו מחובר.

  3. הפניה לנתיב /api/auth/login תגיע אוטומטית לקוד טיפול של auth0, כמו גם פניה לנתיב /api/auth/logout ובעצם לכל נתיב שמתחיל ב api/auth. זה קורה בגלל הסוגריים המרובעים בשם התיקייה שיצרנו. סוגריים מרובעים מציינים נתיב עם פרמטר, כך שהמילה שאחרי ה auth היא הפרמטר לפונקציית הטיפול. הפונקציה עצמה handleAuth מוגדרת בספריה של auth0 והיא שולחת אוטומטית את המשתמש לנתיב המתאים על auth0 עם הפרמטרים המתאימים.

כאן תהיה נקודה טובה לשחק עם הקוד ולראות שאתם מצליחים להתחבר ולראות את פרטי המשתמש שלכם.

4. שמירת המשתמש בתור כותב הפוסט במסך הפוסטים

אחרי בניית המנגנון אפשר לחבר אותו למנגנון הפוסטים שכבר יש לנו ביישום. אני מעדכן את הקובץ db/posts.ts כך שימשוך את פרטי המשתמש מ auth0 במקום לקבל אותם כפרמטר:

"use server"

import { PrismaClient } from '@prisma/client'
import { getSession } from '@auth0/nextjs-auth0';
const prisma = new PrismaClient()

export async function createPost(text: string) {
  const session = await getSession();
  const author = session?.user?.name || "[Guest User]";

  const res = await prisma.post.create({
    data: {
      author,
      text,
    }
  })

  return res;
}

ואז משנה את הקובץ newpost.tsx כך שלא ישלח את ה author וגם לא יציג את תיבת הטקסט שלו:

"use client";
import { useRef } from 'react';
import { useRouter } from 'next/navigation';
import { createPost
 } from "../db/posts"

export default () => {
  const router = useRouter();
  const textFieldRef = useRef<HTMLInputElement>(null);

  async function handleCreate(formData: FormData) {
    const text = formData.get('text') as string;
    const newPost = await createPost(text);
    console.log(newPost);
    if (textFieldRef.current) {
      textFieldRef.current.value = '';
    }
    router.refresh();
  }

  return (
    <form action={handleCreate}>
      <label>
        Text: 
        <input type="text" name="text" className="text-black" ref={textFieldRef} />
      </label>
      <input type="submit" value="Create" />
    </form>
  )
}

אחרי שנכנס שוב לעמוד וניצור כמה פוסטים נוכל לראות שאם אנחנו מחוברים הפוסטים נוצרים עם שם המשתמש שלנו, ואם אנחנו לא מחוברים הם נוצרים עם שם משתמש Guest User.

בחלק הבא והאחרון של המדריך נראה איך להעלות את היישום שלנו לשרת באינטרנט כדי לשתף אותו עם חברים ומשפחה.