tutorial

38 Otorisasi dengan Interceptor gRPC

gRPC telah menjadi salah satu tulang punggung komunikasi antar microservice yang modern. Daya pikatnya? Kecepatan, efisiensi, dan ekosistem yang matang. Namun, ketika sebuah layanan mulai menampung permintaan dari klien yang beragam dengan hak akses bervariasi, satu isu besar pun muncul: OTORISASI.

Jika Anda meng-kode rule otorisasi langsung di masing-masing handler service, masalah segera datang: duplikasi, kode yang sulit dirawat, hingga bug keamanan yang tersembunyi. Inilah kenapa gRPC Interceptor menjadi solusi elegan–mereka bertindak sebagai middleware, memungkinkan Anda untuk menanamkan lapisan otorisasi secara konsisten di seluruh endpoint dengan cara yang bersih dan testable.

Pada artikel ini, saya akan membahas:

  • Konsep dasar Interceptor gRPC
  • Apa perbedaan “Otorisasi” & “Autentikasi”?
  • Skema implementasi otorisasi dengan Interceptor
  • Contoh kode otorisasi sederhana di Go
  • Simulasi skenario otorisasi
  • Rangkuman best practices

Autentikasi vs. Otorisasi

Meski serupa, keduanya berbeda. Mari kita perjelas.

AutentikasiOtorisasi
Siapa user Anda?Apakah user ini mendapat akses?
Biasanya lewat JWT/TokenRole, permission, polis, dsb
Sering direspons 401/UnauthSering direspons 403/Forbidden

Di kebanyakan aplikasi modern, autentikasi terjadi lebih dulu. Saat header authorization lolos, baru otorisasi dilakukan untuk memastikan apakah user boleh mengakses method/endpoint tertentu.

Pada gRPC, dua langkah ini bisa Anda tanamkan pada Interceptor yang berbeda, atau digabung dalam satu interceptor berurutan.


Mengenal Interceptor gRPC

Secara sederhana, gRPC Interceptor adalah fungsi middleware yang berjalan sebelum method utama dipanggil. Bisa dipasang untuk:

  • Unary Interceptor: Untuk endpoint yang request-response
  • Stream Interceptor: Untuk endpoint stream (client/server/bidirectional)

Pada otorisasi, Unary Interceptor adalah yang paling sering digunakan.

Diagram alur sederhana otorisasi pada interceptor dapat digambarkan seperti berikut:

flowchart TD
    req[Permintaan masuk ke gRPC Service]
    auth[Autentikasi Token/Identitas]
    authorize[Otorisasi: Pengecekan role/permission]
    handler[Eksekusi Handler utamanya]
    reject[Return Error Forbidden]
    
    req --> auth
    auth -- valid --> authorize
    auth -- invalid --> reject
    authorize -- allowed --> handler
    authorize -- denied --> reject

Studi Kasus: Service gRPC dengan Otorisasi

Misal, kita punya dua method pada service Buku:

  • GetBook : Siapa pun yang sudah login boleh akses
  • DeleteBook : Hanya user dengan role admin yang boleh akses

Protobuf Service

service BookService {
    rpc GetBook(GetBookRequest) returns (BookResponse) {}
    rpc DeleteBook(DeleteBookRequest) returns (DeleteBookResponse) {}
}

Implementasi Interceptor Otorisasi di Golang

Ada banyak bahasa yang mendukung gRPC, namun di ekosistem cloud (dan startup), Go menjadi pilihan populer. Berikut contoh Unary Interceptor untuk otorisasi sederhana pakai JWT.

1. Dekode Token & Extract Role

// Fungsi pasien buat mendecode token JWT (tanpa library external)
func extractRoleFromToken(authHeader string) (string, error) {
    // Anggap format "Bearer eyJhbGciOiJIUzI1NiIsInR5cC..."
    split := strings.Split(authHeader, " ")
    if len(split) != 2 || split[0] != "Bearer" {
        return "", errors.New("invalid authorization header")
    }
    // Simulasikan: role di-encode di bagian payload setelah "."
    parts := strings.Split(split[1], ".")
    if len(parts) != 3 {
        return "", errors.New("malformed jwt")
    }
    // Pada use-case nyata: parse JSON, base64-decode, dll.
    // Demo: role 'admin' kalau token == "admin.token"
    if split[1] == "admin.token" {
        return "admin", nil
    }
    return "user", nil
}

