1

I don't know if you can help me understand the right way forward with this issue. I need to provide a little bit of background first.

I have a VB.Net Console Utility that uses the Google V3 Calendar API. This utility has the following process to authenticate:

Private Function DoAuthentication(ByRef rStrToken As String, ByRef rParameters As OAuth2Parameters) As Boolean
    Dim credential As UserCredential
    Dim Secrets = New ClientSecrets() With {
        .ClientId = m_strClientID,
        .ClientSecret = m_strClientSecret
    }
    'm_Scopes.Add(CalendarService.Scope.Calendar)
    m_Scopes.Add("https://www.googleapis.com/auth/calendar https://www.google.com/m8/feeds/ https://mail.google.com/")

    Try
        credential = GoogleWebAuthorizationBroker.AuthorizeAsync(Secrets, m_Scopes,
                                                                 "user", CancellationToken.None,
                                                                 New FileDataStore("PublicTalkSoftware.Calendar.Application")).Result()

        ' Create the calendar service using an initializer instance
        Dim initializer As New BaseClientService.Initializer() With {
            .HttpClientInitializer = credential,
            .ApplicationName = "~~~~~~~~~~"
        }
        m_Service = New CalendarService(initializer)

        rStrToken = credential.Token.AccessToken.ToString()
        rParameters.AccessToken = credential.Token.AccessToken
        rParameters.RefreshToken = credential.Token.RefreshToken
    Catch ex As Exception
        ' We encountered some kind of problem, perhaps they have not yet authenticated?
        Return False
    End Try

    Return True
End Function

This part of the application process works fine. The data store file gets created and once the user has authenticated it all seems to just work find from there on. The user will be able to update the calendar without any further authenticating on there part.

Now, I also have a part of my MFC (the main application) project that sends emails for the user. This uses the following CkMainManW library.

For the most part that works too. If the user has correctly set up their credentials it is fine. However, if they are using GMail, then I do things slightly differently. This is to avoid the need to have the "Allow third party apps" option to be ticked in the Google account.

So, for GMail users, we send emails like this:

mailman.put_SmtpUsername(strUsername);
mailman.put_OAuth2AccessToken(strGoogleToken);

As you can see, I use the OAuth2AccessToken. This actual value passed is the credential.Token.AccessToken.ToString() value stored from when the user authenticated. Now, I have since understood that this actual token only lasts for one hour. This would explain why some users have to repeatedly run my calendar authentication again to get a new access token.

Clearly, when I do the calendar authentication which uses the data store file, it does something under the hood the avoid the user being asked all the time to authenticate.

Now, I have read this tutorial about using the Chilkat Library for this. I notice now that in the sample code it has a comment:

//  Now that we have the access token, it may be used to send as many emails as desired
//  while it remains valid.  Once the access token expires, a new access token should be
//  retrieved and used.

So, with all the background, how do I resolve my issue? So I have a data store file that contains the original access token from when they authorised and a refresh token. This file was created by the VB.Net command line module.

By the sounds of it, the Chilkat routine needs an access token that is valid. So, what is the right way for me to get an updated access token from the refresh token, so that when I send emails it won't fail after an hour?

Update

I am getting myself confused. I changed my code so that it called the DoAuthentification call above to get the refresh token and access token. But I am finding that the actual data store file is not getting revised. The text file is not being revised.

I have to revoke access and then do the authentication to get the data store file revised. And it is only once it has been revised that the access token will work for sending emails.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164

2 Answers2

2

I think I have found the solution. I saw this answer:

https://stackoverflow.com/a/33813994/2287576

Based on the answer I added this method:

Private Function RefreshAuthentication(ByRef rStrAccessToken As String, ByRef rStrRefreshToken As String) As Boolean
    Dim parameters As New OAuth2Parameters
    With parameters
        .ClientId = m_strClientID
        .ClientSecret = m_strClientSecret
        .AccessToken = rStrAccessToken ' Needed?
        .RefreshToken = rStrRefreshToken
        .AccessType = "offline"
        .TokenType = "refresh"
        .Scope = "https://www.googleapis.com/auth/calendar https://www.google.com/m8/feeds/ https://mail.google.com/"
    End With

    Try
        Google.GData.Client.OAuthUtil.RefreshAccessToken(parameters)
        rStrAccessToken = parameters.AccessToken
        rStrRefreshToken = parameters.RefreshToken
        RefreshAuthentication = True
    Catch ex As Exception
        RefreshAuthentication = False
    End Try
End Function

I am not sure if I need to pass in the existing access token or not before refreshing. But either way, the tokens get updated and I can proceed with sending emails.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
0

FYI, in the end it became apparent that I did not need any bespoke Refresh at all because the system manages it for you under the hood.

Private Async Function DoAuthenticationAsync() As Task(Of Boolean)
    Dim credential As UserCredential
    Dim Secrets = New ClientSecrets() With {
        .ClientId = m_strClientID,
        .ClientSecret = m_strClientSecret
    }

    Try
        credential = Await GoogleWebAuthorizationBroker.AuthorizeAsync(Secrets, m_Scopes,
                                                             "user", CancellationToken.None,
                                                             New FileDataStore("xxx.Calendar.Application"))

        ' Create the calendar service using an initializer instance
        Dim initializer As New BaseClientService.Initializer() With {
            .HttpClientInitializer = credential,
            .ApplicationName = "yy"
        }

        m_Service = New CalendarService(initializer)

    Catch ex As Exception
        ' We encountered some kind of problem, perhaps they have not yet authenticated?
        ' Can we isolate that as the exception?
        m_logger.Error(ex, "DoAuthenticationAsync")

        Return False
    End Try

    Return True
End Function

I have not required any bespoke Refresh of tokens for a long time now.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164