Hvordan implementere token-autentisering i Next.js ved hjelp av JWT-er

Token-autentisering er en populær strategi som brukes for å beskytte nett- og mobilapplikasjoner mot uautorisert tilgang. I Next.js kan du bruke autentiseringsfunksjonene levert av Next-auth.

Alternativt kan du velge å utvikle et tilpasset token-basert autentiseringssystem ved å bruke JSON Web Tokens (JWTs). Ved å gjøre det sikrer du at du har mer kontroll over autentiseringslogikken; i hovedsak tilpasse systemet til nøyaktig å matche kravene til prosjektet ditt.

Sett opp et Next.js-prosjekt

For å komme i gang, installer Next.js ved å kjøre kommandoen nedenfor på terminalen din.

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

Denne guiden vil bruke Next.js 13 som inkluderer appkatalogen.

Installer deretter disse avhengighetene i prosjektet ditt ved å bruke npm, Node Package Manager.

 npm install jose universal-cookie 

Jose er en JavaScript-modul som gir et sett med verktøy for å jobbe med JSON Web Tokens mens universal-informasjonskapsel avhengighet gir en enkel måte å jobbe med nettleserinformasjonskapsler i både klient- og server-sidemiljøer.

Opprett brukergrensesnittet for påloggingsskjemaet

Åpne src/app-katalogen, opprett en ny mappe og navngi den pålogging. Inne i denne mappen legger du til en ny page.js-fil og inkluderer koden nedenfor.

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

Koden ovenfor oppretter en funksjonskomponent for påloggingssiden som vil gjengi et enkelt påloggingsskjema på nettleseren slik at brukere kan skrive inn et brukernavn og et passord.

Use client-setningen i koden sikrer at en grense er deklarert mellom server-only og client-only-kode i app-katalogen.

I dette tilfellet brukes den til å erklære at koden på påloggingssiden, spesielt handleSubmit-funksjonen kjøres kun på klienten; ellers vil Next.js gi en feilmelding.

  Engangs e-postadresser (DEA) forklart på 5 minutter eller mindre

La oss nå definere koden for handleSubmit-funksjonen. Inne i den funksjonelle komponenten legger du til følgende kode.

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

For å administrere påloggingsautentiseringslogikken fanger denne funksjonen opp brukerlegitimasjonen fra påloggingsskjemaet. Den sender deretter en POST-forespørsel til et API-endepunkt som sender brukerdetaljene for verifisering.

Hvis legitimasjonen er gyldig, noe som indikerer at påloggingsprosessen var vellykket – API-en returnerer en suksessstatus i svaret. Behandlerfunksjonen vil da bruke Next.js sin ruter for å navigere brukeren til en spesifisert URL, i dette tilfellet den beskyttede ruten.

Definer Login API Endpoint

Opprett en ny mappe i src/app-katalogen og navngi den api. Inne i denne mappen legger du til en ny login/route.js-fil og inkluderer koden nedenfor.

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

Den primære oppgaven for denne APIen er å verifisere påloggingslegitimasjonen som sendes i POST-forespørslene ved hjelp av falske data.

Etter vellykket verifisering genererer den et kryptert JWT-token knyttet til de autentiserte brukerdetaljene. Til slutt sender den et vellykket svar til klienten, inkludert token i responsinformasjonskapslene; ellers returnerer den et feilstatussvar.

Implementer Token Verification Logic

Det første trinnet i token-autentisering er å generere tokenet etter en vellykket påloggingsprosess. Det neste trinnet er å implementere logikken for tokenverifisering.

I hovedsak vil du bruke jwtVerify-funksjonen som tilbys av Jose-modulen for å bekrefte JWT-tokenene som ble sendt med påfølgende HTTP-forespørsler.

  10 beste apper for boligkjøp/husjakt for 2023

I src-katalogen oppretter du en ny libs/auth.js-fil og inkluderer koden nedenfor.

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

Den hemmelige nøkkelen brukes til å signere og bekrefte tokens. Ved å sammenligne den dekodede tokensignaturen med den forventede signaturen, kan serveren effektivt verifisere at det angitte tokenet er gyldig, og til slutt autorisere brukernes forespørsler.

Opprett .env-fil i rotkatalogen og legg til en unik hemmelig nøkkel som følger:

 NEXT_PUBLIC_JWT_SECRET_KEY=your_secret_key 

Lag en beskyttet rute

Nå må du opprette en rute som bare autentiserte brukere kan få tilgang til. For å gjøre det, opprett en ny beskyttet/page.js-fil i src/app-katalogen. Inni denne filen legger du til følgende kode.

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

Opprett en krok for å administrere autentiseringstilstanden

Opprett en ny mappe i src-katalogen og navngi den kroker. Legg til en ny useAuth/index.js-fil i denne mappen og inkluder koden nedenfor.

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

Denne kroken administrerer autentiseringstilstanden på klientsiden. Den henter og verifiserer gyldigheten til JWT-tokenet som finnes i informasjonskapsler ved å bruke verifyJwtToken-funksjonen, og setter deretter de autentiserte brukerdetaljene til autentiseringstilstanden.

Ved å gjøre det lar det andre komponenter få tilgang til og bruke den autentiserte brukerens informasjon. Dette er viktig for scenarier som å lage UI-oppdateringer basert på autentiseringsstatus, lage påfølgende API-forespørsler eller gjengi forskjellig innhold basert på brukerroller.

  Hvordan få det til å fungere tipsbilk.net

I dette tilfellet vil du bruke kroken til å gjengi forskjellig innhold på hjemmeruten basert på autentiseringstilstanden til en bruker.

En alternativ tilnærming du kan vurdere er å håndtere statsadministrasjon ved å bruke Redux Toolkit eller bruke et statlig administrasjonsverktøy som Jotai. Denne tilnærmingen garanterer at komponenter kan få global tilgang til autentiseringstilstanden eller en hvilken som helst annen definert tilstand.

Gå videre og åpne app/page.js-filen, slett Next.js-koden og legg til følgende kode.

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

Koden ovenfor bruker useAuth-kroken for å administrere autentiseringstilstanden. Ved å gjøre det, gjengir den betinget en offentlig hjemmeside med en lenke til ruten for påloggingssiden når brukeren ikke er autentisert, og viser et avsnitt for en autentisert bruker.

Legg til en mellomvare for å håndheve autorisert tilgang til beskyttede ruter

Opprett en ny middleware.js-fil i src-katalogen, og legg til koden nedenfor.

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

Denne mellomvarekoden fungerer som en vakt. Den sjekker for å sikre at når brukere ønsker å få tilgang til beskyttede sider, er de autentisert og autorisert til å få tilgang til rutene, i tillegg til å omdirigere uautoriserte brukere til påloggingssiden.

Sikring av Next.js-applikasjoner

Token-autentisering er en effektiv sikkerhetsmekanisme. Det er imidlertid ikke den eneste tilgjengelige strategien for å beskytte applikasjonene dine mot uautorisert tilgang.

For å styrke applikasjoner mot det dynamiske cybersikkerhetslandskapet, er det viktig å ta i bruk en omfattende sikkerhetstilnærming som helhetlig adresserer potensielle sikkerhetshull og sårbarheter for å garantere grundig beskyttelse.