1

I am debugging a console application that uses Oauth2 credentials, and the service provider requires that I specify a redirect URI. I specify the redirect URI in the service provider's Web portal. The service is Google Analytics.

I have successfully tested the service with a Node.js application. In the service's configuration, I have specified a redirect URI of http://localhost:8080. The Node application actually starts on port 8080 by default, so I can simply run http-server, browse to http://localhost:8080, call the service, and see the response with data.

However, when I try to call the same service with a C# console application, I receive an HTTP 400 error in the browser. The error states:

The redirect URI in the request, http://localhost:59291/authorize/, does not match the ones authorized for the OAuth client.

Each time I try to run the application, the port changes. Here is the code (referenced from Analytics Reporting API V4 Client Library for .NET):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Google.Apis.AnalyticsReporting.v4;
using Google.Apis.AnalyticsReporting.v4.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System.Threading;

namespace Google_Analytics_API_Test
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var credential = GetCredential().Result;
                using (var svc = new AnalyticsReportingService(
                    new BaseClientService.Initializer
                    {
                        HttpClientInitializer = credential,
                        ApplicationName = "Google Analytics API Console"
                    }))
                {
                    var dateRange = new DateRange
                    {
                        StartDate = "2017-04-24",
                        EndDate = "today"
                    };
                    var sessions = new Metric
                    {
                        Expression = "ga:sessions",
                        Alias = "Sessions"
                    };
                    var date = new Dimension { Name = "ga:date" };

                    var reportRequest = new ReportRequest
                    {
                        DateRanges = new List<DateRange> { dateRange },
                        Dimensions = new List<Dimension> { date },
                        Metrics = new List<Metric> { sessions },
                        ViewId = "<<viewID>>"
                    };

                    var getReportsRequest = new GetReportsRequest
                    {
                        ReportRequests = new List<ReportRequest> {    reportRequest }
                    };
                    var batchRequest =    svc.Reports.BatchGet(getReportsRequest);
                    var response = batchRequest.Execute();
                    foreach (var x in response.Reports.First().Data.Rows)
                    {
                        Console.WriteLine(string.Join(",", x.Dimensions) +
                            " " + string.Join(", ",    x.Metrics.First().Values));
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

            static async Task<UserCredential> GetCredential()
            {
                using (var stream = new FileStream("client_secret.json",
                    FileMode.Open, FileAccess.Read))
                {
                    const string loginEmailAddress = "<<emailAddress>>";
                return await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    new[] { AnalyticsReportingService.Scope.Analytics },
                    loginEmailAddress, CancellationToken.None,
                    new FileDataStore("GoogleAnalyticsApiConsole"));
                }
           }
       }
    }

I have searched for a way to configure IIS Express with a static port for debugging, but I'm not sure that IIS is even involved here - rather than just something like TcpClient / TcpListener objects that are implemented by the Google Analytics library?

Is there a way to specify a port for async / await requests in a C# .NET console application?

Community
  • 1
  • 1
Rocky Raccoon
  • 243
  • 3
  • 14
  • Have you followed all of the *configuration* steps, including generating and setting the json file? OAuth Callbacks are meant for web site applications, not for callbacks. It's the way the OAuth provider is telling your "web site" that `I just authenticated a user with this token, let him pass`. Obviously this is meaningless for desktop client applications. – Panagiotis Kanavos May 08 '17 at 15:48
  • The answer you linked to obviously works and *doesn't* result in callbacks. Create a new project and follow the steps as described in that answer, making sure you complete all configuration steps, set all constants to the correct values, write the correct settings to the json file etc – Panagiotis Kanavos May 08 '17 at 15:52

1 Answers1

0

The issue here, obviously, is that your request is going out through an HttpClient; the Google.Apis.Http.HttpClientfactory returns this via ConfigurableHttpClient. The HttpClient does not expose the methods to the underlying ServicePoint to set the local bound IP address (more information at bottom)

The best way to resolve this is to make sure that you are using the Credentials in the correct manner. Since you are connecting from a Console application you can easily set these credentials to Other, which is in fact the preferred way. try looking at the "Create Credentials" button and select the "Help Me Choose", which can guide you through Scenarios. Otherwise, just create as below.

