58. gRPC dengan JWT untuk Otentikasi
gRPC dan JWT adalah dua teknologi populer dalam membangun microservices modern yang aman dan scalable. Pada artikel ke-58 ini, saya akan membagikan praktik terbaik implementasi gRPC dengan JWT untuk otentikasi, disertai contoh kode, diagram, dan simulasi sederhana.
Mengapa gRPC & JWT?
Ketika membangun arsitektur microservice yang butuh performa tinggi dan interoperabilitas lintas bahasa, gRPC sering jadi pilihan utama. gRPC menggunakan Protocol Buffers untuk serialisasi data, sehingga komunikasi antar-service jadi ringan dan cepat. Namun, keamanan tetap jadi perhatian utama, terutama soal authentication & authorization.
Di sini, JWT (JSON Web Token) hadir sebagai salah satu solusi otentikasi yang populer dan efektif. JWT mudah digunakan, ringan, serta stateless, cocok sekali untuk distributed systems dan microservice.
Diagram Alur Otentikasi gRPC dengan JWT
Mari lihat dulu secuil gambaran proses otentikasi pada gRPC service dengan JWT:
sequenceDiagram
participant Client
participant AuthService
participant GRPCServer
Client->>AuthService: Request Token (user & password)
AuthService-->>Client: Return JWT Token
Client->>GRPCServer: gRPC Request (attach JWT)
GRPCServer->>GRPCServer: Validate JWT Token
GRPCServer-->>Client: Response (if valid)
Flow JWT pada gRPC
- User Login
Client mengirim username dan password ke AuthService. - Token Generation
AuthService membuat dan mengirim JWT jika login sukses. - Request ke gRPC Service
Client mengirim request gRPC ke service yang ingin diakses dengan JWT sebagai metadata/header. - JWT Validation
gRPC service memvalidasi JWT sebelum mengeksekusi bisnis proses. - Authorized Response
Jika valid, service akan memproses request dan mengirim response.
Kode: Implementasi JWT Auth pada gRPC (Golang)
Saya gunakan Go, karena ekosistemnya matang untuk implementasi gRPC. Prinsipnya sama di bahasa lain.
1. Membuat Service Authentication JWT
Mari mulai dari AuthService sederhana: menghasilkan JWT jika kredensial benar.
// auth_service.go
package main
import (
"net/http"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/gin-gonic/gin"
)
var jwtKey = []byte("secret_mykey")
type Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
func GenerateJWT(username string) (string, error) {
claims := &jwt.RegisteredClaims{
Subject: username,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
func Login(c *gin.Context) {
var cred Credentials
if err := c.BindJSON(&cred); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
return
}
// Contoh hardcode user, production pakai DB!
if cred.Username == "alice" && cred.Password == "password123" {
tokenString, _ := GenerateJWT(cred.Username)
c.JSON(http.StatusOK, gin.H{"token": tokenString})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
}
}
func main() {
r := gin.Default()
r.POST("/login", Login)
r.Run(":8080")
}
2. Mendefinisikan gRPC Service (Proto)
// service.proto
syntax = "proto3";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Generate kode Go dengan:
protoc --go_out=. --go-grpc_out=. service.proto
3. Implementasi Interceptor JWT pada gRPC
Interceptor akan memvalidasi JWT di metadata setiap request.
// server.go
package main
import (
"context"
"errors"
"fmt"
"log"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/golang-jwt/jwt/v4"
pb "path/to/your/proto"
)
var jwtKey = []byte("secret_mykey")
func validateJWT(tokenString string) (string, error) {
token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
return "", err
}
if claims, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid {
return claims.Subject, nil
}
return "", errors.New("invalid token")
}
// 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, fmt.Errorf("missing metadata")
}
tokens := md["authorization"]
if len(tokens) == 0 {
return nil, fmt.Errorf("authorization token is required")
}
jwtToken := tokens[0]
_, err := validateJWT(jwtToken)
if err != nil {
return nil, fmt.Errorf("invalid token: %v", err)
}
// Forward to handler
return handler(ctx, req)
}
// gRPC Service Implementation
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor))
pb.RegisterGreeterServer(s, &server{})
log.Println("gRPC server started on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
4. Simulasi: Call gRPC Service dengan JWT
A. Mendapatkan Token
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username":"alice", "password":"password123"}'
Respon:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
}
B. Call gRPC dengan JWT (Contoh Go client)
import (
"context"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
pb "path/to/your/proto"
)
func main() {
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := pb.NewGreeterClient(conn)
jwtToken := "<token-dari-step-sebelumnya>"
md := metadata.New(map[string]string{"authorization": jwtToken})
ctx := metadata.NewOutgoingContext(context.Background(), md)
resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "Alice"})
if err != nil {
log.Fatal("Error: ", err)
}
log.Println(resp.Message)
}
Tabel: Perbandingan gRPC Auth
| Opsi Auth | Stateless | Mudah Integrasi | Halaman Web (Cookie) | Microservice | Saran Penggunaan |
|---|---|---|---|---|---|
| gRPC + Basic | No | Mudah | Ya | Kurang Aman | Internal service testing |
| gRPC + API Key | Yes | Mudah | Tidak | Cukup Aman | API Public low risk |
| gRPC + JWT | Yes | Sangat Mudah | Tidak langsung | Sangat Kuat | Microservice production |
| gRPC + OAuth2 | Yes | Kompleks | Ya | Sangat Kuat | OpenAPI, 3rd party, SSO |
Tips Best Practice
- Gunakan HTTPS: Aman dari sniffing ketika JWT berpindah antar-node.
- Token Expiry: Selalu tentukan masa berlaku JWT, untuk menghindari token rentan.
- Key Rotation: Rutin rotasi
jwtKey, gunakan JWT signing asymmetric pada skala besar. - Claim Secure: Jangan menaruh data sensitif di dalam JWT!
- Logging & Error Handling: Log semua request gagal otentikasi untuk keperluan audit.
Kesimpulan
Mengimplementasi gRPC dengan JWT untuk otentikasi adalah salah satu fondasi microservices yang aman, scalable, dan stateless. Dengan memanfaatkan interceptor, kita dapat menjaga seluruh endpoint gRPC tetap terlindungi dari akses tak berhak. Jangan lupa, keamanan bukan cuma soal teknik, tapi juga disiplin dalam memelihara ekosistem aplikasi!
Jika ingin source code lengkap atau tanya lebih lanjut, feel free tinggalkan komentar! 👋
Semoga bermanfaat. Jangan lupa share jika berguna! 🚀