Search code examples
c#oauthgoogle-oauthgmail-apigoogle-api-dotnet-client

C# GoogleWebAuthorizationBroker for OAuth2 connection to GMail not work in windows service


My application is written with .NET Framework 4.7.2. The application I created must interface with a GMail mailbox to download mail (via POP3). To authenticate my application to the mailbox via OAuth2, I created an application on Google Cloud Platform, and from the application I authenticate via IdClient and IdSecret.

My application consists of two executable programs:

  • First exe program for visual configuration mode, where the user can indicate the parameters to access the application on the google cloud platform
  • Secondo exe program for automatic silent mode, in which the executable will be launched on a schedual basis from a windows service

To authenticate to the application created on the cloud platform, I use the Google.Apis.Auth NuGet package. To request the token I use the following code. This code is shared by both executable programs in my application

        UserCredential credential;
        credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
            new ClientSecrets
            {
                ClientId = "XXXXXXX",
                ClientSecret = "XXXXXXX"
            },
            new[] { "https://mail.google.com/" },
            "user",
            System.Threading.CancellationToken.None).Result;

        if (credential.Token.IsExpired(credential.Flow.Clock))
        {
            if (!(credential.RefreshTokenAsync(CancellationToken.None).Result))
            {
                CrmEMFunc.LogMessage("Access token can't be refreshed");
                return;
            }
        }

During the first installation, the configuration application (visual application) will run, so that the user can enter all the parameters necessary for authentication. When the connection configuration is saved for the first time, the browser will be opened to request consent to access emails via the application created on the cloud platform.

Once this configuration phase has been successfully completed, a token will be generated which can be used or refreshed as long as it is not revoked. For all future executions it will therefore no longer be necessary to view the browser to request consent.

Reopen visual exe program, it will ask for credentials as many times as necessary, without opening the browser anymore.

But when I run the non visual exe program from the windows service, when I get to the credential request code (code shown above), it hangs. The token is valid and active, so the browser shouldn't open, and the application should be able to proceed without user iteration.

I also tried using the await or methods GetAwaiter().GetResult() methods

credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(...)
        

or

credential = GoogleWebAuthorizationBroker.AuthorizeAsync(...).GetAwaiter().GetResult()

But nothing has changed, the application keeps hanging.

On the windows service I activated the option "Allow the service to interact with the desktop" (service > properties > connection > Local system account), but the problem continues to occur.

In the non-visual exe program options, I have tried setting the output type for the application (Application > Output Type) as either "Console Application" or "Windows Application," but in both cases it does not change the situation

Any ideas on how to solve this problem?


Solution

  • Well what a great question.

    First off you need to understand how GoogleWebAuthorizationBroker stores the user credentials.

    Lets look at a little more advanced version then what you have.

    credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
          GoogleClientSecrets.Load(stream).Secrets,
          new[] { DriveService.Scope.Drive,  DriveService.Scope.DriveFile },
          "LookIAmAUniqueUser",
           CancellationToken.None,
          new FileDataStore("Drive.Auth.Store")                               
          ).Result;
    

    By default FileDataStore stores the users authorization credentials %AppData% App data is different for each user. So when your app runs the first time and you authorize it the credentials are stored in the example above %AppDatat%\Drive.Auth.Store

    When you run as a windows service the code is run as a service user and not the user who originally authorized your code. So the service cant find the authorization and it will try to open the consent screen again which it cant as its a service.

    Solution is to supply FileDataStore with a directory that the windows service will have access to. Then authorize it again the file will be created with the credentials, then when the service runs it will have access.

    new FileDataStore(@"c:\datastore",true)   
    

    refresh token note

    If your app is still in testing phase then your refresh token is going to expire in seven days.