Tokenová autentizace je oblíbená strategie používaná při ochraně webových a mobilních aplikací před neoprávněným přístupem. V Next.js můžete využít autentizační funkce poskytované Next-auth.
Případně se můžete rozhodnout vyvinout vlastní ověřovací systém založený na tokenech pomocí webových tokenů JSON (JWT). Tím zajistíte, že budete mít větší kontrolu nad logikou ověřování; v podstatě přizpůsobení systému tak, aby přesně odpovídal požadavkům vašeho projektu.
Table of Contents
Nastavte projekt Next.js
Chcete-li začít, nainstalujte Next.js spuštěním příkazu níže na vašem terminálu.
npx create-next-app@latest next-auth-jwt --experimental-app
Tato příručka bude využívat Next.js 13, který obsahuje adresář aplikace.
Dále nainstalujte tyto závislosti do svého projektu pomocí npm, Správce balíčků uzlů.
npm install jose universal-cookie
Jose je modul JavaScriptu, který poskytuje sadu nástrojů pro práci s webovými tokeny JSON, zatímco univerzální soubor cookie závislost poskytuje jednoduchý způsob práce se soubory cookie prohlížeče v prostředí na straně klienta i na straně serveru.
Vytvořte uživatelské rozhraní přihlašovacího formuláře
Otevřete adresář src/app, vytvořte novou složku a pojmenujte ji login. Do této složky přidejte nový soubor page.js a zahrňte níže uvedený kód.
"use client";
import { useRouter } from "next/navigation";export default function LoginPage() {
return (
<form onSubmit={handleSubmit}>
<label>
Username:
<input type="text" name="username" />
</label>
<label>
Password:
<input type="password" name="password" />
</label>
<button type="submit">Login</button>
</form>
);
}
Výše uvedený kód vytvoří funkční komponentu přihlašovací stránky, která v prohlížeči vykreslí jednoduchý přihlašovací formulář, který uživatelům umožní zadat uživatelské jméno a heslo.
Příkaz use client v kódu zajišťuje, že je deklarována hranice mezi kódem pouze pro server a pouze pro klienta v adresáři aplikace.
V tomto případě se používá k deklaraci, že kód na přihlašovací stránce, zejména funkce handleSubmit, se provádí pouze na klientovi; jinak Next.js vyvolá chybu.
Nyní definujeme kód pro funkci handleSubmit. Do funkční součásti přidejte následující kód.
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");
}
};
Pro správu logiky ověřování přihlášení tato funkce zachycuje přihlašovací údaje uživatele z přihlašovacího formuláře. Poté odešle požadavek POST na koncový bod API, který předá údaje o uživateli k ověření.
Pokud jsou pověření platná, což znamená, že proces přihlášení byl úspěšný – rozhraní API vrátí v odpovědi stav úspěchu. Funkce manipulátoru pak použije směrovač Next.js k navigaci uživatele na zadanou adresu URL, v tomto případě na chráněnou cestu.
Definujte koncový bod Login API
V adresáři src/app vytvořte novou složku a pojmenujte ji api. Do této složky přidejte nový soubor login/route.js a vložte níže uvedený kód.
import { SignJWT } from "jose";
import { NextResponse } from "next/server";
import { getJwtSecretKey } from "@/libs/auth";export async function POST(request) {
const body = await request.json();
if (body.username === "admin" && body.password === "admin") {
const token = await new 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: "https://www.makeuseof.com/",
});
return response;
}
return NextResponse.json({ success: false });
}
Primárním úkolem tohoto rozhraní API je ověření přihlašovacích údajů předávaných v požadavcích POST pomocí falešných dat.
Po úspěšném ověření vygeneruje zašifrovaný token JWT spojený s podrobnostmi ověřeného uživatele. Nakonec odešle klientovi úspěšnou odpověď, včetně tokenu v souborech cookie odpovědi; jinak vrátí odpověď na stav selhání.
Implementujte logiku ověření tokenu
Prvním krokem v autentizaci tokenu je vygenerování tokenu po úspěšném přihlášení. Dalším krokem je implementace logiky pro ověření tokenu.
V zásadě budete používat funkci jwtVerify poskytovanou modulem Jose k ověření tokenů JWT předávaných s následnými požadavky HTTP.
V adresáři src vytvořte nový soubor libs/auth.js a zahrňte níže uvedený kód.
import { jwtVerify } from "jose";export function getJwtSecretKey() {
const secret = process.env.NEXT_PUBLIC_JWT_SECRET_KEY;
if (!secret) {
throw new Error("JWT Secret key is not matched");
}
return new TextEncoder().encode(secret);
}export async function verifyJwtToken(token) {
try {
const { payload } = await jwtVerify(token, getJwtSecretKey());
return payload;
} catch (error) {
return null;
}
}
Tajný klíč se používá při podepisování a ověřování tokenů. Porovnáním dekódovaného podpisu tokenu s očekávaným podpisem může server efektivně ověřit, zda je poskytnutý token platný, a nakonec autorizovat požadavky uživatelů.
Vytvořte soubor .env v kořenovém adresáři a přidejte jedinečný tajný klíč následovně:
NEXT_PUBLIC_JWT_SECRET_KEY=your_secret_key
Vytvořte chráněnou trasu
Nyní musíte vytvořit trasu, ke které budou mít přístup pouze ověření uživatelé. Chcete-li tak učinit, vytvořte nový soubor protected/page.js v adresáři src/app. Do tohoto souboru přidejte následující kód.
export default function ProtectedPage() {
return <h1>Very protected page</h1>;
}
Vytvořte Hook pro správu stavu ověřování
Vytvořte novou složku v adresáři src a pojmenujte ji hooks. Do této složky přidejte nový soubor useAuth/index.js a přidejte níže uvedený kód.
"use client" ;
import React from "react";
import Cookies from "universal-cookie";
import { verifyJwtToken } from "@/libs/auth";export function useAuth() {
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;
}
Tento hák spravuje stav ověřování na straně klienta. Načte a ověří platnost tokenu JWT přítomného v souborech cookie pomocí funkce ověřeníJwtToken a poté nastaví podrobnosti o ověřeném uživateli do stavu auth.
Tím umožňuje ostatním komponentům přístup k informacím ověřeného uživatele a jejich využití. To je nezbytné pro scénáře, jako je provádění aktualizací uživatelského rozhraní na základě stavu ověření, provádění následných požadavků API nebo vykreslování různého obsahu na základě uživatelských rolí.
V tomto případě použijete hák k vykreslení různého obsahu na domovské trase na základě stavu ověření uživatele.
Alternativním přístupem, který byste mohli zvážit, je řízení stavu pomocí nástroje Redux Toolkit nebo použití nástroje pro řízení stavu, jako je Jotai. Tento přístup zaručuje, že komponenty mohou získat globální přístup ke stavu autentizace nebo jinému definovanému stavu.
Pokračujte a otevřete soubor app/page.js, odstraňte standardní kód Next.js a přidejte následující kód.
"use client" ;import { useAuth } from "@/hooks/useAuth";
import Link from "next/link";
export default function Home() {
const auth = useAuth();
return <>
<h1>Public Home Page</h1>
<header>
<nav>
{auth ? (
<p>logged in</p>
) : (
<Link href="https://wilku.top/login">Login</Link>
)}
</nav>
</header>
</>
}
Výše uvedený kód využívá háček useAuth ke správě stavu ověřování. Přitom podmíněně vykreslí veřejnou domovskou stránku s odkazem na trasu přihlašovací stránky, když uživatel není ověřen, a zobrazí odstavec pro ověřeného uživatele.
Přidejte middleware k vynucení autorizovaného přístupu k chráněným trasám
V adresáři src vytvořte nový soubor middleware.js a přidejte níže uvedený kód.
import { NextResponse } from "next/server";
import { verifyJwtToken } from "@/libs/auth";const AUTH_PAGES = ["https://wilku.top/login"];
const isAuthPages = (url) => AUTH_PAGES.some((page) => page.startsWith(url));
export async function middleware(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();
}
export const config = { matcher: ["https://wilku.top/login", "/protected/:path*"] };
Tento middlewarový kód funguje jako stráž. Kontroluje, že když uživatelé chtějí přistupovat na chráněné stránky, jsou autentizováni a autorizováni pro přístup k trasám, a navíc přesměrovává neoprávněné uživatele na přihlašovací stránku.
Zabezpečení aplikací Next.js
Autentizace pomocí tokenu je účinný bezpečnostní mechanismus. Není to však jediná dostupná strategie pro ochranu vašich aplikací před neoprávněným přístupem.
Pro posílení aplikací proti dynamickému prostředí kybernetické bezpečnosti je důležité přijmout komplexní bezpečnostní přístup, který holisticky řeší potenciální bezpečnostní mezery a zranitelnosti, aby byla zaručena důkladná ochrana.