{"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":"validation","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/0c51461d27177d997e14c642fe18c11668fc09a3/credentials/oauth/oauth.go#L30-L66","documentation":"Returned by oauth.TokenSource.GetRequestMetadata when CheckSecurityLevel finds the transport is below PrivacyAndIntegrity. TokenSource carries OAuth2 access tokens, so gRPC refuses to send them over an insecure channel. The %v is the security-level error. This is the canonical 'use TLS' error for oauth.TokenSource.","triggerScenarios":"Dialing with insecure.NewCredentials() while supplying oauth.TokenSource (or oauth.NewComputeEngine / NewApplicationDefault) as the per-RPC credential; a bundle that downgrades transport security.","commonSituations":"Quick local testing with plaintext; misconfigured dev environment; proxy terminating TLS.","solutions":["Dial with credentials.NewTLS(&tls.Config{}) or a secure bundle.","For local testing, generate a self-signed cert and use credentials.NewTLS with the appropriate RootCAs.","Ensure RequireTransportSecurity()==true is honored end-to-end."],"exampleFix":"// before\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(oauth.TokenSource{ts}))\n// after\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithPerRPCCredentials(oauth.TokenSource{ts}))","handlingStrategy":"validation","validationCode":"conn, err := grpc.Dial(addr,\n    grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),\n    grpc.WithPerRPCCredentials(oauth.TokenSource{TokenSource: ts}),\n)","typeGuard":null,"tryCatchPattern":"if status.Code(err) == codes.Unauthenticated && strings.Contains(err.Error(), \"PerRPCCredentials\") {\n    log.Fatal(\"oauth.TokenSource requires a TLS-secured transport\")\n}","preventionTips":["Always pair oauth.TokenSource with TLS transport credentials.","Use self-signed certs for local dev.","Audit dial sites for insecure.NewCredentials() in code paths that register oauth creds."],"tags":["grpc","oauth","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"}