Verifikacija putem tokena predstavlja popularan metod zaštite web i mobilnih aplikacija od neautorizovanog pristupa. U Next.js okruženju, možete iskoristiti funkcionalnosti za autentifikaciju koje pruža NextAuth biblioteka.
Alternativno rešenje je kreiranje sopstvenog sistema autentifikacije zasnovanog na tokenima koristeći JSON Web Tokens (JWT). Na ovaj način obezbeđujete veću kontrolu nad logikom autentifikacije, prilagođavajući sistem specifičnim zahtevima vašeg projekta.
Konfigurisanje Next.js projekta
Za početak, instalirajte Next.js pokretanjem sledeće komande u vašem terminalu:
npx create-next-app@latest next-auth-jwt --experimental-app
Ovaj vodič koristi Next.js verziju 13, koja uključuje direktorijum aplikacije.
Nakon toga, instalirajte neophodne zavisnosti u vaš projekat koristeći npm (Node Package Manager):
npm install jose universal-cookie
Jose je JavaScript modul koji nudi alatke za rad sa JSON Web tokenima, dok universal-cookie zavisnost omogućava jednostavno upravljanje kolačićima pregledača, kako na klijentskoj, tako i na serverskoj strani.
Izrada korisničkog interfejsa forme za prijavu
Otvorite direktorijum `src/app`, napravite novi folder i nazovite ga `login`. Unutar ovog foldera kreirajte novu datoteku `page.js` i dodajte sledeći kod:
"use client";
import { useRouter } from "next/navigation";export default function LoginPage() {
return (
<form onSubmit={handleSubmit}>
<label>
Korisničko ime:
<input type="text" name="username" />
</label>
<label>
Lozinka:
<input type="password" name="password" />
</label>
<button type="submit">Prijavi se</button>
</form>
);
}
Navedeni kod generiše funkcionalnu komponentu za stranicu za prijavu, prikazujući osnovnu formu za prijavu u pregledaču, gde korisnici mogu uneti svoje korisničko ime i lozinku.
Direktiva `“use client“` u kodu specificira granicu između koda koji se izvršava samo na serveru i koda koji se izvršava samo na klijentu unutar direktorijuma `app`.
U ovom slučaju, ona se koristi da označi da se kod na stranici za prijavu, a posebno funkcija `handleSubmit`, izvršava isključivo na klijentu. U suprotnom, Next.js bi prikazao grešku.
Sada definišimo kod za funkciju `handleSubmit`. Dodajte sledeći kod unutar funkcionalne komponente:
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("Prijava neuspešna");
}
};
Ova funkcija, koja upravlja logikom autentifikacije za prijavu, prikuplja korisničke podatke iz forme za prijavu. Potom šalje POST zahtev ka API krajnjoj tački, prenoseći korisničke podatke radi verifikacije.
Ukoliko su podaci ispravni, što ukazuje na uspešan proces prijave, API vraća status uspeha u odgovoru. Funkcija rukovalac će zatim koristiti Next.js ruter da preusmeri korisnika na određenu URL adresu, u ovom slučaju na zaštićenu rutu.
Definisanje API krajnje tačke za prijavu
Unutar direktorijuma `src/app`, kreirajte novi folder pod imenom `api`. Unutar tog foldera dodajte novu datoteku `login/route.js` i uključite sledeći kod:
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 });
}
Primarna uloga ovog API-ja je da verifikuje podatke za prijavu prosleđene u POST zahtevima koristeći fiktivne podatke.
Nakon uspešne verifikacije, generiše se šifrovani JWT token povezan sa podacima autentifikovanog korisnika. Zatim se šalje uspešan odgovor klijentu, uključujući token u kolačićima odgovora. U suprotnom, vraća se odgovor sa statusom greške.
Implementiranje logike za verifikaciju tokena
Početni korak u autentifikaciji tokena je generisanje tokena nakon uspešne prijave. Sledeći korak je implementiranje logike za verifikaciju tokena.
U suštini, koristićete funkciju `jwtVerify` koju pruža `jose` modul da proverite JWT tokene prosleđene sa narednim HTTP zahtevima.
U direktorijumu `src` napravite novu datoteku `libs/auth.js` i dodajte sledeći kod:
import { jwtVerify } from "jose";export function getJwtSecretKey() {
const secret = process.env.NEXT_PUBLIC_JWT_SECRET_KEY;
if (!secret) {
throw new Error("JWT Secret key nije pronađen");
}
return new TextEncoder().encode(secret);
}export async function verifyJwtToken(token) {
try {
const { payload } = await jwtVerify(token, getJwtSecretKey());
return payload;
} catch (error) {
return null;
}
}
Tajni ključ se koristi za potpisivanje i verifikaciju tokena. Upoređivanjem dekodiranog potpisa tokena sa očekivanim potpisom, server može efikasno proveriti da li je priloženi token validan i, kao rezultat toga, odobriti korisničke zahteve.
Kreirajte `.env` datoteku u glavnom direktorijumu i dodajte jedinstveni tajni ključ na sledeći način:
NEXT_PUBLIC_JWT_SECRET_KEY=vas_tajni_kljuc
Kreiranje zaštićene rute
Sada je potrebno kreirati rutu kojoj mogu pristupiti samo verifikovani korisnici. Da biste to uradili, kreirajte novu datoteku `protected/page.js` u direktorijumu `src/app`. Unutar ove datoteke dodajte sledeći kod:
export default function ProtectedPage() {
return <h1>Veoma zaštićena stranica</h1>;
}
Kreiranje hook-a za upravljanje stanjem autentifikacije
Napravite novi folder u `src` direktorijumu i nazovite ga `hooks`. Unutar ovog foldera dodajte novu datoteku `useAuth/index.js` i uključite sledeći kod:
"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;
}
Ovaj hook upravlja stanjem autentifikacije na klijentskoj strani. On preuzima i proverava validnost JWT tokena prisutnog u kolačićima pomoću funkcije `verifyJwtToken`, a zatim postavlja autentifikovane podatke o korisniku u stanje autentifikacije.
Na taj način omogućava drugim komponentama da pristupe i koriste informacije o autentifikovanom korisniku. Ovo je od suštinskog značaja u scenarijima poput pravljenja ažuriranja korisničkog interfejsa na osnovu statusa autentifikacije, daljih API zahteva ili prikazivanja različitog sadržaja u zavisnosti od korisničkih uloga.
U ovom slučaju, koristićete hook za prikazivanje različitog sadržaja na početnoj ruti u zavisnosti od statusa autentifikacije korisnika.
Alternativni pristup koji biste mogli razmotriti je upravljanje stanjem koristeći Redux Toolkit ili korišćenje alata za upravljanje stanjem kao što je Jotai. Ovakav pristup garantuje da komponente mogu dobiti globalni pristup stanju autentifikacije ili bilo kom drugom definisanom stanju.
Slobodno otvorite datoteku `app/page.js`, izbrišite generisani Next.js kod i dodajte sledeći kod:
"use client" ;import { useAuth } from "@/hooks/useAuth";
import Link from "next/link";
export default function Home() {
const auth = useAuth();
return <>
<h1>Javna početna stranica</h1>
<header>
<nav>
{auth ? (
<p>Prijavljeni ste</p>
) : (
<Link href="https://wilku.top/login">Prijavi se</Link>
)}
</nav>
</header>
</>
}
Gornji kod koristi `useAuth` hook za upravljanje stanjem autentifikacije. U zavisnosti od toga, prikazuje javnu početnu stranicu sa linkom ka stranici za prijavu kada korisnik nije autentifikovan i prikazuje pasus za autentifikovanog korisnika.
Dodavanje middleware-a za primenu autorizovanog pristupa zaštićenim rutama
U direktorijumu `src` kreirajte novu datoteku `middleware.js` i dodajte sledeći kod:
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*"] };
Ovaj middleware kod deluje kao čuvar. On proverava da li su korisnici, kada žele pristup zaštićenim stranicama, autentifikovani i ovlašćeni da im pristupe. Takođe, preusmerava neovlašćene korisnike na stranicu za prijavu.
Osiguranje Next.js aplikacija
Verifikacija putem tokena je efikasan sigurnosni mehanizam. Međutim, to nije jedina strategija dostupna za zaštitu vaših aplikacija od neautorizovanog pristupa.
Kako bi se ojačale aplikacije u odnosu na dinamički pejzaž sajber bezbednosti, važno je usvojiti sveobuhvatan pristup bezbednosti koji holistički rešava potencijalne sigurnosne rupe i ranjivosti, kako bi se garantovala temeljna zaštita.