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
UserServiceyang memiliki satu methodGetUserProfile. 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
| Skenario | Authorization Header | Hasil |
|---|---|---|
| Token valid | Bearer abc.valid… | Success |
| Token invalid | Bearer xxx.invalid | Error 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.