58. gRPC dengan JWT untuk Otentikasi
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.
1// auth_service.go
2package main
3
4import (
5 "net/http"
6 "time"
7
8 "github.com/golang-jwt/jwt/v4"
9 "github.com/gin-gonic/gin"
10)
11
12var jwtKey = []byte("secret_mykey")
13
14type Credentials struct {
15 Username string `json:"username"`
16 Password string `json:"password"`
17}
18
19func GenerateJWT(username string) (string, error) {
20 claims := &jwt.RegisteredClaims{
21 Subject: username,
22 ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
23 }
24
25 token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
26 return token.SignedString(jwtKey)
27}
28
29func Login(c *gin.Context) {
30 var cred Credentials
31 if err := c.BindJSON(&cred); err != nil {
32 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
33 return
34 }
35 // Contoh hardcode user, production pakai DB!
36 if cred.Username == "alice" && cred.Password == "password123" {
37 tokenString, _ := GenerateJWT(cred.Username)
38 c.JSON(http.StatusOK, gin.H{"token": tokenString})
39 } else {
40 c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
41 }
42}
43
44func main() {
45 r := gin.Default()
46 r.POST("/login", Login)
47 r.Run(":8080")
48}2. Mendefinisikan gRPC Service (Proto)
1// service.proto
2syntax = "proto3";
3
4service Greeter {
5 rpc SayHello (HelloRequest) returns (HelloReply) {}
6}
7
8message HelloRequest {
9 string name = 1;
10}
11
12message HelloReply {
13 string message = 1;
14}Generate kode Go dengan:
1protoc --go_out=. --go-grpc_out=. service.proto3. Implementasi Interceptor JWT pada gRPC
Interceptor akan memvalidasi JWT di metadata setiap request.
1// server.go
2package main
3
4import (
5 "context"
6 "errors"
7 "fmt"
8 "log"
9 "net"
10
11 "google.golang.org/grpc"
12 "google.golang.org/grpc/metadata"
13 "github.com/golang-jwt/jwt/v4"
14
15 pb "path/to/your/proto"
16)
17
18var jwtKey = []byte("secret_mykey")
19
20func validateJWT(tokenString string) (string, error) {
21 token, err := jwt.ParseWithClaims(tokenString, &jwt.RegisteredClaims{}, func(token *jwt.Token) (interface{}, error) {
22 return jwtKey, nil
23 })
24 if err != nil {
25 return "", err
26 }
27 if claims, ok := token.Claims.(*jwt.RegisteredClaims); ok && token.Valid {
28 return claims.Subject, nil
29 }
30 return "", errors.New("invalid token")
31}
32
33// Unary interceptor
34func authInterceptor(
35 ctx context.Context,
36 req interface{},
37 info *grpc.UnaryServerInfo,
38 handler grpc.UnaryHandler,
39) (interface{}, error) {
40 md, ok := metadata.FromIncomingContext(ctx)
41 if !ok {
42 return nil, fmt.Errorf("missing metadata")
43 }
44
45 tokens := md["authorization"]
46 if len(tokens) == 0 {
47 return nil, fmt.Errorf("authorization token is required")
48 }
49 jwtToken := tokens[0]
50 _, err := validateJWT(jwtToken)
51 if err != nil {
52 return nil, fmt.Errorf("invalid token: %v", err)
53 }
54
55 // Forward to handler
56 return handler(ctx, req)
57}
58
59// gRPC Service Implementation
60type server struct {
61 pb.UnimplementedGreeterServer
62}
63
64func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
65 return &pb.HelloReply{Message: "Hello " + in.Name}, nil
66}
67
68func main() {
69 lis, err := net.Listen("tcp", ":50051")
70 if err != nil {
71 log.Fatalf("failed to listen: %v", err)
72 }
73 s := grpc.NewServer(grpc.UnaryInterceptor(authInterceptor))
74 pb.RegisterGreeterServer(s, &server{})
75 log.Println("gRPC server started on :50051")
76 if err := s.Serve(lis); err != nil {
77 log.Fatalf("failed to serve: %v", err)
78 }
79}4. Simulasi: Call gRPC Service dengan JWT
A. Mendapatkan Token
1curl -X POST http://localhost:8080/login \
2-H "Content-Type: application/json" \
3-d '{"username":"alice", "password":"password123"}'Respon:
1{
2 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
3}B. Call gRPC dengan JWT (Contoh Go client)
1import (
2 "context"
3 "log"
4 "google.golang.org/grpc"
5 "google.golang.org/grpc/metadata"
6 pb "path/to/your/proto"
7)
8
9func main() {
10 conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
11 defer conn.Close()
12 client := pb.NewGreeterClient(conn)
13
14 jwtToken := "<token-dari-step-sebelumnya>"
15 md := metadata.New(map[string]string{"authorization": jwtToken})
16 ctx := metadata.NewOutgoingContext(context.Background(), md)
17
18 resp, err := client.SayHello(ctx, &pb.HelloRequest{Name: "Alice"})
19 if err != nil {
20 log.Fatal("Error: ", err)
21 }
22 log.Println(resp.Message)
23}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! 🚀