Lesson 12 · Interceptors, middleware & auth

The middleware chain

Many interceptors, one order — and why the order matters.

Your win: explain how interceptors chain, in what order they run, and what each interceptor in your services' actual stack does.

Chaining: many interceptors → one

You rarely have just one interceptor. gRPC composes several into a single chain — via the native grpc.ChainUnaryInterceptor or (in our repo) grpc_middleware.WithUnaryServerChain. They execute left to right, and each passes its ctx changes to the next.1

request ─▶ [ tags ][ log ][ recovery ][ timeout ][ auth ][ tracer ] ─▶ handler └──────────────── on the way in ───────────────┘ │ ◀──────────────── on the way out ──────────────────────┘ (like nested wrappers — an onion: outer wraps inner in both directions)

Order is a correctness concern

Why order matters Recovery must be outer so it catches panics from everything inside. Auth must run before the handler (and before anything that assumes a user). Timeout wraps the handler's context. Tags/logging go first so every later log line is correlated. Reorder these and you get silent auth bypasses or uncaught panics.

Your actual chain

The framework assembles a default set plus each service's custom interceptors (internal/golibs/bootstrap/grpc.go:98-130):

#InterceptorDoes
1grpc_ctxtagsattach request tags for logs/tracing
2grpc_zapstructured request logging
3payload loggerlog the request payload on failure
4context-canceled remap"context canceled" → codes.Canceled (Lesson 8)
5recovery (non-local)panic → codes.Unknown + stacktrace
6timeoutwrap ctx with 5s deadline (Lesson 9)
7authverify JWT from metadata, inject claims (Lesson 13)
8activity-log traceremit an activity/trace span
Anchor conversationmgmt appends its customs after the defaults (cmd/server/conversationmgmt/gserver.go:131): DefaultUnaryServerInterceptor(rsc) then authInterceptor + tracer.UnaryActivityLogRequestInterceptor. spike/notification add a fake-JWT interceptor for internal calls (Lesson 13). Metrics ride along via OpenCensus GrpcServerViews (latency, request count) → Prometheus.
Read this next

go-grpc-middleware (chaining) + gRPC Interceptors guide

ChainUnaryServer docs explain left-to-right execution and ctx flow; the interceptors guide covers the concept.

github.com/grpc-ecosystem/go-grpc-middleware
grpc.io/docs/guides/interceptors

Check yourself (from memory)

Q1. Chained interceptors execute in what order?

Left-to-right; each sees the previous one's ctx changes. Nested like an onion — before and after the handler.

Q2. Which interceptor should sit outermost?

Recovery must wrap everything inside so it catches their panics; auth must precede the handler. Order is a correctness concern.

Q3. Our default chain includes logging, tags, and…

ctxtags → zap log → payload-log → context-canceled remap; then recovery + timeout + auth + tracer.
How do interceptors chain, why does order matter, and what's in our chain?
recall, then click to reveal
They're combined into one with grpc_middleware.WithUnaryServerChain (native: grpc.ChainUnaryInterceptor) and run LEFT-TO-RIGHT, each passing ctx changes to the next (nested onion — before and after the handler). Order matters: recovery outermost (catch inner panics), auth before the handler, timeout wrapping. Our chain: ctxtags → zap logging → payload-log → context-canceled remap → recovery → timeout(5s) → auth → activity-log tracer → handler; metrics via OpenCensus GrpcServerViews.
Want to see exactly where recovery/timeout get inserted (and why they're skipped locally)? Ask me.

1. go-grpc-middleware — ChainUnaryServer; gRPC Interceptors.