Check the same user token on gRPC as on HTTP.

Microservices can forward the raw bearer from the interceptor context.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-28 17:37:08 +02:00
co-authored by Cursor
parent f082561cc6
commit b344df5c1b
5 changed files with 179 additions and 5 deletions
+45
View File
@@ -0,0 +1,45 @@
package usertoken
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// UnaryServerInterceptor requires a bearer on every RPC.
// Success stores the Caller and the raw JWT on the handler context, same as
// [Auth.Middleware]. Native clients send metadata key "authorization".
// The HTTP gateway's forwarded header is "grpcgateway-authorization".
func (a *Auth) UnaryServerInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
raw, ok := bearerFromMetadata(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "unauthorized")
}
caller, err := a.Verify(ctx, raw)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "unauthorized")
}
return handler(withAuth(ctx, caller, raw), req)
}
}
func bearerFromMetadata(ctx context.Context) (string, bool) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", false
}
for _, key := range []string{"authorization", "grpcgateway-authorization"} {
vals := md.Get(key)
if len(vals) == 0 {
continue
}
if raw, ok := bearerToken(vals[0]); ok {
return raw, true
}
}
return "", false
}