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.
Autentikasi | Otorisasi |
---|---|
Siapa user Anda? | Apakah user ini mendapat akses? |
Biasanya lewat JWT/Token | Role, permission, polis, dsb |
Sering direspons 401/Unauth | Sering 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 aksesDeleteBook
: Hanya user dengan roleadmin
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
Method | Token | Role Ditebak | Outcome |
---|---|---|---|
GetBook | Bearer xxx | user | Allowed |
DeleteBook | Bearer xxx | user | Denied |
DeleteBook | Bearer admin.token | admin | Allowed |
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!
15 Struktur Penulisan Resolver yang Baik di Go
Artikel Terhangat
38 Otorisasi dengan Interceptor gRPC
07 Jul 2025
37 Otentikasi di Middleware gRPC
07 Jul 2025
36 Rate Limiting di Interceptor
07 Jul 2025
13 Membuat Resolver Pertama di graphql-go
07 Jul 2025
35 Validasi Request menggunakan Interceptor
07 Jul 2025

38 Otorisasi dengan Interceptor gRPC

37 Otentikasi di Middleware gRPC

36 Rate Limiting di Interceptor

13 Membuat Resolver Pertama di graphql-go
