Skip to content

Security & Auth

Security in gRPC answers two different questions, and it’s crucial not to conflate them:

  • Is the connection private and is the server who it claims to be?transport security (TLS).
  • Who is the caller, and are they allowed to make this call?call authentication (a token, usually in metadata).

gRPC models these as two kinds of credentials: channel credentials (the transport layer) and call credentials (per-RPC auth). You almost always want both.

flowchart TB
  subgraph transport["Channel credentials — TLS"]
    t["encrypts the connection
+ verifies server identity
(mTLS also verifies client)"]
  end
  subgraph call["Call credentials — per RPC"]
    c["attaches a token in metadata
identifies the caller
checked by a server interceptor"]
  end
  transport --> call
Transport security and call auth are separate layers

By default a plaintext channel (insecure) is fine for a local demo and unacceptable in production. TLS encrypts the connection and lets the client verify the server’s certificate, so nobody can eavesdrop or impersonate the server.

mTLS (mutual TLS) goes one step further: the server also verifies the client’s certificate. This is the common pattern for service-to-service auth inside a mesh — both ends prove their identity with certificates, and no bearer token is needed for the machine identity.

TLS proves the server’s identity and the machine’s, but often you also need the user’s identity — a bearer token (JWT, OAuth) sent per call. gRPC attaches this as call credentials: the client puts an authorization token in metadata on every RPC, and a server interceptor validates it and rejects bad ones with UNAUTHENTICATED.

Call credentials are per-RPC, so different calls on the same channel can carry different tokens (e.g. one channel, many end users).

// Channel credentials: TLS. Call credentials: a token on every RPC.
creds := credentials.NewClientTLSFromCert(certPool, "")
perRPC := oauth.TokenSource{TokenSource: myTokenSource}
conn, _ := grpc.NewClient(
"api.example.com:443",
grpc.WithTransportCredentials(creds), // TLS
grpc.WithPerRPCCredentials(perRPC), // token per call
)

Never send a bearer token over an insecure channel. gRPC enforces this: call credentials refuse to attach to a plaintext connection, precisely because a token sent in cleartext is a token anyone on the path can steal. Call credentials require channel credentials (TLS) underneath — the framework is protecting you from the most common gRPC security bug.

What question do channel credentials (TLS) answer?
What does mTLS add over plain TLS?
Why are call credentials described as per-RPC?
Why does gRPC refuse to attach call credentials to an insecure channel?