Token-autentisering er en utbredt metode for å sikre nett- og mobilapplikasjoner mot uautorisert tilgang. I Next.js kan man benytte seg av autentiseringsfunksjoner fra Next-auth.
Alternativt kan man utvikle et skreddersydd tokenbasert autentiseringssystem ved hjelp av JSON Web Tokens (JWT). Dette gir større kontroll over autentiseringslogikken og muliggjør tilpasning av systemet for å møte spesifikke prosjektbehov.
Oppsett av et Next.js-prosjekt
For å starte, installer Next.js ved å bruke følgende kommando i terminalen:
npx create-next-app@latest next-auth-jwt --experimental-app
Denne veiledningen bruker Next.js 13, som inkluderer app-katalogen.
Installer deretter nødvendige avhengigheter i prosjektet ved hjelp av npm (Node Package Manager):
npm install jose universal-cookie
Jose er et JavaScript-bibliotek som tilbyr verktøy for å håndtere JSON Web Tokens, mens universal-cookie forenkler bruken av nettleserkapsler i både klient- og servermiljøer.
Opprettelse av brukergrensesnitt for påloggingsskjema
I src/app-katalogen, opprett en ny mappe kalt «login». Inne i denne mappen, lag en fil kalt «page.js» og legg til følgende kode:
"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 genererer en funksjonskomponent for påloggingssiden som viser et enkelt påloggingsskjema der brukere kan skrive inn brukernavn og passord.
Uttrykket «use client» sikrer at det er en tydelig grense mellom kode som kjører på serveren og kode som kjører i nettleseren. I dette tilfellet angir det at koden på påloggingssiden, spesielt «handleSubmit»-funksjonen, bare skal kjøres i nettleseren, ellers vil Next.js generere en feil.
La oss definere koden for «handleSubmit»-funksjonen. Legg til følgende kode inne i den funksjonelle komponenten:
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"); } };
Denne funksjonen håndterer autentiseringslogikken ved å hente brukerens påloggingsinformasjon fra skjemaet. Den sender en POST-forespørsel til et API-endepunkt for å validere brukerdataene. Hvis påloggingsinformasjonen er gyldig, returnerer API-et en bekreftelse. Da vil funksjonen bruke Next.js sin ruter for å omdirigere brukeren til den beskyttede ruten.
Definere Login API-endepunkt
Opprett en ny mappe i src/app-katalogen og kall den «api». Inne i denne mappen, opprett en fil kalt «login/route.js» og legg til følgende kode:
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 }); }
Denne API-en verifiserer påloggingsinformasjon sendt i POST-forespørsler ved hjelp av mock-data. Ved godkjent verifikasjon genereres en kryptert JWT-token, og den autentiserte brukerens detaljer kobles til denne. API-et sender deretter et svar som bekrefter vellykket autentisering, inkludert token i response-kapslene. Hvis påloggingsinformasjonen ikke er godkjent, returneres en feilmelding.
Implementere tokenverifiseringslogikk
Det første trinnet i tokenautentisering er å generere tokenet etter en vellykket pålogging. Neste skritt er å implementere logikken for tokenverifisering.
Kort sagt, du vil bruke funksjonen jwtVerify fra Jose-modulen for å verifisere JWT-tokenene som sendes med påfølgende HTTP-forespørsler.
I src-katalogen, lag en ny fil kalt «libs/auth.js» og legg til følgende kode:
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 verifisere tokens. Ved å sammenligne den dekodede tokensignaturen med den forventede signaturen, kan serveren effektivt verifisere at det angitte tokenet er gyldig, og dermed autorisere brukerforespørsler.
Opprett en .env-fil i rotkatalogen og legg til en unik hemmelig nøkkel som følger:
NEXT_PUBLIC_JWT_SECRET_KEY=din_hemmelige_nøkkel
Lag en beskyttet rute
Nå må du opprette en rute som kun autentiserte brukere kan få tilgang til. For å gjøre det, opprett en ny fil «protected/page.js» i src/app-katalogen. Legg til følgende kode i denne filen:
export default function ProtectedPage() { return <h1>Meget beskyttet side</h1>; }
Opprett en hook for å administrere autentiseringstilstanden
Opprett en ny mappe i src-katalogen og gi den navnet «hooks». Legg til en ny fil «useAuth/index.js» i denne mappen og legg til følgende kode:
"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 hooken administrerer autentiseringstilstanden på klientsiden. Den henter og validerer gyldigheten til JWT-tokenet som er lagret i kapsler ved hjelp av verifyJwtToken-funksjonen og setter deretter de autentiserte brukerdetaljene til autentiseringstilstanden.
Dette gir andre komponenter tilgang til og bruke den autentiserte brukerens informasjon. Dette er nyttig i situasjoner som krever UI-oppdateringer basert på autentiseringsstatus, påfølgende API-forespørsler eller visning av forskjellig innhold basert på brukerroller.
I dette eksemplet vil du bruke hooken for å vise forskjellig innhold på hjemmesiden avhengig av en brukers autentiseringstilstand.
En alternativ tilnærming er å håndtere tilstandsadministrasjon ved hjelp av Redux Toolkit eller lignende statshåndteringsverktøy som Jotai. Dette sikrer at komponenter har global tilgang til autentiseringstilstanden eller andre definerte tilstander.
Åpne filen app/page.js, 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>Offentlig hjemmeside</h1> <header> <nav> {auth ? ( <p>Logget inn</p> ) : ( <Link href="https://wilku.top/login">Logg inn</Link> )} </nav> </header> </>; }
Koden over bruker «useAuth» hooken for å administrere autentiseringstilstanden. Den viser en offentlig hjemmeside med en lenke til påloggingssiden når brukeren ikke er logget inn, og viser et avsnitt for autentiserte brukere.
Legg til en middleware for å tvinge autorisert tilgang til beskyttede ruter
Opprett en ny fil «middleware.js» i src-katalogen og legg til følgende kode:
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 kontrollerer at brukere som forsøker å få tilgang til beskyttede sider er autentisert og har tillatelse. Den sørger også for at uautoriserte brukere blir omdirigert til påloggingssiden.
Sikring av Next.js-applikasjoner
Token-autentisering er en effektiv sikkerhetsmekanisme. Det er likevel ikke den eneste strategien for å beskytte applikasjonene dine mot uautorisert tilgang.
For å styrke applikasjoner mot et dynamisk cybersikkerhetsmiljø, er det viktig å ha en omfattende tilnærming til sikkerhet. Dette innebærer å håndtere sikkerhetshull og sårbarheter for å garantere grundig beskyttelse.