1. User Sessions සහ JWT (JSON Web Tokens)
පරිශීලකයෙක් සාර්ථකව login වූ පසු, ඔහු login වී සිටින බව browser එකට "මතක තබා" ගැනීමට ක්රමයක් අවශ්යයි. මේ සඳහා අපි **JWT (JSON Web Token)** භාවිතා කරමු. JWT යනු පරිශීලකයාගේ තොරතුරු (user ID වැනි) අඩංගු, ආරක්ෂිතව sign කරන ලද digital token එකකි.
මෙම token එක අපි browser එකේ **httpOnly cookie** එකක ගබඩා කරමු. `httpOnly` යනු cookie එක JavaScript මගින් කියවීමට නොහැකි වන පරිදි සකස් කිරීමයි. එමගින් XSS (Cross-Site Scripting) වැනි ප්රහාර වලින් අපේ app එක ආරක්ෂා වේ.
පළමුව, අවශ්ය libraries install කරගනිමු. Terminal එකේ පහත command එක run කරන්න.
2. Login API එකට Session Logic එකතු කිරීම
දැන් අපි Module 4 හි සෑදූ Login API එක (/api/auth/login/route.ts) update කර, සාර්ථක login එකකදී JWT එකක් සාදා cookie එකක් ලෙස සකස් කරමු.
➡️ .env.local File එක සෑදීම
JWT එක sign කිරීමට අපට රහස් යතුරක් (secret key) අවශ්යයි. එය project එකේ root එකේ .env.local නමින් file එකක් සාදා එහි ගබඩා කරමු. මෙම file එක කිසිවිටකත් Git වලට commit නොකරන්න.
➡️ /api/auth/login/route.ts යාවත්කාලීන කිරීම
ඔබේ login API file එක පහත පරිදි update කරන්න.
import { NextResponse } from 'next/server';
import db from '@/lib/db';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { cookies } from 'next/headers';
export async function POST(request: Request) {
try {
const { username, password } = await request.json();
const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username) as any;
if (!user) {
return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });
}
const isValid = await bcrypt.compare(password, user.password_hash);
if (!isValid) {
return NextResponse.json({ error: 'Invalid username or password' }, { status: 401 });
}
// --- Create JWT Token ---
const secret = process.env.JWT_SECRET;
if (!secret) {
throw new Error('JWT secret not found in environment variables.');
}
const token = jwt.sign({ userId: user.id, username: user.username }, secret, {
expiresIn: '1h', // Token expires in 1 hour
});
// --- Set token in httpOnly cookie ---
cookies().set('session_token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60, // 1 hour in seconds
path: '/',
});
return NextResponse.json({ message: 'Logged in successfully' }, { status: 200 });
} catch (error) {
console.error(error);
return NextResponse.json({ error: 'An error occurred' }, { status: 500 });
}
}
3. Next.js Middleware මගින් Routes ආරක්ෂා කිරීම
Middleware යනු request එකක් page එකකට ළඟා වීමට පෙර run වන කේතයකි. අපට මෙය භාවිතා කර user login වී ඇත්දැයි පරීක්ෂා කර, අවශ්ය නම් login පිටුවට redirect කළ හැක.
Project එකේ root directory එකේ (app folder එකට வெளியே) middleware.ts නමින් file එකක් සාදන්න.
එම middleware.ts file එකට පහත කේතය ඇතුළත් කරන්න.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const sessionToken = request.cookies.get('session_token')?.value;
const { pathname } = request.nextUrl;
// If user is not logged in and tries to access a protected route (e.g., /notes), redirect to login
if (!sessionToken && pathname.startsWith('/notes')) {
return NextResponse.redirect(new URL('/auth/login', request.url));
}
// If user is logged in and tries to access auth pages (login/register), redirect to notes
if (sessionToken && (pathname.startsWith('/auth/login') || pathname.startsWith('/auth/register'))) {
return NextResponse.redirect(new URL('/notes', request.url));
}
return NextResponse.next();
}
// See "Matching Paths" below to learn more
export const config = {
matcher: ['/notes/:path*', '/auth/login', '/auth/register'],
};
කේතය පැහැදිලි කිරීම:
config.matcher: Middleware එක run විය යුතු paths මෙහිදී define කරයි.request.cookies.get('session_token'): Request එකේ ඇති cookie එක ලබා ගනී.- Cookie එක නොමැතිව
/notesවෙත පිවිසීමට උත්සාහ කළහොත්,/auth/loginවෙත redirect කරයි. - Cookie එක ඇතිව
/auth/loginවෙත පිවිසීමට උත්සාහ කළහොත්,/notesවෙත redirect කරයි.
4. Navbar එක සහ Logout ක්රියාවලිය
අවසාන වශයෙන්, user login වී ඇත්නම් "Logout" button එකක්ද, එසේ නොමැති නම් "Login" සහ "Register" links ද පෙන්වන Navbar එකක් සහ logout වීමේ ක්රියාවලිය සකස් කරමු.
➡️ Logout API Route
app/api/auth/logout/route.ts යන path එකට file එකක් සාදා, cookie එක clear කරන කේතය ලියමු.
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
export async function POST() {
// Clear the session cookie
cookies().set('session_token', '', {
httpOnly: true,
expires: new Date(0), // Set expiry date to the past
path: '/',
});
return NextResponse.json({ message: 'Logged out successfully' });
}
➡️ Navbar Component එක
Logout වීමට, client-side component එකකින් API route එකට request එකක් යැවිය යුතුය. ඒ සඳහා components නමින් folder එකක් root එකේ සාදා, ඒ තුළ Navbar.tsx නමින් file එකක් සාදන්න.
'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import Cookies from 'js-cookie';
export default function Navbar() {
const router = useRouter();
const sessionToken = Cookies.get('session_token');
const handleLogout = async () => {
await fetch('/api/auth/logout', { method: 'POST' });
Cookies.remove('session_token');
router.push('/auth/login');
router.refresh(); // To reflect the changes immediately
};
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
<div className="container">
<Link className="navbar-brand" href="/">NotesApp</Link>
<div className="collapse navbar-collapse">
<ul className="navbar-nav ms-auto mb-2 mb-lg-0">
{sessionToken ? (
<li className="nav-item">
<button className="btn btn-link nav-link" onClick={handleLogout}>Logout</button>
</li>
) : (
<>
<li className="nav-item">
<Link className="nav-link" href="/auth/login">Login</Link>
</li>
<li className="nav-item">
<Link className="nav-link" href="/auth/register">Register</Link>
</li>
</>
)}
</ul>
</div>
</div>
</nav>
);
}
➡️ Navbar එක Layout එකට එකතු කිරීම
දැන්, app/layout.tsx file එක open කර, අප සෑදූ Navbar component එක <body> tag එක තුළට import කරමු.
import Navbar from '@/components/Navbar'; // Import the Navbar
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={...}>
<Navbar /> {/* Add the Navbar here */}
<main className="container pt-4">
{children}
</main>
</body>
</html>
);
}