Mọi API thật đều cần biết ai đang gọi. Với REST, bạn gắn token vào HTTP header Authorization. gRPC không có "header" theo nghĩa HTTP thuần, nhưng có thứ tương đương: metadata — những cặp key-value gửi kèm mỗi request, hệt như header. Kết hợp metadata với interceptor (bài grpc-06), bạn kiểm xác thực tập trung một chỗ cho mọi RPC, thay vì rải code kiểm token trong từng handler. Bài này (phần 8/12) đo thật luồng auth hoàn chỉnh trong go-lab: client gửi token, server kiểm trong interceptor, và trích danh tính người dùng ra để dùng.
Metadata: header của gRPC
Metadata là cặp key-value đi kèm request (và cả response). Client gắn vào context gửi đi; server đọc từ context nhận được:
// CLIENT: gắn token vào metadata
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer tok-alice")
resp, err := c.Me(ctx, req) // token đi KÈM sang server
// SERVER (trong interceptor): đọc metadata
md, _ := metadata.FromIncomingContext(ctx)
tok := strings.TrimPrefix(md.Get("authorization")[0], "Bearer ")
Kiểm token trong một interceptor (thay vì từng handler) là cách làm chuẩn — áp một lần cho mọi RPC, và sau khi xác thực, gắn danh tính người dùng vào context cho handler dùng:

Hình 1: Client gắn token bằng metadata.AppendToOutgoingContext; server đọc bằng metadata.FromIncomingContext trong một auth interceptor, kiểm token rồi gắn user vào context cho handler. Metadata như HTTP header — và không tự mã hoá nên bắt buộc chạy trên TLS.
Đo thật: ba loại token
Mình dựng service Account với RPC Me() được bảo vệ bởi một auth interceptor, rồi gọi với ba loại token:

Hình 2: Đo thật. (1) Không token → Unauthenticated. (2) Token sai → PermissionDenied. (3) Token đúng (tok-alice) → auth interceptor cho qua, server đọc user=alice từ token → OK, role=member.
Kết quả thật:
- ① Không token →
Unauthenticated("thieu token (metadata authorization)"). Interceptor thấy không có keyauthorizationtrong metadata, chặn ngay với code 401-tương-đương. - ② Token sai ("sai-be-bet") →
PermissionDenied("token khong hop le"). Có token nhưng không hợp lệ — code 403-tương-đương. Lưu ý sự phân biệt: thiếu token (chưa xác thực) khác sai token (xác thực thất bại) — hai code khác nhau để client xử lý đúng. - ③ Token đúng ("tok-alice") → interceptor log
token OK -> user=alice, cho qua, handler chạy, trảuser=alice, role=member. Quan trọng: server trích được danh tính (alice) từ token và gắn vào context — handler dùng nó cho logic nghiệp vụ (kiểm quyền sở hữu ở bài IDOR của sê-ri bảo mật, ghi log có user, v.v.).
Toàn bộ logic auth nằm một chỗ (interceptor), áp cho mọi RPC của service. Thêm một RPC mới? Nó tự động được bảo vệ, không cần nhớ chép code kiểm token.
Đánh đổi cần cân nhắc
Metadata KHÔNG tự mã hoá — bắt buộc chạy trên TLS. Giống HTTP header gửi qua HTTP thuần, metadata (gồm token) đi trần trên dây nếu không có TLS. Bất kỳ ai nghe được đường truyền đọc được token. Đây là lý do production luôn chạy gRPC trên TLS (credentials.NewTLS thay vì insecure) — demo này dùng insecure chỉ vì chạy localhost trong lab. Nối thẳng với bài TLS của sê-ri bảo mật: token trên kênh không mã hoá là token bị lộ.
Per-RPC credentials tiện hơn gắn tay mỗi call. Demo gắn token thủ công bằng AppendToOutgoingContext mỗi lần — dễ quên. gRPC có PerRPCCredentials (một interface tự cung cấp metadata cho mọi call của connection), hoặc dùng interceptor phía client. Với token OAuth tự refresh, per-RPC credentials là cách sạch hơn: cấu hình một lần, mọi call tự mang token mới nhất.
Xác thực khác uỷ quyền — interceptor chỉ lo phần đầu. Auth interceptor trả lời "bạn là ai" (xác thực). Nhưng "bạn được làm gì với đối tượng này" (uỷ quyền mức đối tượng) thường phải kiểm trong handler, nơi biết đối tượng cụ thể — đúng như bài Broken Access Control/IDOR. Đừng tưởng qua được auth interceptor là được làm mọi thứ; interceptor chặn kẻ lạ, handler vẫn phải kiểm quyền trên từng tài nguyên.
Ba ý mang về
- Metadata là header của gRPC — cặp key-value kèm mỗi request. Client gắn bằng
metadata.AppendToOutgoingContext, server đọc bằngmetadata.FromIncomingContext. Dùng cho token auth, request-id, trace context... như HTTP header. - Kiểm auth trong interceptor: một chỗ, mọi RPC. Đo thật: không token →
Unauthenticated, token sai →PermissionDenied, token đúng → cho qua và server tríchuser=alicetừ token để dùng. Thêm RPC mới tự động được bảo vệ. - Metadata không tự mã hoá và auth ≠ authz. Bắt buộc chạy trên TLS (token trần = token lộ); dùng per-RPC credentials cho token tự refresh; và nhớ interceptor chỉ lo xác thực — uỷ quyền trên từng đối tượng vẫn phải kiểm trong handler.
Nguồn
- gRPC Go — Metadata: https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md
- gRPC — Authentication: https://grpc.io/docs/guides/auth/
- gRPC Go — PerRPCCredentials: https://pkg.go.dev/google.golang.org/grpc/credentials
Phần sau ta đo hiệu năng streaming: so sánh gửi nhiều message qua một stream với gọi nhiều unary call riêng lẻ, và flow control của HTTP/2 kiểm soát backpressure thế nào.