קבל שליטה רבה יותר על לוגיקת האימות של אפליקציית Next.js שלך באמצעות הטמעת אימות מותאמת אישית מבוסס JWT.
אימות אסימון הוא אסטרטגיה פופולרית המשמשת להגנה על יישומי אינטרנט וניידים מפני גישה לא מורשית. ב-Next.js, אתה יכול להשתמש בתכונות האימות שמסופקות על ידי Next-auth.
לחלופין, אתה יכול לבחור לפתח מערכת אימות מבוססת אסימון מותאמת אישית באמצעות JSON Web Tokens (JWTs). על ידי כך, אתה מבטיח שיש לך יותר שליטה על לוגיקה של האימות; בעצם, התאמה אישית של המערכת כך שתתאים בדיוק לדרישות הפרויקט שלך.
הגדר פרויקט Next.js
כדי להתחיל, התקן את Next.js על ידי הפעלת הפקודה למטה בטרמינל שלך.
npx create-next-app@latest next-auth-jwt --experimental-app
מדריך זה ישתמש Next.js 13 הכולל את ספריית האפליקציה.
לאחר מכן, התקן את התלות הללו בפרויקט שלך באמצעות npm, מנהל חבילות הצומת.
npm install jose universal-cookie
חוזה הוא מודול JavaScript המספק קבוצה של כלי עזר לעבודה עם JSON Web Tokens תוך כדי עוגיה אוניברסלית תלות מספקת דרך פשוטה לעבוד עם קובצי Cookie של דפדפן הן בסביבת צד הלקוח והן בסביבת השרת.
אתה יכול למצוא את הקוד של הפרויקט הזה בזה מאגר GitHub.
צור את ממשק המשתמש של טופס התחברות
פתח את ה src/app ספרייה, צור תיקיה חדשה ושמה לה התחברות. בתוך תיקיה זו, הוסף חדש page.js קובץ וכלול את הקוד למטה.
"use client";
import { useRouter } from"next/navigation";
exportdefaultfunctionLoginPage() {
return (
הקוד שלמעלה יוצר רכיב פונקציונלי של דף הכניסה שיציג טופס התחברות פשוט בדפדפן כדי לאפשר למשתמשים להזין שם משתמש וסיסמה.
ה להשתמש בלקוח הצהרה בקוד מבטיחה שגבול יוכרז בין קוד שרת בלבד לקוד לקוח בלבד ב- אפליקציה מַדרִיך.
במקרה זה, הוא משמש כדי להצהיר שהקוד בדף הכניסה, במיוחד, ה handleSubmitהפונקציה מבוצעת רק על הלקוח; אחרת, Next.js יזרוק שגיאה.
כעת, בואו נגדיר את הקוד עבור ה handleSubmit פוּנקצִיָה. בתוך הרכיב הפונקציונלי, הוסף את הקוד הבא.
const router = useRouter();
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const username = formData.get("username");
const password = formData.get("password");
const res = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ username, password }),
});
const { success } = await res.json();
if (success) {
router.push("/protected");
router.refresh();
} else {
alert("Login failed");
}
};
כדי לנהל את לוגיקה של אימות הכניסה, פונקציה זו לוכדת את אישורי המשתמש מטופס הכניסה. לאחר מכן הוא שולח בקשת POST לנקודת קצה של API המעביר את פרטי המשתמש לאימות.
אם האישורים תקפים, המציין שתהליך הכניסה הצליח - ה-API מחזיר סטטוס הצלחה בתגובה. פונקציית המטפל תשתמש אז בנתב של Next.js כדי לנווט את המשתמש לכתובת URL שצוינה, במקרה זה, מוּגָן מַסלוּל.
הגדר את נקודת הקצה של Login API
בתוך ה src/app ספרייה, צור תיקיה חדשה ושמה לה API. בתוך תיקיה זו, הוסף חדש login/route.js קובץ וכלול את הקוד למטה.
import { SignJWT } from"jose";
import { NextResponse } from"next/server";
import { getJwtSecretKey } from"@/libs/auth";
exportasyncfunctionPOST(request) {
const body = await request.json();
if (body.username "admin" && body.password "admin") {
const token = awaitnew SignJWT({
username: body.username,
})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("30s")
.sign(getJwtSecretKey());
const response = NextResponse.json(
{ success: true },
{ status: 200, headers: { "content-type": "application/json" } }
);
response.cookies.set({
name: "token",
value: token,
path: "/",
});
return response;
}
return NextResponse.json({ success: false });
}
המשימה העיקרית עבור API זה היא לאמת את אישורי הכניסה שהועברו בבקשות ה-POST באמצעות נתונים מדומים.
לאחר אימות מוצלח, הוא יוצר אסימון JWT מוצפן המשויך לפרטי המשתמש המאומתים. לבסוף, הוא שולח תגובה מוצלחת ללקוח, כולל האסימון בעוגיות התגובה; אחרת, הוא מחזיר תגובת מצב כשל.
הטמעת לוגיקת אימות אסימון
השלב הראשוני באימות האסימון הוא יצירת האסימון לאחר תהליך כניסה מוצלח. השלב הבא הוא ליישם את ההיגיון לאימות אסימון.
בעיקרון, תשתמש ב- jwtVerify פונקציה שמסופקת על ידי חוזה מודול לאימות אסימוני JWT שהועברו עם בקשות HTTP עוקבות.
בתוך ה src ספרייה, צור חדש libs/auth.js קובץ וכלול את הקוד למטה.
import { jwtVerify } from"jose";
exportfunctiongetJwtSecretKey() {
const secret = process.env.NEXT_PUBLIC_JWT_SECRET_KEY;
if (!secret) {
thrownewError("JWT Secret key is not matched");
}
returnnew TextEncoder().encode(secret);
}
exportasyncfunctionverifyJwtToken(token) {
try {
const { payload } = await jwtVerify(token, getJwtSecretKey());
return payload;
} catch (error) {
returnnull;
}
}
המפתח הסודי משמש בחתימה ואימות האסימונים. על ידי השוואת חתימת האסימון המפוענח לחתימה הצפויה, השרת יכול למעשה לוודא שהאסימון שסופק תקף, ובסופו של דבר לאשר את בקשות המשתמשים.
לִיצוֹר .env קובץ בספריית השורש והוסף מפתח סודי ייחודי באופן הבא:
NEXT_PUBLIC_JWT_SECRET_KEY=your_secret_key
צור מסלול מוגן
כעת, עליך ליצור מסלול שרק משתמשים מאומתים יכולים לקבל גישה אליו. כדי לעשות זאת, צור חדש protected/page.js קובץ ב- src/app מַדרִיך. בתוך הקובץ הזה, הוסף את הקוד הבא.
exportdefaultfunctionProtectedPage() {
return<h1>Very protected pageh1>;
}
צור הוק לניהול מצב האימות
צור תיקיה חדשה ב- src ספרייה ושמות לה ווים. בתוך תיקיה זו הוסף חדש useAuth/index.js קובץ וכלול את הקוד למטה.
"use client" ;
import React from"react";
import Cookies from"universal-cookie";
import { verifyJwtToken } from"@/libs/auth";exportfunctionuseAuth() {
const [auth, setAuth] = React.useState(null);
const getVerifiedtoken = async () => {
const cookies = new Cookies();
const token = cookies.get("token")?? null;
const verifiedToken = await verifyJwtToken(token);
setAuth(verifiedToken);
};
React.useEffect(() => {
getVerifiedtoken();
}, []);
return auth;
}
וו זה מנהל את מצב האימות בצד הלקוח. הוא מאחזר ומאמת את תקפותו של אסימון JWT הקיים בקובצי Cookie באמצעות verifyJwtToken פונקציה, ולאחר מכן מגדיר את פרטי המשתמש המאומתים ל- אישור מדינה.
על ידי כך, הוא מאפשר לרכיבים אחרים לגשת למידע של המשתמש המאומת ולהשתמש בו. זה חיוני לתרחישים כמו ביצוע עדכוני ממשק משתמש על סמך סטטוס אימות, ביצוע בקשות API עוקבות או עיבוד תוכן שונה על סמך תפקידי משתמש.
במקרה זה, תשתמש ב-hook כדי להציג תוכן שונה ב- בית מסלול המבוסס על מצב האימות של משתמש.
גישה חלופית שאתה יכול לשקול היא טיפול ניהול מדינה באמצעות Redux Toolkit או העסקת א כלי ניהול מדינה כמו Jotai. גישה זו מבטיחה שרכיבים יכולים לקבל גישה גלובלית למצב האימות או לכל מצב מוגדר אחר.
קדימה ופתח את app/page.js קובץ, מחק את קוד Next.js של boilerplate, והוסף את הקוד הבא.
"use client" ;
import { useAuth } from"@/hooks/useAuth";
import Link from"next/link";
exportdefaultfunctionHome() {
const auth = useAuth();
return<>Public Home Page</h1>
הקוד לעיל משתמש ב- useAuth הוק לניהול מצב האימות. בכך, הוא מעבד דף בית ציבורי עם קישור ל- התחברות נתיב עמוד כאשר המשתמש אינו מאומת, ומציג פסקה עבור משתמש מאומת.
הוסף תוכנת ביניים כדי לאכוף גישה מורשית למסלולים מוגנים
בתוך ה src ספרייה, צור חדש middleware.js קובץ, והוסיפו את הקוד למטה.
import { NextResponse } from"next/server";
import { verifyJwtToken } from"@/libs/auth";const AUTH_PAGES = ["/login"];
const isAuthPages = (url) => AUTH_PAGES.some((page) => page.startsWith(url));
exportasyncfunctionmiddleware(request) {
const { url, nextUrl, cookies } = request;
const { value: token } = cookies.get("token")?? { value: null };
const hasVerifiedToken = token && (await verifyJwtToken(token));
const isAuthPageRequested = isAuthPages(nextUrl.pathname);if (isAuthPageRequested) {
if (!hasVerifiedToken) {
const response = NextResponse.next();
response.cookies.delete("token");
return response;
}
const response = NextResponse.redirect(new URL(`/`, url));
return response;
}if (!hasVerifiedToken) {
const searchParams = new URLSearchParams(nextUrl.searchParams);
searchParams.set("next", nextUrl.pathname);
const response = NextResponse.redirect(
new URL(`/login?${searchParams}`, url)
);
response.cookies.delete("token");
return response;
}return NextResponse.next();
}
exportconst config = { matcher: ["/login", "/protected/:path*"] };
קוד התווך הזה פועל כשומר. הוא בודק כדי לוודא שכאשר משתמשים רוצים לגשת לדפים מוגנים, הם מאומתים ומורשים לגשת למסלולים, בנוסף להפניה מחדש של משתמשים לא מורשים לדף ההתחברות.
אבטחת יישומי Next.js
אימות אסימון הוא מנגנון אבטחה יעיל. עם זאת, זו לא האסטרטגיה היחידה הזמינה כדי להגן על היישומים שלך מפני גישה לא מורשית.
כדי לחזק יישומים מול נוף אבטחת הסייבר הדינמי, חשוב לאמץ אבטחה מקיפה גישה המתייחסת באופן הוליסטי לפרצות אבטחה פוטנציאליות ופגיעויות כדי להבטיח ביסודיות הֲגָנָה.