{"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":"exception","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/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/oauth/oauth.go#L153-L189","documentation":"Thrown from serviceAccount.GetRequestMetadata (oauth.go:171) when CheckSecurityLevel fails for credentials built via oauth.NewServiceAccountFromKey/NewServiceAccountFromFile. The service-account flow mints an OAuth2 access token, which is a bearer secret, so it is rejected on a connection below PrivacyAndIntegrity. RequireTransportSecurity() returns true.","triggerScenarios":"Dialing with insecure.NewCredentials() (or no transport creds) while attaching service-account per-RPC credentials.","commonSituations":"Service-account auth configured for a dev environment that runs plaintext; refactored dial options that dropped TLS but kept per-RPC creds; copying tutorial code that omitted TLS.","solutions":["Pair the per-RPC service-account creds with TLS transport credentials.","On Google Cloud, use alts credentials or ComputeTokenSource over ALTS so the security level is satisfied.","Remove the per-RPC credentials if the channel is intentionally plaintext."],"exampleFix":"// before\ncreds, _ := oauth.NewServiceAccountFromFile(keyPath, scope)\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(insecure.NewCredentials()),\n    grpc.WithPerRPCCredentials(creds),\n)\n\n// after\ncreds, _ := oauth.NewServiceAccountFromFile(keyPath, scope)\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(caPool, \"\")),\n    grpc.WithPerRPCCredentials(creds),\n)","handlingStrategy":"validation","validationCode":"saCreds, err := oauth.NewServiceAccountFromFile(keyPath, scope)\nif err != nil { return err }\ntlsCreds := credentials.NewClientTLSFromCert(caPool, \"\")\nconn, err := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(tlsCreds),\n    grpc.WithPerRPCCredentials(saCreds),\n)","typeGuard":null,"tryCatchPattern":"if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {\n    if strings.Contains(st.Message(), \"serviceAccount PerRPCCredentials\") {\n        // channel is insecure; re-dial with TLS\n    }\n}","preventionTips":["Pair service-account per-RPC creds with TLS on every dial.","On GCE/GKE prefer alts or ComputeTokenSource to avoid this class entirely.","Static analysis: flag dials that pair serviceAccount creds with insecure transport."],"tags":["oauth","security","transport","call-credentials","go","service-account"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}