Belajar GraphQL - Role-Based Access Control (RBAC) dan Permissions
Episode 14 of 51

Belajar GraphQL - Role-Based Access Control (RBAC) dan Permissions

Episode 14 membangun authorization dan access control: pola field-level dan object-level authorization, Role-Based Access Control dengan role hierarchy, permission granular, direktif @auth @hasRole @isOwner, middleware reusable, hingga data filtering berdasarkan kepemilikan dan scope organisasi.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

Episode 13 membuktikan siapa pengguna kalian. Episode 14 menjawab pertanyaan berikutnya: apa yang boleh mereka lakukan? Inilah authorization dan access control — menentukan izin berdasarkan identitas, peran, dan kepemilikan.

Authorization Patterns

Field-Level dan Object-Level

  • Field-level: melindungi field tertentu, misalnya email hanya untuk pemilik dan admin.
  • Object-level: melindungi seluruh operasi pada objek, misalnya hanya pemilik yang bisa mengubah post.
Field yang dilindungi
type User {
  id: ID!
  username: String!
  email: String
  isPrivate: Boolean!
}

Resolver-Level Checks

JSPemeriksaan izin di resolver
async function deletePost(_, args, ctx) {
  const post = await ctx.db.posts.find(args.id);
  if (!post) throw new NotFoundError("Post");
 
  const isOwner = post.authorId === ctx.user?.id;
  const isAdmin = ctx.user?.role === "ADMIN";
  if (!isOwner && !isAdmin) {
    throw new GraphQLError("Anda tidak berhak menghapus post ini", {
      extensions: { code: "FORBIDDEN" },
    });
  }
 
  return ctx.db.posts.delete(args.id);
}

Role-Based Access Control (RBAC)

Mendefinisikan Role dan Hierarchy

RBAC memberi izin berdasarkan peran pengguna. Mulai dari mendefinisikan role dan hierarkinya:

JSDefinisi role dan hierarchy
const ROLES = { GUEST: 0, MEMBER: 1, ADMIN: 2 };
 
function hasRole(user, required) {
  return user && ROLES[user.role] >= ROLES[required];
}

Role Checking dalam Resolvers

JSPeriksa role dengan helper
Mutation: {
  banUser: (_, args, ctx) => {
    if (!hasRole(ctx.user, "ADMIN")) throw new ForbiddenError();
    return ctx.userService.ban(args.id);
  },
},

Permission-Based Authorization

RBAC menangani mayoritas kebutuhan, tetapi aplikasi kompleks butuh permission granular — izin spesifik seperti "mengundang anggota" atau "mengubah billing" yang tidak cocok diwakili satu role. Struktur yang umum adalah permission action:resource:

JSPermission granular
const permissions = {
  "invite:member": ["ADMIN"],
  "billing:edit": ["ADMIN"],
  "post:create": ["MEMBER", "ADMIN"],
};
 
function can(user, permission) {
  return permissions[permission]?.includes(user?.role) ?? false;
}

Authorization Directives

Membangun Direktif @auth dan @hasRole

JSDirektif @auth
import { mapSchema, getDirective, MapperKind } from "@graphql-tools/utils";
 
function authDirectiveTransformer(schema) {
  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
      const directive = getDirective(schema, fieldConfig, "auth")?.[0];
      if (!directive) return fieldConfig;
      const { resolve } = fieldConfig;
      fieldConfig.resolve = async (source, args, context, info) => {
        if (!context.user) throw new ForbiddenError();
        return resolve(source, args, context, info);
      };
      return fieldConfig;
    },
  });
}

Schema-nya:

Schema dengan direktif
directive @auth on FIELD_DEFINITION
directive @hasRole(role: String!) on FIELD_DEFINITION
 
type Query {
  me: User @auth
  adminStats: Stats @hasRole(role: "ADMIN")
}

Library seperti graphql-shield menyediakan implementasi siap pakai jika kalian tidak ingin menulis transformer sendiri.

Middleware untuk Authorization

Composable Authorization Functions

JSMiddleware authorization
const requireAuth = (next) => async (parent, args, ctx, info) => {
  if (!ctx.user) throw new ForbiddenError();
  return next(parent, args, ctx, info);
};
 
const requireRole = (role) => (next) => async (parent, args, ctx, info) => {
  if (!hasRole(ctx.user, role)) throw new ForbiddenError();
  return next(parent, args, ctx, info);
};
 
export const resolvers = {
  Mutation: {
    deletePost: requireRole("MEMBER")(async (_, args, ctx) =>
      ctx.postService.delete(args.id)
    ),
  },
};

Pola higher-order function ini memungkinkan stacking: requireAuth(requireRole("ADMIN")(resolver)). Logika authorization terpusat, mudah dites (episode 21), dan tidak membebani schema.

Data Filtering

Filtering Berdasarkan Kepemilikan

JSFilter data berdasarkan izin
Query: {
  posts: async (_, __, ctx) => {
    const isAdmin = ctx.user?.role === "ADMIN";
    const where = isAdmin ? {} : { OR: [{ published: true }, { authorId: ctx.user?.id }] };
    return ctx.db.posts.findMany({ where });
  },
},

Pola ini diterapkan untuk: konten privat yang hanya dilihat pemilik, data terbatas per organisasi, dan konten yang belum dipublikasikan.

Penutup

Inti yang harus dibawa pulang:

  • Authorization bisa diterapkan di level field, object, atau resolver.
  • RBAC dengan role hierarchy menyederhanakan pemeriksaan peran.
  • Permission granular cocok untuk izin spesifik di luar role.
  • Direktif seperti @auth dan @hasRole menjadikan aturan terlihat di schema.
  • Middleware dengan higher-order function membuat izin reusable dan composable.
  • Data filtering memastikan user hanya melihat data yang berhak, tanpa bocorkan keberadaan data.

Di episode 15 selanjutnya kalian akan mempelajari security best practices — query depth limiting, query complexity analysis, rate limiting, introspetri, CORS, dan persisted queries. API GraphQL kalian akan dilindungi dari serangan yang paling umum terjadi!

Belajar GraphQL - Role-Based Access Control (RBAC) dan Permissions | Belajar GraphQL