grpc/grpc-go · error
unable to transfer serviceAccount PerRPCCredentials
Error message
unable to transfer serviceAccount PerRPCCredentials: %v
What it means
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.
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.
Example fix
// before
creds, _ := oauth.NewServiceAccountFromKey(jsonKey, scope)
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(creds))
// after
creds, _ := oauth.NewServiceAccountFromKey(jsonKey, scope)
conn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithPerRPCCredentials(creds)) Defensive patterns
Strategy: validation
Validate before calling
conn, err := grpc.Dial(addr,
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
grpc.WithPerRPCCredentials(serviceAccountCreds),
) Try / catch
if status.Code(err) == codes.Unauthenticated && strings.Contains(err.Error(), "serviceAccount PerRPCCredentials") {
log.Fatal("serviceAccount credential requires TLS transport")
} Prevention
- 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.
When it happens
Trigger: Using oauth.NewServiceAccountFromKey/File and dialing with insecure.NewCredentials(); a bundle downgrade dropping the transport below PrivacyAndIntegrity.
Common situations: Local dev with TLS off; misconfigured credentials bundle; mesh/proxy terminating TLS on the wrong hop.
Related errors
- unable to transfer jwtAccess PerRPCCredentials
- unable to transfer oauthAccess PerRPCCredentials
- unable to transfer TokenSource PerRPCCredentials
- cannot send secure credentials on an insecure connection
- credentials: cannot send secure credentials on an insecure…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/a332d2f582f50554.
Report an issue: GitHub.
Appendix: source
Thrown at credentials/oauth/oauth.go:171
type serviceAccount struct {
mu sync.Mutex
config *jwt.Config
t *oauth2.Token
}
func (s *serviceAccount) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {
s.mu.Lock()
defer s.mu.Unlock()
if !s.t.Valid() {
var err error
s.t, err = s.config.TokenSource(ctx).Token()
if err != nil {
return nil, err
}
}
ri, _ := credentials.RequestInfoFromContext(ctx)
if err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {
return nil, fmt.Errorf("unable to transfer serviceAccount PerRPCCredentials: %v", err)
}
return map[string]string{
"authorization": s.t.Type() + " " + s.t.AccessToken,
}, nil
}
func (s *serviceAccount) RequireTransportSecurity() bool {
return true
}
// NewServiceAccountFromKey constructs the PerRPCCredentials using the JSON key slice
// from a Google Developers service account.
func NewServiceAccountFromKey(jsonKey []byte, scope ...string) (credentials.PerRPCCredentials, error) {
config, err := google.JWTConfigFromJSON(jsonKey, scope...)
if err != nil {
return nil, err
}
return &serviceAccount{config: config}, nilView on GitHub (pinned to 0c51461d27)