Membuat Autentikasi Role-Based dengan Next.js dan Prisma

Cara mudah membuat autentikasi role-based menggunakan Next-auth (Auth.js) dan adaptor prisma.

Lie Tjien Lien
Membuat Autentikasi Role-Based dengan Next.js dan Prisma

Dalam artikel blog ini, kami akan memandu Anda melalui proses langkah demi langkah dalam membuat autentikasi role-based untuk aplikasi Next.js Anda menggunakan Next Auth dan Prisma adapter. Anda akan memiliki fondasi yang kuat untuk membangun sistem kontrol akses pengguna yang fleksibel dan dapat diskalakan pada akhirnya.

Dalam project ini, kita akan menggunakan Next.js App Directory Seperti yang Anda ketahui, setelah Next.js 13, kita membuat rute API menggunakan App Directory dan file route.

Mari kita instal library autentikasi dan mulai membuat auth API route.

npm install next-auth

Untuk menambahkan Next Auth.js ke dalam project, buatlah sebuah file bernama route.ts di folder app/api/auth/[…nextauth].

Anda dapat langsung menambahkan opsi auth Anda di file ini, tetapi saya lebih suka menggunakan folder yang berbeda agar dapat menggunakan kembali opsi tersebut nantinya.

Mari buat file auth.ts di direktori src/utils dan tambahkan provider.

import { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export const authOptions: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID!,
      clientSecret: process.env.GOOGLE_SECRET!,
    }),
  ],
};

Jika Anda ingin menambahkan provider lain seperti GitHub, Facebook.

Kita sekarang siap untuk membuat route handler dan menambahkan beberapa opsi ini. Buka file route dan tambahkan ini.

app/api/auth/[…nextauth]/route.ts

import { authOptions } from "@/utils/auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

Sekarang, kita bisa masuk menggunakan akun Google. Tetapi kita tidak menyimpan pengguna dalam basis data. Jadi, mari kita instal Prisma dan buat skema autentikasi kita.

npm install prisma @prisma/client @next-auth/prisma-adapter

untuk menginisialisasinya:

npx prisma init

Buka file schema.prisma di folder prisma dan tambahkan blok kode ini:

datasource db {
  //You can use any database provider. It doesn't have to be PostgreSQL
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider        = "prisma-client-js"
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String    @unique
  image         String?
  isAdmin       Boolean   @default(false)
  emailVerified DateTime?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

Kami menggunakan skema standar ini untuk Prisma adapter; Anda dapat menemukannya di dokumentasi resmi.

Saya hanya menambahkan field isAdmin pada model pengguna untuk memberikan role kepada user. Saya menggunakan boolean karena aplikasi saya hanya memiliki dua role (admin/user reguler). Tapi jika Anda ingin, Anda dapat membuat lebih banyak role dengan mengubah field tersebut.

Ini akan membuat file migrasi SQL dan menjalankannya:

npx prisma migrate dev

Mari kita kembali ke opsi auth dan menambahkan prisma adapter.

src/utils/auth.ts

const prisma = new PrismaClient();

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID!,
      clientSecret: process.env.GOOGLE_SECRET!,
    }),
  ],
};

Mulai sekarang, adaptor akan menangani autentikasi dan secara otomatis menambahkan user, session, dan account baru ke dalam database.

Tetapi ketika kita mencoba untuk bereaksi terhadap pengguna menggunakan session (hook useSession untuk sisi klien dan getServerSession untuk sisi server), ia hanya akan mengembalikan name, email, dan image. Tetapi kita membutuhkan properti isAdmin untuk autorisasi.

Untuk melakukan itu, pertama-tama kita harus menemukan user dalam database dan menambahkan nilai isAdmin ke session. Mari tambahkan kode berikut ini:

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  session: {
    strategy: 'jwt',
  },
  providers: [{
      clientId: process.env.GOOGLE_ID!,
      clientSecret: process.env.GOOGLE_SECRET!,
    },
  ],
  callbacks: {
    async session({ token, session }) {
      if (token) {
        session.user.isAdmin = token.isAdmin;
      }

      return session;
    },
    async jwt({ token }) {
      const dbUser = await prisma.user.findUnique({
        where: {
          email: token.email!,
        },
      });

      token.isAdmin = Boolean(dbUser?.isAdmin);

      return token;
    },
  },
};

Pada callback jwt, kita mengambil nilai isAdmin dari database dan menyembunyikannya di JWT. Dan dalam callback session, kita mengambil token, mencari nilai isAdmin yang telah kita tetapkan, dan menambahkannya ke dalam session. Dan perhatikan ketika kita mencoba mengakses session, session akan mengembalikan properti isAdmin bersama dengan properti pengguna lainnya.

Jika Anda mengalami masalah pada titik ini, coba bersihkan cache di folder .next dan cookie di browser.