grpc/grpc-go · error
unable to transfer TokenSource PerRPCCredentials: %v
Error message
unable to transfer TokenSource PerRPCCredentials: %v
What it means
Thrown from oauth.TokenSource.GetRequestMetadata (oauth.go:48) when CheckSecurityLevel fails. oauth.TokenSource wraps an oauth2.TokenSource and injects the access token as an Authorization header; because that token is a reusable bearer secret, gRPC will not attach it to a connection below PrivacyAndIntegrity. RequireTransportSecurity() returns true.
Source
Thrown at credentials/oauth/oauth.go:48
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
"google.golang.org/grpc/credentials"
)
// TokenSource supplies PerRPCCredentials from an oauth2.TokenSource.
type TokenSource struct {
oauth2.TokenSource
}
// GetRequestMetadata gets the request metadata as a map from a TokenSource.
func (ts TokenSource) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {
token, err := ts.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 TokenSource PerRPCCredentials: %v", err)
}
return map[string]string{
"authorization": token.Type() + " " + token.AccessToken,
}, nil
}
// RequireTransportSecurity indicates whether the credentials requires transport security.
func (ts TokenSource) RequireTransportSecurity() bool {
return true
}
// removeServiceNameFromJWTURI removes RPC service name from URI.
func removeServiceNameFromJWTURI(uri string) (string, error) {
parsed, err := url.Parse(uri)
if err != nil {
return "", err
}
parsed.Path = "/"View on GitHub (pinned to 03255a9237)
Solutions
- Pair the per-RPC oauth creds with TLS transport credentials (credentials.NewTLS / NewClientTLSFromFile).
- On GCE/GKE prefer alts.NewClientCreds so the security level is satisfied without managing certs.
- Remove the per-RPC credentials if the channel is intentionally plaintext.
Example fix
// before
conn, _ := grpc.NewClient(addr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: ts}),
)
// after
conn, _ := grpc.NewClient(addr,
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(caPool, "")),
grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: ts}),
) Defensive patterns
Strategy: validation
Validate before calling
// Enforce: bearer per-RPC credentials ride on TLS.
tlsCreds := credentials.NewTLS(&tls.Config{ServerName: host, RootCAs: caPool})
conn, err := grpc.NewClient(addr,
grpc.WithTransportCredentials(tlsCreds),
grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: ts}),
) Try / catch
if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {
if strings.Contains(st.Message(), "unable to transfer TokenSource PerRPCCredentials") {
// channel is insecure; re-dial with TLS
}
} Prevention
- Always pass credentials.NewTLS(...) on dial options that include oauth.TokenSource.
- Add a linter/code-review check forbidding insecure creds in files that import oauth.
- Use NewApplicationDefault on Google Cloud so the same dial always pairs with TLS.
When it happens
Trigger: Using grpc.WithPerRPCCredentials(oauth.TokenSource{...}) on a channel created with insecure.NewCredentials() or with no transport credentials, so the negotiated security level is below PrivacyAndIntegrity.
Common situations: Reusing an oauth2 token source from a Google client library on a plaintext dev channel; dropping TLS during debugging; wiring ADC (NewApplicationDefault) result onto an insecure dial.
Related errors
- unable to transfer jwtAccess PerRPCCredentials: %v
- unable to transfer oauthAccess PerRPCCredentials: %v
- unable to transfer serviceAccount PerRPCCredentials: %v
- unable to transfer STS PerRPCCredentials: %v
- cannot send secure credentials on an insecure connection: %v
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/cc3163aec8a9cca5.
Report an issue: GitHub.