Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
07 Aug 2025 · 5 min read ·Article 60 / 110
Go

60. Case Study: Authentication Middleware with gRPC

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

60. Case Study: Authentication Middleware with gRPC

Middleware is one of the crucial components in building scalable and maintainable backend applications. In the context of microservices, middleware is frequently used to handle cross-cutting concerns such as logging, monitoring, rate-limiting, and of course, authentication. In this article, we’ll dissect how to build authentication middleware for a gRPC service, breaking down how it works, the code implementation, and even a simple simulation. This piece is intended for engineers who are already familiar with gRPC and want to integrate authentication into it.


Why Authentication in gRPC?

Unlike a regular HTTP API, gRPC offers high performance through the HTTP/2 protocol and data serialization via Protocol Buffers (protobuf). However, this also means we can’t simply rely on standard HTTP middleware, such as the Express.js framework in NodeJS or the request handler middleware in Django.

To keep communication between microservices secure, the commonly used standards are authentication via JWT (JSON Web Token), API-Key, or even OAuth2. The challenge is that gRPC doesn’t have a middleware concept as expressive as REST APIs, but we can still implement it through interceptors.


Core Concept: Interceptors in gRPC

An interceptor in gRPC is analogous to middleware in an HTTP framework. Interceptors can be used both on the client side (Client Interceptor) and on the server side (Server Interceptor). Authentication is generally performed on the server, especially when validating the requester’s credentials.

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

Mermaid Diagram:

MERMAID
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 

Case Study: A gRPC Service with JWT Authentication Middleware

Let’s simulate the following case:

Danger
There is a gRPC service called UserService that has a single method, GetUserProfile. Only users with a valid JWT token are allowed to access this endpoint.

1. Protofile

We’ll start by designing its protobuf file.

proto
 1// user.proto
 2syntax = "proto3";
 3
 4service UserService {
 5  rpc GetUserProfile(GetUserProfileRequest) returns (UserProfileResponse) {}
 6}
 7
 8message GetUserProfileRequest {
 9  string user_id = 1;
10}
11
12message UserProfileResponse {
13  string user_id = 1;
14  string name = 2;
15  string email = 3;
16}

2. Implementing the gRPC Server in Go

We choose Go because of its tooling ecosystem and its popularity in microservices environments.

a. Import Dependencies

go
 1import (
 2  "context"
 3  "errors"
 4  "fmt"
 5  "log"
 6  "net"
 7  "strings"
 8
 9  "github.com/golang-jwt/jwt/v4"
10  "google.golang.org/grpc"
11  "google.golang.org/grpc/metadata"
12)

b. JWT Validation Function

go
 1var jwtSecret = []byte("R4has14-4p1-anda")
 2
 3func validateJWT(tokenStr string) (jwt.MapClaims, error) {
 4  token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
 5    // Only allow HS256
 6    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
 7      return nil, fmt.Errorf("Unexpected signing method")
 8    }
 9    return jwtSecret, nil
10  })
11
12  if err != nil || !token.Valid {
13    return nil, errors.New("invalid token")
14  }
15
16  claims, ok := token.Claims.(jwt.MapClaims)
17  if !ok {
18    return nil, errors.New("invalid claims")
19  }
20  return claims, nil
21}

c. Middleware: Unary Interceptor

go
 1func AuthInterceptor(
 2    ctx context.Context,
 3    req interface{},
 4    info *grpc.UnaryServerInfo,
 5    handler grpc.UnaryHandler,
 6) (interface{}, error) {
 7
 8  md, ok := metadata.FromIncomingContext(ctx)
 9  if !ok {
10    return nil, grpc.Errorf(grpc.Code(grpc.Unauthenticated), "missing metadata")
11  }
12
13  var token string
14  if val, exists := md["authorization"]; exists && len(val) > 0 {
15    parts := strings.SplitN(val[0], " ", 2)
16    if len(parts) == 2 && strings.EqualFold(parts[0], "Bearer") {
17      token = parts[1]
18    }
19  }
20  if token == "" {
21    return nil, grpc.Errorf(grpc.Code(grpc.Unauthenticated), "missing token")
22  }
23
24  claims, err := validateJWT(token)
25  if err != nil {
26    return nil, grpc.Errorf(grpc.Code(grpc.Unauthenticated), "invalid token: %v", err)
27  }
28
29  // Inject claims into the context if needed
30  newCtx := context.WithValue(ctx, "claims", claims)
31
32  // Forward the request to the handler
33  return handler(newCtx, req)
34}

d. Registering the Interceptor on the Server

go
1server := grpc.NewServer(
2    grpc.UnaryInterceptor(AuthInterceptor),
3)

e. Implementing UserService

go
 1type userServiceServer struct{}
 2
 3func (s *userServiceServer) GetUserProfile(ctx context.Context, req *pb.GetUserProfileRequest) (*pb.UserProfileResponse, error) {
 4  // Retrieve claims from the context (e.g., for multi-role support)
 5  userID := req.GetUserId()
 6  // Simulate fetching data
 7  return &pb.UserProfileResponse{
 8    UserId: userID,
 9    Name:   "Rudi Santoso",
10    Email:  "rudi@contoso.com",
11  }, nil
12}

Simulating a Request

a. Scenarios

ScenarioAuthorization HeaderResult
Valid tokenBearer abc.valid…Success
Invalid tokenBearer xxx.invalidError 401
No token-Error 401

b. Client Request with Metadata

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

Explaining the Workflow

Mermaid Flowchart:

MERMAID
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]

Production Tips

  • Use HTTPS (TLS) for the transport layer. JWT only protects authorization, it does not prevent traffic interception!
  • Refresh the JWT token if you support auto-login or SSO.
  • You can extend the interceptor for multi-role support, claim logging, or rate limiting.
  • Monitor gRPC error codes for observability.

Conclusion

Authentication middleware in gRPC can be implemented through the interceptor concept on the server side. By validating the JWT on every request, we can protect all endpoints without the hassle of implementing it in each individual handler. This approach is easy to extend for other needs, such as role-based authorization or metadata logging. As a result, security becomes more centralized and maintainable.


References:


Happy building secure services! For discussion or feedback, don’t hesitate to share in the comments section.

Related Articles

💬 Comments