Search code examples
c#oauth-2.0gmail-apigmail-imap

Expiry time of a login token through gmail api


I have a testbed application that runs constantly to download amount of messages in the Inbox of a gmail account. I connect through gmail-api to request a login token and login to the account. The problem I am facing is that on each iteration the expiry time of the access token is 3600s, i.e. it never changes. Do I need to re-download the access token each time to see if it needs refreshing or does the api handle this for me?

ImapClient is part of the AE.Net library.

See the below code:

try
{
    X509Certificate2 certificate = new X509Certificate2("certificate.p12", "notasecret", X509KeyStorageFlags.Exportable);
    ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential
        .Initializer("Service account address")
    {
        //Note: other scopes can be found here: https://developers.google.com/gmail/api/auth/scopes
        Scopes = new[] { "https://mail.google.com/" },
        User = "email address"
    }.FromCertificate(certificate));

    Task<bool> result = credential.RequestAccessTokenAsync(CancellationToken.None);
    if (!result.Result)
    {
        Console.WriteLine("Failed to connect to gmail!");
    }

    using (ImapClient imap = new ImapClient())
    {
        imap.AuthMethod = ImapClient.AuthMethods.SaslOAuth;

        imap.Connect("imap.gmail.com", 993, true, false);
        if (!imap.IsConnected)
        {
            throw new Exception("Unable to connect to the host");
        }

        imap.Login("email address", credential.Token.AccessToken);
        if (!imap.IsAuthenticated)
        {
            throw new Exception("Currently not authenticated (2)?");
        }

        string sourceMailBox = "Inbox";
        imap.SelectMailbox(sourceMailBox);

        while (true)
        {
            int messageCount = imap.GetMessageCount(sourceMailBox);

            Console.WriteLine("Messages in '" + sourceMailBox + "': " + messageCount);
            Console.WriteLine("Token expires in {0}s", credential.Token.ExpiresInSeconds);

            System.Threading.Thread.Sleep(10000);
        }
    }
}
catch(Exception ex)
{
    Console.Error.WriteLine("IMAP Exception: " + ex.Message);
    Console.Error.WriteLine(ex.ToString());
}

Example output (Note I am not writing out the GetResponse lines):

GetResponse(): '* STATUS "Inbox" (MESSAGES 1)'

GetResponse(): 'xm070 OK Success'

Messages in 'Inbox': 1

Token expires in 3600s

GetResponse(): '* STATUS "Inbox" (MESSAGES 1)'

GetResponse(): 'xm071 OK Success'

Messages in 'Inbox': 1

Token expires in 3600s

After running for about 3100 seconds it gave me the following exception which I believe to be due to the token expiring.

IMAP Exception: Unable to write data to the transport connection: An established connection was aborted by the software in your host machine.

System.IO.IOException: Unable to write data to the transport connection: An established connection was aborted by the software in your host machine. ---> System.Net.Sockets.SocketException: An established connection was aborted by the software in your host machine

at System.Net.Sockets.Socket.Send(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)

at System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size)

--- End of inner exception stack trace ---

at System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size)

at System.Net.Security._SslStream.StartWriting(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)

at System.Net.Security._SslStream.ProcessWrite(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)

at System.Net.Security.SslStream.Write(Byte[] buffer, Int32 offset, Int32 count)

at AE.Net.Mail.TextClient.SendCommand(String command)

at AE.Net.Mail.ImapClient.OnLogout()

at AE.Net.Mail.TextClient.Logout()

at AE.Net.Mail.TextClient.Disconnect()

at AE.Net.Mail.TextClient.Dispose(Boolean disposing)

at AE.Net.Mail.ImapClient.Dispose(Boolean disposing)

at AE.Net.Mail.TextClient.Dispose()

at GmailOAuth.Program.DoWork() in

c:\Projects\Development\EmailSystem\GmailOAuth\GmailOAuth\Program.cs:line 82


Solution

  • As specified in the Documentation Token.ExpiresInSeconds can provide you the Expiry time for a Token and its value is not updated automatically. If you want to know the time left to refresh the token you have to find difference between when the token is issued (Token.Issued) and current time(Datetime.Now) and check whether its less than Expiry time of token.

    //calculate time to expire by finding difference between current time and time when token is issued
    
    if((DateTime.Now-credential.Token.Issued).TotalSeconds>credential.Token.ExpiresInSeconds) 
    {
        //refresh your token
    } 
    else 
    {
        //your method call
    }
    

    You can also use Token.IsExpired() method to check whether the token is expired.

    if (credential.Token.IsExpired(credential.Flow.Clock))
    {
        //refresh your token
    } 
    else 
    {
        //your method call
    }