Perbaikan route NextAuth
Perbaikan route NextAuth
Section titled “Perbaikan route NextAuth”Impor lengkap, sesi JWT yang benar, token tetap di server.
Berkas: alat/hari-3/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 };