Lewati ke konten

Perbaikan route NextAuth

Impor lengkap, sesi JWT yang benar, token tetap di server.

Berkas: alat/hari-3/Praktik 10/perbaikan/route.js

Praktik 10/perbaikan/route.js
// =====================================================================
// Praktik 10 - versi perbaikan route.js NextAuth
//
// Bug yang diperbaiki (ditemukan oleh tools/audit-api.mjs):
//
// [KRITIS] route.js:60 - "verifyAccessToken" dipakai tanpa di-import.
// Berkas versi modul mengimpor hanya verifyCredentials dan signAccessToken:
// import { verifyCredentials } from ".../verifyCredentials";
// import { signAccessToken } from ".../jwt";
// tetapi memanggil verifyAccessToken(token.accessToken) di dalam callback
// jwt(). Akibatnya setiap permintaan sesi melempar ReferenceError,
// tertangkap oleh try/catch, dan accessToken dibuat ulang pada SETIAP
// permintaan. Login tetap "berhasil", jadi bug ini tidak terlihat sampai
// ada yang memperhatikan bahwa token terus diperbarui.
//
// Masalah lain yang diperbaiki:
//
// 2. session.user.user_id tidak pernah terisi. Versi modul menulis
// `token.id = user.user_id` saat login, lalu di callback session membaca
// `session.user.user_id = token.user_id` — padahal token.user_id tidak
// pernah diisi. Akibatnya session.user.user_id selalu undefined.
// Di sini keduanya diisi.
//
// 3. accessToken tidak lagi ikut ke session. Nilai yang dikembalikan
// callback session ikut terkirim ke browser (bisa dibaca lewat
// /api/auth/session), sehingga bearer token aplikasi menjadi dapat
// dibaca JavaScript di halaman. Simpan di token JWT NextAuth yang
// terenkripsi, dan sediakan endpoint terpisah bila klien benar-benar
// membutuhkannya.
//
// 4. Ada penjagaan bila verifyAccessToken gagal karena sebab lain, dan
// token yang dibuat ulang diberi umur yang sama seperti sebelumnya.
//
// Letakkan berkas ini di: src/app/api/auth/[...nextauth]/route.js
// =====================================================================
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { verifyCredentials } from "../../../../../lib/auth/verifyCredentials";
import { signAccessToken, verifyAccessToken } from "../../../../../lib/auth/jwt";
export const authOptions = {
providers: [
Credentials({
id: "geoportal-credential",
name: "geoportal-credential",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
authorize: async (credentials) => {
try {
const user = await verifyCredentials(credentials.email, credentials.password);
const accessToken = signAccessToken(user);
return {
id: user.user_id, // NextAuth membutuhkan properti `id`
user_id: user.user_id,
email: user.email,
role: user.role,
accessToken,
};
} catch (err) {
// Pesan dari verifyCredentials sengaja diteruskan supaya
// pengguna tahu akunnya belum diaktivasi. Pastikan pesan
// itu tidak memuat detail database.
throw new Error(err.message || "Terjadi kesalahan server");
}
},
}),
],
session: { strategy: "jwt", maxAge: 60 * 60 }, // 1 jam
callbacks: {
async jwt({ token, user }) {
// 1. Saat pertama kali login
if (user) {
token.id = user.user_id;
token.user_id = user.user_id; // dipakai callback session
token.email = user.email;
token.role = user.role;
token.accessToken = user.accessToken;
return token;
}
// 2. Sesi berikutnya: pastikan bearer token masih sah.
// verifyAccessToken HARUS sudah diimpor di atas.
if (!token.accessToken) {
token.accessToken = signAccessToken({
user_id: token.id,
email: token.email,
role: token.role,
});
return token;
}
try {
verifyAccessToken(token.accessToken);
} catch {
// Hanya perbarui bila memang sudah tidak sah. Versi lama selalu
// masuk ke sini karena verifyAccessToken tidak pernah terdefinisi.
token.accessToken = signAccessToken({
user_id: token.id,
email: token.email,
role: token.role,
});
}
return token;
},
async session({ session, token }) {
session.user.id = token.id;
session.user.user_id = token.user_id ?? token.id;
session.user.email = token.email;
session.user.role = token.role;
// accessToken SENGAJA tidak dimasukkan ke session.
// Kalau klien membutuhkannya, buat route terpisah yang
// memeriksa sesi lebih dahulu, misalnya:
// GET /api/auth/access-token -> { access_token }
// dan jangan pernah mengirimkannya ke komponen klien.
return session;
},
},
pages: { signIn: "/login" },
secret: process.env.NEXTAUTH_SECRET,
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };