tutorial

  1. Studi Kasus: Middleware Otentikasi dengan gRPC

60. Studi Kasus: Middleware Otentikasi dengan gRPC

Middleware merupakan salah satu komponen krusial dalam membangun aplikasi backend yang scalable dan maintainable. Dalam konteks microservices, middleware kerap digunakan untuk mengelola cross-cutting concerns seperti logging, monitoring, rate-limiting, dan tentu saja, otentikasi (authentication). Pada artikel ini, kita akan membedah pembuatan middleware otentikasi pada layanan gRPC, seraya mengurai cara kerja, implementasi kode, hingga simulasi sederhana. Tulisan ini ditujukan bagi engineer yang sudah familier dengan gRPC dan ingin mengintegrasikan aspek otentikasi di antaranya.


Mengapa Otentikasi di gRPC?

Berbeda dengan HTTP API biasa, gRPC menawarkan performa yang tinggi dengan protokol HTTP/2 dan serialisasi data via Protocol Buffers (protobuf). Namun, ini juga berarti kita tidak bisa serta-merta mengandalkan middleware HTTP standar, seperti pada framework Express.js di NodeJS ataupun middleware request handler di Django.

Untuk menjaga keamanan komunikasi antar-microservice, standar yang sering digunakan adalah otentikasi menggunakan JWT (JSON Web Token), API-Key, atau bahkan OAuth2. Tantangannya, gRPC tidak memiliki konsep middleware se-ekspresif REST API, namun kita tetap dapat mengimplementasikannya melalui interceptors.


Konsep Dasar: Interceptor pada gRPC

Interceptor pada gRPC analog dengan middleware dalam HTTP framework. Interceptor dapat digunakan baik pada sisi client (Client Interceptor), maupun pada sisi server (Server Interceptor). Otentikasi umumnya dilakukan di server apalagi saat validasi kredensial requester.

Client ----> Interceptor (Server) ---> Handler

Mermaid Diagram:

sequenceDiagram
    participant C as Client
    participant IS as Interceptor (Server)
    participant H as RPC Handler

    C->>IS: gRPC Request (metadata+payload)
    IS->>IS: Validasi Token/Otentikasi
    alt Otentikasi berhasil
      IS->>H: Forward Request
      H-->>IS: Response
    else Gagal otentikasi
      IS-->>C: Error: Unauthorized
    end
    IS-->>C: Response/Error 

Studi Kasus: Layanan gRPC dengan Middleware Otentikasi JWT

Mari kita simulasi kasus berikut:

Ada layanan gRPC bernama UserService yang memiliki satu method GetUserProfile. Hanya user dengan token JWT valid yang diperbolehkan mengakses endpoint ini.

1. Protofile

Kita mulai dengan mendesain file protobuf-nya.

// user.proto
syntax = "proto3";

service UserService {
  rpc GetUserProfile(GetUserProfileRequest) returns (UserProfileResponse) {}
}

message GetUserProfileRequest {
  string user_id = 1;
}

message UserProfileResponse {
  string user_id = 1;
  string name = 2;
  string email = 3;
}

2. Implementasi Server gRPC di Go

Kita pilih bahasa Go, karena ekosistem tooling dan popularitasnya di lingkungan microservices.

a. Import Dependencies

import (
  "context"
  "errors"
  "fmt"
  "log"
  "net"
  "strings"

  "github.com/golang-jwt/jwt/v4"
  "google.golang.org/grpc"
  "google.golang.org/grpc/metadata"
)

b. JWT Validation Function

var jwtSecret = []byte("R4has14-4p1-anda")

func validateJWT(tokenStr string) (jwt.MapClaims, error) {
  token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
    // Only allow HS256
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
      return nil, fmt.Errorf("Unexpected signing method")
    }
    return jwtSecret, nil
  })

  if err != nil || !token.Valid {
    return nil, errors.New("invalid token")
  }

  claims, ok := token.Claims.(jwt.MapClaims)
  if !ok {
    return nil, errors.New("invalid claims")
  }
  return claims, nil
}

c. Middleware: Unary Interceptor

func AuthInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {

  md, ok := metadata.FromIncomingContext(ctx)
  if !ok {
    return nil, grpc.Errorf(grpc.Code(grpc.Unauthenticated), "missing metadata")
  }

  var token string
  if val, exists := md["authorization"]; exists && len(val) > 0 {
    parts := strings.SplitN(val[0], " ", 2)
    if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
      token = parts[1]
    }
  }
  if token == "" {
    return nil, grpc.Errorf(grpc.Code(grpc.Unauthenticated), "missing token")
  }

  claims, err := validateJWT(token)
  if err != nil {
    return nil, grpc.Errorf(grpc.Code(grpc.Unauthenticated), "invalid token: %v", err)
  }

  // Inject claims ke context jika diperlukan
  newCtx := context.WithValue(ctx, "claims", claims)

  // Forward request ke handler
  return handler(newCtx, req)
}

d. Registrasi Interceptor ke Server

server := grpc.NewServer(
    grpc.UnaryInterceptor(AuthInterceptor),
)

e. Implement UserService

type userServiceServer struct{}

func (s *userServiceServer) GetUserProfile(ctx context.Context, req *pb.GetUserProfileRequest) (*pb.UserProfileResponse, error) {
  // Ambil claims dari context (jika ingin multi-role, dsb)
  userID := req.GetUserId()
  // Simulasi ambil data
  return &pb.UserProfileResponse{
    UserId: userID,
    Name:   "Rudi Santoso",
    Email:  "rudi@contoso.com",
  }, nil
}

Simulasi Request

a. Skenario

SkenarioAuthorization HeaderHasil
Token validBearer abc.valid…Success
Token invalidBearer xxx.invalidError 401
Tanpa token-Error 401

b. Client Request dengan Metadata

md := metadata.Pairs("authorization", "Bearer "+jwtToken)
ctx := metadata.NewOutgoingContext(context.Background(), md)
resp, err := client.GetUserProfile(ctx, &pb.GetUserProfileRequest{UserId: "123"})
if err != nil {
  log.Fatalf("could not get user profile: %v", err)
}

Penjelasan Alur Kerja

Mermaid Flowchart:

flowchart TD
  A[Client Request] -->B[Interceptor: Cek Metadata]
  B -->|Token tidak ada| F[Error: Unauthenticated]
  B -->C{Parse Token}
  C -->|Token invalid| F
  C -->|Valid| D[Inject claims ke context]
  D -->E[Handler: GetUserProfile]
  E -->G[Response]

Tips Production

  • Gunakan HTTPS (TLS) untuk transport layer. JWT hanya melindungi otorisasi, bukan intercept traffic!
  • Refresh token JWT jika mendukung auto-login atau SSO.
  • Bisa extend interceptor untuk multi-role, logging claim, atau rate limiting.
  • Monitoring gRPC error code untuk observabilitas.

Kesimpulan

Middleware otentikasi di gRPC dapat diimplementasikan melalui konsep interceptor pada server side. Dengan validasi JWT pada setiap request, kita dapat melindungi seluruh endpoint tanpa perlu merepotkan implementasi di setiap handler. Metode ini mudah diperluas untuk kebutuhan lain seperti role-based authorization ataupun logging metadata. Dengan demikian, security dapat lebih terpusat dan maintainable.


Referensi:


Selamat membangun service yang secure! Untuk diskusi atau masukan, jangan ragu share di kolom komentar.

comments powered by Disqus