2. Mapping Permission ke Method

Agar clean dan flexible, pakai map:

var methodPermission = map[string]string{
    "/BookService/DeleteBook": "admin",
    // Method lain jika ingin tambahkan role khusus
}

3. Interceptor Otorisasi

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    "context"
)

func AuthorizationInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    // Ambil metadata (HTTP header)
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Error(codes.Unauthenticated, "metadata missing")
    }

    // Ambil Authorization header
    authHeaders := md["authorization"]
    if len(authHeaders) == 0 {
        return nil, status.Error(codes.Unauthenticated, "authorization header missing")
    }

    userRole, err := extractRoleFromToken(authHeaders[0])
    if err != nil {
        return nil, status.Error(codes.Unauthenticated, "invalid token")
    }

    // Check apakah role cukup untuk method ini
    needRole, ok := methodPermission[info.FullMethod]
    if ok && userRole != needRole {
        return nil, status.Error(codes.PermissionDenied, "forbidden")
    }

    // Kalau lolos, teruskan ke handler
    return handler(ctx, req)
}

4. Pasang Interceptor pada server

grpcServer := grpc.NewServer(
    grpc.UnaryInterceptor(AuthorizationInterceptor),
)
// register server dll...

Simulasi Skenario: Akses & Denied

Tabel Skema

MethodTokenRole DitebakOutcome
GetBookBearer xxxuserAllowed
DeleteBookBearer xxxuserDenied
DeleteBookBearer admin.tokenadminAllowed

Pseudo-Test

// Simulasi request ke /BookService/DeleteBook

ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs(
    "authorization", "Bearer admin.token",
))
_, err := AuthorizationInterceptor(ctx, &DeleteBookRequest{}, &grpc.UnaryServerInfo{
    FullMethod: "/BookService/DeleteBook",
}, HandlerStub)
fmt.Println(err) // <nil>, diizinkan

ctx2 := metadata.NewIncomingContext(context.Background(), metadata.Pairs(
    "authorization", "Bearer user.token",
))
_, err = AuthorizationInterceptor(ctx2, &DeleteBookRequest{}, &grpc.UnaryServerInfo{
    FullMethod: "/BookService/DeleteBook",
}, HandlerStub)
fmt.Println(err) // PermissionDenied, forbidden

Diagram Alur Lengkap

sequenceDiagram
    participant Client
    participant Interceptor
    participant Handler
    Client->>Interceptor: gRPC call + header authorization
    Interceptor->>Interceptor: Validasi token & mapping method to role
    alt Token invalid / tak cukup role
        Interceptor-->>Client: Error 401 / 403
    else Token valid & role sesuai
        Interceptor->>Handler: teruskan request
        Handler-->>Client: Return response
    end

Best Practices & Catatan

  • Pisahkan AuthN dan AuthZ: Interceptor bisa di-chain, buat autentikasi dan otorisasi sendiri agar mudah maintenance.
  • Granuler: Implementasi bisa dibuat per-method, per-resource, bahkan berdasarkan dynamic logic.
  • Extensible: Integrasikan dengan Identity Provider (OAuth, OIDC, dst) jika production-ready.
  • Audit & Logging: Tambahkan logging untuk setiap kegagalan otorisasi.
  • Test Coverage: Buat test case untuk semua kombinasi role dan endpoint.

Kesimpulan

Otorisasi dengan gRPC Interceptor adalah pola industri yang scalable, testable, dan elegan–khususnya di microservices architecture. Layered keamanan bisa dibangun tanpa mengotori handler/core logic.
Mulailah dari mapping statik seperti di atas, lanjutkan ke rule dinamis sesuai kebutuhan organisasi Anda.

Bagaimana dengan Anda? Sudahkah otorisasi Anda cukup modular dan maintainable di layanan-layanan gRPC produksi Anda? Jika belum, barangkali sekarang saatnya refactor – dan Interceptor adalah partner terbaik Anda.


Referensi:

Silakan bagikan pengalaman atau pertanyaan Anda di kolom komentar!

comments powered by Disqus

Topik Terhangat

programming
209
tutorial
76
tips-and-trick
43
jaringan
28
hardware
11
linux
4
kubernetes
1