{"record":{"id":"cc3163aec8a9cca5","repo":"grpc/grpc-go","slug":"unable-to-transfer-tokensource-perrpccredentials","errorCode":null,"errorMessage":"unable to transfer TokenSource PerRPCCredentials: %v","messagePattern":"unable to transfer TokenSource PerRPCCredentials: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/oauth/oauth.go","lineNumber":48,"sourceCode":"\t\"golang.org/x/oauth2/google\"\n\t\"golang.org/x/oauth2/jwt\"\n\t\"google.golang.org/grpc/credentials\"\n)\n\n// TokenSource supplies PerRPCCredentials from an oauth2.TokenSource.\ntype TokenSource struct {\n\toauth2.TokenSource\n}\n\n// GetRequestMetadata gets the request metadata as a map from a TokenSource.\nfunc (ts TokenSource) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {\n\ttoken, err := ts.Token()\n\tif err != nil {\n\t\treturn nil, err\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 TokenSource PerRPCCredentials: %v\", err)\n\t}\n\treturn map[string]string{\n\t\t\"authorization\": token.Type() + \" \" + token.AccessToken,\n\t}, nil\n}\n\n// RequireTransportSecurity indicates whether the credentials requires transport security.\nfunc (ts TokenSource) RequireTransportSecurity() bool {\n\treturn true\n}\n\n// removeServiceNameFromJWTURI removes RPC service name from URI.\nfunc removeServiceNameFromJWTURI(uri string) (string, error) {\n\tparsed, err := url.Parse(uri)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tparsed.Path = \"/\"","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/oauth/oauth.go#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(insecure.NewCredentials()),\n    grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: ts}),\n)\n\n// after\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(caPool, \"\")),\n    grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: ts}),\n)","handlingStrategy":"validation","validationCode":"// Enforce: bearer per-RPC credentials ride on TLS.\ntlsCreds := credentials.NewTLS(&tls.Config{ServerName: host, RootCAs: caPool})\nconn, err := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(tlsCreds),\n    grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: ts}),\n)","typeGuard":null,"tryCatchPattern":"if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {\n    if strings.Contains(st.Message(), \"unable to transfer TokenSource PerRPCCredentials\") {\n        // channel is insecure; re-dial with TLS\n    }\n}","preventionTips":["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."],"tags":["oauth","security","transport","call-credentials","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}