{"record":{"id":"a332d2f582f50554","repo":"grpc/grpc-go","slug":"unable-to-transfer-serviceaccount-perrpccredential","errorCode":null,"errorMessage":"unable to transfer serviceAccount PerRPCCredentials: %v","messagePattern":"unable to transfer serviceAccount PerRPCCredentials: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/oauth/oauth.go","lineNumber":171,"sourceCode":"type serviceAccount struct {\n\tmu     sync.Mutex\n\tconfig *jwt.Config\n\tt      *oauth2.Token\n}\n\nfunc (s *serviceAccount) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif !s.t.Valid() {\n\t\tvar err error\n\t\ts.t, err = s.config.TokenSource(ctx).Token()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tri, _ := credentials.RequestInfoFromContext(ctx)\n\tif err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to transfer serviceAccount PerRPCCredentials: %v\", err)\n\t}\n\treturn map[string]string{\n\t\t\"authorization\": s.t.Type() + \" \" + s.t.AccessToken,\n\t}, nil\n}\n\nfunc (s *serviceAccount) RequireTransportSecurity() bool {\n\treturn true\n}\n\n// NewServiceAccountFromKey constructs the PerRPCCredentials using the JSON key slice\n// from a Google Developers service account.\nfunc NewServiceAccountFromKey(jsonKey []byte, scope ...string) (credentials.PerRPCCredentials, error) {\n\tconfig, err := google.JWTConfigFromJSON(jsonKey, scope...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &serviceAccount{config: config}, nil","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/credentials/oauth/oauth.go#L153-L189","documentation":"Returned by serviceAccount.GetRequestMetadata when CheckSecurityLevel finds the transport below PrivacyAndIntegrity. The serviceAccount credential (built by NewServiceAccountFromKey/FromFile) exchanges a JWT for an OAuth2 access token, which gRPC will not transmit over an insecure channel. The %v is the security-level error.","triggerScenarios":"Using oauth.NewServiceAccountFromKey/File and dialing with insecure.NewCredentials(); a bundle downgrade dropping the transport below PrivacyAndIntegrity.","commonSituations":"Local dev with TLS off; misconfigured credentials bundle; mesh/proxy terminating TLS on the wrong hop.","solutions":["Dial with credentials.NewTLS(&tls.Config{}).","Use a self-signed cert for local testing rather than insecure.NewCredentials().","Run the credential on the secure hop when behind a TLS-terminating proxy."],"exampleFix":"// before\ncreds, _ := oauth.NewServiceAccountFromKey(jsonKey, scope)\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(creds))\n// after\ncreds, _ := oauth.NewServiceAccountFromKey(jsonKey, scope)\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithPerRPCCredentials(creds))","handlingStrategy":"validation","validationCode":"conn, err := grpc.Dial(addr,\n    grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),\n    grpc.WithPerRPCCredentials(serviceAccountCreds),\n)","typeGuard":null,"tryCatchPattern":"if status.Code(err) == codes.Unauthenticated && strings.Contains(err.Error(), \"serviceAccount PerRPCCredentials\") {\n    log.Fatal(\"serviceAccount credential requires TLS transport\")\n}","preventionTips":["Pair NewServiceAccountFromKey/File with TLS transport credentials.","Use self-signed certs for local development.","Audit dial sites to ensure no insecure.NewCredentials() is paired with serviceAccount creds."],"tags":["grpc","oauth","service-account","tls","security","credentials"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}