GoogleAPI Credentials, Choose Other

On the suggestion of @panagiotis-kanavos, I will add the clarification:

HttpClient does not expose access to ServicePoint. You could use reflection to access the underlying ServicePoint object. It may provide a resolution to your attempts to bind to a specifical local address if you do not want to use console API credentials withing Google API for Analytics

To do this, please examine this question: Use httpclient to send a request from a specific IP address

As far as I am aware, the BaseClientService will only allow you to set a custom HttpFactory or a custom HttpClientInitializer (modify post-create), so you would need to create your a custom HttpFactory instance returning the ConfigurableHttpClient modified as above.

  1. GoogleApi HttpClientFactory
  2. GoogleApi ConfigurableHttpClient

Once you've done that you pass your custom HttpClientFactory through.

 using (var svc = new AnalyticsReportingService(
     new BaseClientService.Initializer
 {
     HttpClientInitializer = credential,
     ApplicationName = "Google Analytics API Console",
     HttpClientFactory = new CustomHttpClientFactory()
 }))
Community
  • 1
  • 1
LimpingNinja
  • 618
  • 1
  • 5
  • 16
  • I'm not sure this makes any sense. HttpClient uses HttpWebRequest. IPEndpoint is just an *address*, it's not a listener. Even if you did create an HttpListener, you wouldn't be able to receive calls anyway because any (if not all) of the intermediate routers would block access to the port. If you want to create an Http*Listener* it doesn't matter how you make outbound requests anyway – Panagiotis Kanavos May 08 '17 at 15:35
  • WebRequest uses the ServicePoint manager, so you can actually bind to an IPEndpoint, since this is not even possible in his situation, I'm unsure why there needs additional clarification, but I have added it. – LimpingNinja May 08 '17 at 15:39
  • and so does HttpClient, which uses WebRequest. An [IPEndpoint](https://msdn.microsoft.com/en-us/library/system.net.ipendpoint(v=vs.110).aspx) is just the **address* it doesn't bind to anything, doesn't listen to anything. What *listens* is the [HttpListener](https://msdn.microsoft.com/en-us/library/system.net.httplistener(v=vs.110).aspx) – Panagiotis Kanavos May 08 '17 at 15:41
  • Yes, but HttpClient does not expose ServicePoint; you could use reflection to access the ServicePoint method of HttpWebRequest; but how would this provide an answer when he could simply rectify his use of the credentials? If he set the API Oauth credentials to accept calling from his console client he would have no concerns. – LimpingNinja May 08 '17 at 15:47
  • @Kaylus The Oauth2 credentials work with a Node.js application. Previously, I tried this same console application with a Service Account, similar to your suggestion. It failed too, but for reasons that are beyond the scope of this question. Respectfully, I disagree that I need to change the type of credentials in this case. Even if I do, I am still left with my original question about specifying a port for async / await requests. I still believe that I can use the Oauth2 credential, if I can specify the port for the Tcp / HttpListener. – Rocky Raccoon May 08 '17 at 15:54
  • @RockyRaccoon The NodeJS application is not using the C# HttpClient, but I will edit the answer for you if you really want to go this route. – LimpingNinja May 08 '17 at 15:56
  • @Kaylus I understand the Node application is running a Web server that is listening on a static port. If there's a way to specify a port for HttpClient / HttpListener, I would like to know and to try it. However, if it becomes unfeasible, I will revisit the Service Account method and let you know what happens. For now, though, I do want to go this route and would appreciate the edit along those lines if you would provide it? – Rocky Raccoon May 08 '17 at 16:02
  • @RockyRaccoon I've modified the answer with both suggestions. – LimpingNinja May 08 '17 at 16:38
  • 1
    @Kaylus Thank you for providing the additional information and clear explanations to help me understand what is happening in my .NET console application. The solution you provided is interesting, and it does illustrate the complexity of my initial approach versus the simplicity of following your original guidance to change the type of credential in Google Analytics. Using another credential is the best solution to my problem (in every way), but I do now understand what is going on with the async / await requests much better and greatly appreciate your help. – Rocky Raccoon May 08 '17 at 16:49