Learn gRPC - Authorization, Auditing & Security Best Practices
Series/Learn gRPC/Episode 12
Episode 12 of 19

Learn gRPC - Authorization, Auditing & Security Best Practices

This episode secures authorization: method-level access control, centralized policy enforcement, audit logging and protection of sensitive metadata, plus rate limiting, circuit breaking, and protection from Denial of Service attacks on gRPC services.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Authentication answers "who are you?". Authorization answers the more important question: "what are you allowed to do?". A legitimate user isn't necessarily allowed to delete products. Episode 12 builds the authorization, audit, and defense layers that make a gRPC service truly secure.

We cover method-level access control, centralized policy enforcement, audit logging that records who did what, protection of sensitive metadata, and then defenses against abuse: rate limiting, circuit breaking, and DoS mitigation.

Method-Level Authorization

RBAC with Token Claims

The most common pattern: Role-Based Access Control (RBAC). Claims inside the JWT (episode 11) are mapped to roles, then roles are mapped to the methods they're allowed to call:

Access rights map per method
var accessRules = map[string]string{
    "catalog.v1.CatalogService/GetProduct":  "user",
    "catalog.v1.CatalogService/ListProducts": "user",
    "catalog.v1.CatalogService/AddBulk":     "admin",
    "catalog.v1.CatalogService/UpdatePrice": "admin",
}

With the map above, GetProduct can be called by the user role, while AddBulk and UpdatePrice are admin-only. Methods not listed can be given a default policy — for example, denied.

Enforce in an Interceptor

Enforcement happens in a server interceptor so one place controls the entire service:

Authorization interceptor
func authorize(ctx context.Context, fullMethod string) error {
    role := roleFromClaims(ctx)
    required, ok := accessRules[fullMethod]
    if !ok {
        return status.Error(codes.PermissionDenied, "method tidak dikenali")
    }
    if role != required && role != "superadmin" {
        return status.Error(codes.PermissionDenied, "hak akses tidak cukup")
    }
    return nil
}

The authorize(ctx, fullMethod) function reads the role from the claims, matches it against the map, and denies with codes.PermissionDenied if it doesn't match. Call this function before handler in the server interceptor.

Policy Enforcement

Separate Policy from Code

Hardcoded policies are hard to change and hard to audit. Move them to external configuration — a YAML file read at startup:

Access policy in YAML
methods:
  - path: catalog.v1.CatalogService/GetProduct
    roles: [user, admin, superadmin]
  - path: catalog.v1.CatalogService/AddBulk
    roles: [admin, superadmin]
  - path: catalog.v1.CatalogService/UpdatePrice
    roles: [admin, superadmin]

With roles: [admin, superadmin] in a separate file, policy changes are just a new configuration deploy without recompiling the server. This approach grows into the Authorization Policy managed by a service mesh in episode 18.

Audit Logging

Recording Who and What

Every important data change must be recorded. A unary interceptor can capture the full call context:

Audit log in a server interceptor
func auditInterceptor(ctx context.Context, req any,
    info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
    identity := identityFromContext(ctx)
    res, err := handler(ctx, req)
    audit.Log(identity, info.FullMethod, req, err)
    return res, err
}

audit.Log(identity, info.FullMethod, req, err) writes an audit line: who called, which method, what payload, and the result. Store it in append-only storage that callers can't modify — for example a centralized log or object storage.

Protecting Sensitive Metadata

Never write tokens or credentials into logs. Tokens live in the authorization metadata, not the payload — make sure audit logging doesn't include raw metadata. If needed for investigation, hash it first or truncate part of it.

Rate Limiting and DoS Protection

Token Bucket per Client

Rate limiting prevents one client from flooding the server. A token bucket grants a request quota per second per identity:

A simple token bucket
func rateLimit(identity string) error {
    limiter, ok := buckets[identity]
    if !ok {
        limiter = rate.NewLimiter(rate.Limit(20), 40)
        buckets[identity] = limiter
    }
    if !limiter.Allow() {
        return status.Error(codes.ResourceExhausted, "terlalu banyak request")
    }
    return nil
}

rate.NewLimiter(rate.Limit(20), 40) allows 20 requests per second with a burst of 40. Violations are answered with codes.ResourceExhausted so the client knows to wait, not just that it failed.

Circuit Breaking and DoS Mitigation

Three more defense layers that are mandatory:

  • Circuit breaker: cuts off a sick dependency so failures don't cascade (details in episode 15).
  • Message size limits: set MaxRecvMsgSize so giant messages can't exhaust memory.
  • Limit streams per connection: prevents one client from opening an unbounded number of streams.
Limit message size
s := grpc.NewServer(
    grpc.MaxRecvMsgSize(4*1024*1024),
    grpc.MaxSendMsgSize(4*1024*1024),
)

The MaxRecvMsgSize(4*1024*1024) setting caps incoming messages at 4 MiB. This combination holds off application-level DoS attacks without waiting for the network to break.

Closing

Key takeaways:

  • Method-level authorization maps JWT claim roles to allowed methods.
  • Enforcement is centralized in a server interceptor so one point controls everything.
  • Separate policy into an external config file so it's easy to change and audit.
  • Audit logs record who, which method, and the result — without sensitive metadata.
  • Rate limiting with a token bucket answers violations with ResourceExhausted.
  • Limit message size and stream count to hold off DoS attacks.

In episode 13 next, we cover gRPC performance and optimization — measuring latency and throughput with ghz, optimizing through compression and connection reuse, handling backpressure, and composing efficient protobuf messages with packed fields and repeated fields. Your solid security is now balanced with measurable speed.

Learn gRPC - Authorization, Auditing & Security Best Practices | Learn gRPC