Cum să implementați autentificarea cu token în Next.js folosind JWT

Autentificarea cu simboluri este o strategie populară folosită pentru a proteja aplicațiile web și mobile împotriva accesului neautorizat. În Next.js, puteți utiliza funcțiile de autentificare oferite de Next-auth.

Alternativ, puteți opta pentru a dezvolta un sistem de autentificare personalizat bazat pe token-uri folosind JSON Web Tokens (JWT-uri). Procedând astfel, vă asigurați că aveți mai mult control asupra logicii de autentificare; în esență, personalizarea sistemului pentru a se potrivi exact cerințelor proiectului dumneavoastră.

Configurați un proiect Next.js

Pentru a începe, instalați Next.js rulând comanda de mai jos pe terminalul dvs.

 npx create-next-app@latest next-auth-jwt --experimental-app 

Acest ghid va utiliza Next.js 13, care include directorul aplicației.

Apoi, instalați aceste dependențe în proiectul dvs. folosind npm, Managerul de pachete Node.

 npm install jose universal-cookie 

Jose este un modul JavaScript care oferă un set de utilități pentru lucrul cu jetoane web JSON în timp ce universal-cookie dependența oferă o modalitate simplă de a lucra cu module cookie de browser atât în ​​mediul client, cât și pe cel server.

Creați interfața de utilizator pentru formularul de conectare

Deschideți directorul src/app, creați un folder nou și denumiți-l login. În acest folder, adăugați un nou fișier page.js și includeți codul de mai jos.

 "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>
  );
}

Codul de mai sus creează o componentă funcțională a paginii de conectare care va afișa un simplu formular de conectare în browser pentru a permite utilizatorilor să introducă un nume de utilizator și o parolă.

Declarația de utilizare a clientului din cod asigură că este declarată o limită între codul doar pentru server și codul numai pentru client în directorul aplicației.

În acest caz, este folosit pentru a declara că codul din pagina de autentificare, în special, funcția handleSubmit este executată doar pe client; în caz contrar, Next.js va arunca o eroare.

  Cum să vă conectați la un server SFTP în Ubuntu

Acum, să definim codul pentru funcția handleSubmit. În interiorul componentei funcționale, adăugați următorul cod.

 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");
    }
 };

Pentru a gestiona logica de autentificare, această funcție captează acreditările utilizatorului din formularul de conectare. Apoi trimite o solicitare POST către un punct final API care transmite detaliile utilizatorului pentru verificare.

Dacă acreditările sunt valide, indicând că procesul de conectare a avut succes — API-ul returnează o stare de succes în răspuns. Funcția de gestionare va folosi apoi routerul Next.js pentru a naviga pe utilizator la o adresă URL specificată, în acest caz, ruta protejată.

Definiți punctul final al API-ului de conectare

În directorul src/app, creați un folder nou și denumiți-l api. În acest folder, adăugați un nou fișier login/route.js și includeți codul de mai jos.

 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 });
}

Sarcina principală pentru acest API este să verifice acreditările de conectare transmise în solicitările POST folosind date simulate.

După verificarea cu succes, generează un token JWT criptat asociat cu detaliile utilizatorului autentificat. În cele din urmă, trimite un răspuns de succes clientului, inclusiv token-ul în cookie-urile de răspuns; în caz contrar, returnează un răspuns de stare de eșec.

Implementați logica de verificare a simbolurilor

Pasul inițial în autentificarea jetonului este generarea jetonului după un proces de conectare cu succes. Următorul pas este implementarea logicii pentru verificarea simbolului.

  Delimitați datele coloanei folosind spațiu, linie nouă, două puncte, punct și punct

În esență, veți folosi funcția jwtVerify furnizată de modulul Jose pentru a verifica jetoanele JWT transmise cu solicitările HTTP ulterioare.

În directorul src, creați un nou fișier libs/auth.js și includeți codul de mai jos.

 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;
  }
}

Cheia secretă este utilizată în semnarea și verificarea jetoanelor. Comparând semnătura simbolului decodificat cu semnătura așteptată, serverul poate verifica în mod eficient dacă simbolul furnizat este valid și, în cele din urmă, poate autoriza solicitările utilizatorilor.

Creați fișierul .env în directorul rădăcină și adăugați o cheie secretă unică, după cum urmează:

 NEXT_PUBLIC_JWT_SECRET_KEY=your_secret_key 

Creați o rută protejată

Acum, trebuie să creați o rută la care numai utilizatorii autentificați pot avea acces. Pentru a face acest lucru, creați un nou fișier protected/page.js în directorul src/app. În interiorul acestui fișier, adăugați următorul cod.

 export default function ProtectedPage() {
    return <h1>Very protected page</h1>;
  }

Creați un cârlig pentru a gestiona starea de autentificare

Creați un folder nou în directorul src și denumiți-l hooks. În acest folder, adăugați un nou fișier useAuth/index.js și includeți codul de mai jos.

 "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;
}

Acest cârlig gestionează starea de autentificare pe partea clientului. Preia și verifică validitatea simbolului JWT prezent în cookie-uri folosind funcția verifyJwtToken și apoi setează detaliile utilizatorului autentificat la starea de autentificare.

Procedând astfel, permite altor componente să acceseze și să utilizeze informațiile utilizatorului autentificat. Acest lucru este esențial pentru scenarii precum realizarea de actualizări ale interfeței de utilizare pe baza stării de autentificare, efectuarea de solicitări API ulterioare sau redarea conținutului diferit în funcție de rolurile utilizatorului.

  Cum să faci un Mood Board?

În acest caz, veți folosi cârligul pentru a reda conținut diferit pe ruta de acasă, în funcție de starea de autentificare a unui utilizator.

O abordare alternativă pe care o puteți lua în considerare este gestionarea managementului de stat folosind Redux Toolkit sau folosirea unui instrument de management de stat precum Jotai. Această abordare garantează că componentele pot obține acces global la starea de autentificare sau la orice altă stare definită.

Continuați și deschideți fișierul app/page.js, ștergeți codul boilerplate Next.js și adăugați următorul cod.

 "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>
  </>
}

Codul de mai sus utilizează cârligul useAuth pentru a gestiona starea de autentificare. Făcând acest lucru, redă condiționat o pagină de pornire publică cu un link către ruta paginii de conectare atunci când utilizatorul nu este autentificat și afișează un paragraf pentru un utilizator autentificat.

Adăugați un middleware pentru a impune accesul autorizat la rutele protejate

În directorul src, creați un nou fișier middleware.js și adăugați codul de mai jos.

 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*"] };

Acest cod middleware acționează ca un gardian. Acesta verifică pentru a se asigura că atunci când utilizatorii doresc să acceseze paginile protejate, aceștia sunt autentificați și autorizați să acceseze rutele, în plus față de redirecționarea utilizatorilor neautorizați către pagina de autentificare.

Securizarea aplicațiilor Next.js

Autentificarea prin token este un mecanism de securitate eficient. Cu toate acestea, nu este singura strategie disponibilă pentru a vă proteja aplicațiile împotriva accesului neautorizat.

Pentru a consolida aplicațiile împotriva peisajului dinamic al securității cibernetice, este important să adoptați o abordare cuprinzătoare de securitate care să abordeze în mod holistic lacunele și vulnerabilitățile potențiale de securitate pentru a garanta o protecție completă.