OAuth 2.0 for Google Regular Accounts (installed applications)

You can create a Windows application which can access Gmail account of a user via IMAP and SMTP without knowing the password of this user. Note that in case of Google Apps you can also use Service Accounts for that (Service Accounts approach is much easier to use).

Google might be very restrictive in authorizing your app for SMTP/IMAP access via OAuth 2.0. You may have to spend some time and efforts to convince their techsupport that your app indeed needs IMAP/SMTP and cannot use their proprietary REST API for that.

.NET Core and UWP/UAP notes

As of May 2019, the current version of Google API requires .NET Standard 2.0 at least (available for UWP starting from Windows 10 Fall Creators Update). UWP for older versions of Windows (e.g. .NET Standard 1.3) won't work.

In UWP, you must use async methods and use JSON file for storing client secrets (cannot specify them directly in code). See example in UWP Edition (Universal Windows, UAP 10.0) topic.

Or, you can use no Google libraries at all. Refer to OAuth 2.0 for Universal Windows apps guide and My Documents\MailBee.NET Objects\Samples\WinForms\.NET Core 2.0\C#\OAuthUniversalApp project on how to use OAuth to authenticate against Google and Microsoft and then check/send mail on their servers.

Register Google project

First, you need to register a project in your Google Developer Console:

Now, at "API Manager/Credentials/OAuth consent screen" section specify your support e-mail address and your product name. People will see this info when authorizing your application on access to your mailbox:

Click Save.

Then, at "API Manager/Credentials/Credentials" section click Create Credentials and select OAuth Client ID. Create a new Client ID selecting "Other" type, you can leave any name for client ID:

Now you have OAuth credentials:

Enable access to Google e-mail address, IMAP and SMTP services

You will also need to go to "API Manager/Library" and locate "Gmail API" and "Google+ API":

Click and enable "Gmail API" to let your application authenticate in Gmail services with XOAUTH2 (OAuth 2.0 extension for IMAP and SMTP):

Finally, repeat the same for "Google+ API" to let your application see the e-mail address of the user:

There can be a few minutes delay on Google end for these changes to take effect.

Create application in Visual Studio

Now create a console application in Visual Studio. The same approach will work for Windows Forms applications as well, see GoogleOAuthWinForms sample.

Then proceed to adding the required Google APIs dependencies. You can use NuGet console or NuGet user interface for that (whatever you prefer).

The sample does not use the helper ExternalProviders library (like it's done in web apps version of OAuth 2.0 tutorial). The library uses ASP.NET Identity database as storage of access tokens and related data but we're not using that database in the sample for this article.

Add Google APIs references to your Visual Studio project (NuGet console)

First, install Google APIs .NET library packages. The sample in this topic was tested in Visual Studio 2013 (.NET 4.5.1), 2015 (.NET 4.6), and 2017 (up to .NET Core 2.2).

Install-Package Google.Apis.Auth
Install-Package Google.Apis.Oauth2.v2

Google.Apis.Oauth2.v2 package is required only if you need to get the e-mail address of the user (i.e. if you can't get it by other means). The sample below asks Google for the user's e-mail address and thus uses this library.

NuGet may install not the latest versions when auto-resolving dependencies, that's why post-update may be required. After adding the packages, it's recommended to run:

Update-Package

packages.config should finally contain these Google entries:

Add Google APIs references to your Visual Studio project (NuGet UI)

If you prefer UI version of NuGet rather than its console, then open "Manage NuGet Packages" of your project, search and select:

"Google.Apis.Oauth2.v2 Client Library" is required only if you need to get the e-mail address of the user (i.e. if you can't get it by other means). The sample below asks Google for the user's e-mail address and thus uses this library.

In case if you're targeting .NET Core 2.0+ or UWP, you'll also need to install System.Text.Encoding.CodePages package. Also, Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) must be called before accessing any MailBee classes. This initializes codepages support in .NET Core runtime.

Also, there are some NuGet dependencies for these libraries and you may need to update NuGet and Visual Studio themselves. So, make sure you have resolved all the references, installed all the updates for both Visual Studio components and for the packages referenced by this project. Otherwise, you may encounter misc. compilation or runtime errors. The picture below lists all the packages used in the sample code for this article:

Open "Manage NuGet Packages for Solution" once again to check if Update All command appeared, and run it if available.

Add MailBee.NET Objects reference

In NuGet console, run:

Install-Package MailBee.NET

Alternatively, you can use Add Reference to plug MailBee.NET.dll to your project. It resides in Assemblies/Framework/Extensions in case if MailBee.NET Objects is fully installed in the system, or use Browse if you have just the .DLL file.

Using MailBee.NET and Google API to access a user's e-mail account

That's what we need to do in order to get access to the user's e-mails:

Now, here is the code which lists the message count in the user's Gmail Inbox.

using System;
using System.Threading;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Oauth2.v2;
using Google.Apis.Oauth2.v2.Data;
using Google.Apis.Services;
using MailBee;
using MailBee.ImapMail;

// This program shows how to issue, refresh and use Google access tokens for IMAP access to a Gmail mailbox.
//
// To run this program, select "GmailEasyLogin" in Project / project's Properties / Application tab / Startup object.
public class GmailEasyLogin
{
	public static void Main(string[] args)
	{
		// Request Gmail IMAP/SMTP scope and the e-mail address scope.
		string[] scopes = new string[] { "https://mail.google.com/", "https://www.googleapis.com/auth/userinfo.email" };

		Console.WriteLine("Requesting authorization");
		UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
			new ClientSecrets
			{
				ClientId = "Your Client ID, like 0123-abcd.apps.googleusercontent.com",
				ClientSecret = "Your Client Secret, like 0a1b2c3d",
			},
			 scopes,
			 "user",
			 CancellationToken.None).Result;
		Console.WriteLine("Authorization granted or not required (if the saved access token already available)");

		if (credential.Token.IsExpired(credential.Flow.Clock))
		{
			Console.WriteLine("The access token has expired, refreshing it");
			if (credential.RefreshTokenAsync(CancellationToken.None).Result)
			{
				Console.WriteLine("The access token is now refreshed");
			}
			else
			{
				Console.WriteLine("The access token has expired but we can't refresh it :(");
				return;
			}
		}
		else
		{
			Console.WriteLine("The access token is OK, continue");
		}

		Console.WriteLine("Requesting the e-mail address of the user from Google");

		// Sometimes, you may also need to set Initializer.ApplicationName property.
		// In our tests, setting just Initializer.HttpClientInitializer was enough for Google.
		Oauth2Service oauthService = new Oauth2Service(
			new BaseClientService.Initializer() { HttpClientInitializer = credential });

		// Userinfo.Get may crash if you run the app under debugger (a bug in Google API).
		// If this happens, use "Start without debugging" instead.
		Userinfo userInfo = oauthService.Userinfo.Get().ExecuteAsync().Result;
		string userEmail = userInfo.Email;
		Console.WriteLine("E-mail address is " + userEmail);

		// Build XOAUTH2 token. Can be used with Gmail IMAP or SMTP.
		string xoauthKey = OAuth2.GetXOAuthKeyStatic(userEmail, credential.Token.AccessToken);

		// Uncomment and set your key if you haven't specified it in app.config or Windows registry.
		// MailBee.Global.LicenseKey = "Your MNXXX-XXXX-XXXX key here";

		// Finally, use MailBee.NET to list the number of e-mails in Inbox.

		Imap imp = new Imap();

		// Logging is not necessary but useful for debugging.
		imp.Log.Enabled = true;
		imp.Log.Filename = @"C:\Temp\log.txt";
		imp.Log.HidePasswords = false;
		imp.Log.Clear();

		imp.Connect("imap.gmail.com");

		// This is the where IMAP XOAUTH2 actually occurs.
		imp.Login(null, xoauthKey, AuthenticationMethods.SaslOAuth2,
			MailBee.AuthenticationOptions.None, null);

		// If we're here, we're lucky.
		imp.SelectFolder("INBOX");
		Console.WriteLine(imp.MessageCount.ToString() + " e-mails in Inbox");
		imp.Disconnect();
	}
}
Imports System.Threading
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Oauth2.v2
Imports Google.Apis.Oauth2.v2.Data
Imports Google.Apis.Services
Imports MailBee
Imports MailBee.ImapMail

' This program shows how to issue, refresh and use Google access tokens for IMAP access to a Gmail mailbox.
'
' To run this program, select "GmailEasyLogin" in Project / project's Properties / Application tab / Startup object.
Public Class GmailEasyLogin
	Public Shared Sub Main(args As String())
		' Request Gmail IMAP/SMTP scope and the e-mail address scope.
		Dim scopes As String() = New String() {"https://mail.google.com/", "https://www.googleapis.com/auth/userinfo.email"}

		Console.WriteLine("Requesting authorization")
		Dim credential As UserCredential = GoogleWebAuthorizationBroker.AuthorizeAsync(New ClientSecrets() With { _
			.ClientId = "Your Client ID, like 0123-abcd.apps.googleusercontent.com", _
			.ClientSecret = "Your Client Secret, like 0a1b2c3d" _
		}, scopes, "user", CancellationToken.None).Result
		Console.WriteLine("Authorization granted or not required (if the saved access token already available)")

		If credential.Token.IsExpired(credential.Flow.Clock) Then
			Console.WriteLine("The access token has expired, refreshing it")
			If credential.RefreshTokenAsync(CancellationToken.None).Result Then
				Console.WriteLine("The access token is now refreshed")
			Else
				Console.WriteLine("The access token has expired but we can't refresh it :(")
				Return
			End If
		Else
			Console.WriteLine("The access token is OK, continue")
		End If

		Console.WriteLine("Requesting the e-mail address of the user from Google")

		' Sometimes, you may also need to set Initializer.ApplicationName property.
		' In our tests, setting just Initializer.HttpClientInitializer was enough for Google.
		Dim oauthService As New Oauth2Service(New BaseClientService.Initializer() With { _
			.HttpClientInitializer = credential _
		})

		' Userinfo.Get may crash if you run the app under debugger (a bug in Google API).
		' If this happens, use "Start without debugging" instead.
		Dim userInfo As Userinfo = oauthService.Userinfo.Get().ExecuteAsync().Result
		Dim userEmail As String = userInfo.Email
		Console.WriteLine("E-mail address is " & userEmail)

		' Build XOAUTH2 token. Can be used with Gmail IMAP or SMTP.
		Dim xoauthKey As String = OAuth2.GetXOAuthKeyStatic(userEmail, credential.Token.AccessToken)

		' Uncomment and set your key if you haven't specified it in app.config or Windows registry.
		' MailBee.Global.LicenseKey = "Your MNXXX-XXXX-XXXX key here"

		' Finally, use MailBee.NET to list the number of e-mails in Inbox.

		Dim imp As New Imap()

		' Logging is not necessary but useful for debugging.
		imp.Log.Enabled = True
		imp.Log.Filename = "C:\Temp\log.txt"
		imp.Log.HidePasswords = False
		imp.Log.Clear()

		imp.Connect("imap.gmail.com")

		' This is the where IMAP XOAUTH2 actually occurs.
		imp.Login(Nothing, xoauthKey, AuthenticationMethods.SaslOAuth2, MailBee.AuthenticationOptions.None, Nothing)

		' If we're here, we're lucky.
		imp.SelectFolder("INBOX")
		Console.WriteLine(imp.MessageCount & " e-mails in Inbox")
		imp.Disconnect()
	End Sub
End Class

The code does not use async methods of MailBee.NET library. You can see async version in GoogleOAuthWinForms project. See Sample projects overview for details.

Clearing or revoking tokens

You may sometimes need to remove tokens you received from Google, for different reasons:

The sample code below will demonstrate both methods.

Advanced sample

The sample below adds:

using System;
using System.Threading;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Util.Store;
using Google.Apis.Services;

// These two are required only if you want to get the e-mail address of the user (with Oauth2Service class).
using Google.Apis.Oauth2.v2;
using Google.Apis.Oauth2.v2.Data;

using MailBee;
using MailBee.ImapMail;
using MailBee.SmtpMail;

// This program shows how to issue, refresh, delete, revoke and use Google access tokens for IMAP and SMTP access to a Gmail mailbox.
//
// To run this program, select "GmailLogin" in Project / project's Properties / Application tab / Startup object.
public class GmailLogin
{
	public static void Main(string[] args)
	{
		// You get these in Google Developer Console.
		string yourAppClientID = "Your Client ID, like 0123-abcd.apps.googleusercontent.com";
		string yourAppClientSecret = "Your Client Secret, like 0a1b2c3d";

		// If you want to clear the saved token (if any) to force Google to show authorization screen again,
		// set clearSavedToken to true. Note that the code below only removes the token from the local storage,
		// Google server still remembers it. Deleting a token just locally without revoking it on Google server
		// makes a little practical sense but can be useful for debugging, such as when your local token became
		// invalid: typically happens during development when you issue such many tokens that Google starts to forget
		// them and your local token no longer corresponds to its counterpart on Google server (even revoking such
		// token will fail).
		// If your purpose is to invalidate a working token everywhere (delete from both local storage and Google server),
		// use UserCredential.RevokeTokenAsync (for that, in the end of this sample, set revokeToken to true).
		bool clearSavedToken = false;
		if (clearSavedToken)
		{
			// If there is no saved token (e.g. Google authorization screen has never been shown for this user),
			// the code below will do nothing. No exception will occur. If the token is there, Google API will delete
			// C:\Users\<YourWindowsUser>\AppData\Roaming\Google.Apis.Auth\Google.Apis.Auth.OAuth2.Responses.TokenResponse-user
			// file (the exact path may vary depending on your OS and Google API version). Without the stored token
			// subsequent GoogleWebAuthorizationBroker.AuthorizeAsync call will open the browser window asking for
			// authorization.
			// Note that there still be a difference in authorization window between two situations: when
			// you actually request authorization for the first time and when you deleted the previous token locally
			// and now force Google to request authorization again. In second case Google still remembers that it already
			// authorized this application to access your user and it will just ask the user that the application wants
			// Offline access. In the first case Google will ask for the specified scopes (full access to your mailbox
			// and e-mail address).
			FileDataStore ds = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
			ds.DeleteAsync<TokenResponse>("user").Wait();
		}

		// Here you should specify the user's e-mail address if you already know it (for instance, from
		// your user's input). If you don't set the e-mail address here, MailBee.NET will ask Google for it
		// but this will require the user to grant extra permissions to the application during the authorization.
		string userEmail = null;

		// Gmail supports a number of scopes. This one is required for IMAP/SMTP access.
		string gmailScope = "https://mail.google.com/";

		string[] scopes;

		// We set only a Gmail scope if we don't need the user's email address (when it's already set).
		// Otherwise, we need to add another scope to determine the user's email address.
		if (userEmail != null)
		{
			scopes = new string[1];
		}
		else
		{
			scopes = new string[2];
		}
		scopes[0] = gmailScope;

		// Adding "https://www.googleapis.com/auth/userinfo.email" causes the app to request more permissions from the user
		// but lets us get the e-mail address automatically. Otherwise, you'll need to get the e-mail address
		// by other means, such as to ask it from the user.
		if (userEmail == null)
		{
			scopes[1] = "https://www.googleapis.com/auth/userinfo.email";
		}

		Console.WriteLine("Requesting authorization");
		UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
			new ClientSecrets
			{
				ClientId = yourAppClientID,
				ClientSecret = yourAppClientSecret,
			},
			 scopes,
			 "user",
			 CancellationToken.None).Result;
		Console.WriteLine("Authorization granted or not required (if the saved access token already available)");

		if (credential.Token.IsExpired(credential.Flow.Clock))
		{
			Console.WriteLine("The access token has expired, refreshing it");
			if (credential.RefreshTokenAsync(CancellationToken.None).Result)
			{
				Console.WriteLine("The access token is now refreshed");
			}
			else
			{
				Console.WriteLine("The access token has expired but we can't refresh it :(");
				return;
			}
		}
		else
		{
			Console.WriteLine("The access token is OK, continue");
		}

		// Here we autodetect the user's email address with Google+ API. This autodetection is not required
		// if you already know the user's email address from other sources (like the user's input).
		// Also, even if you do ask for the email address, it's not required to do this every time you access
		// the Google mailbox of that user. For instance, you can do this just once and then store the e-mail
		// address locally. This will spare you one HTTP request per each mailbox access and increase performance.
		if (userEmail == null)
		{
			Console.WriteLine("Requesting the e-mail address of the user from Google");

			// Sometimes, you may also need to set Initializer.ApplicationName property.
			// In our tests, setting just Initializer.HttpClientInitializer was enough for Google.
			Oauth2Service oauthService = new Oauth2Service(
				new BaseClientService.Initializer() { HttpClientInitializer = credential });

			Userinfo userInfo = oauthService.Userinfo.Get().ExecuteAsync().Result;
			userEmail = userInfo.Email;
			Console.WriteLine("E-mail address is " + userEmail);
		}

		string xoauthKey = OAuth2.GetXOAuthKeyStatic(userEmail, credential.Token.AccessToken);

		bool useImap = true; // Set to false to use SMTP (send e-mail) instead of IMAP (check Inbox).

		// Uncomment and set your key if you haven't specified it in app.config or Windows registry.
		// MailBee.Global.LicenseKey = "Your MNXXX-XXXX-XXXX key here";

		if (useImap)
		{
			Imap imp = new Imap();

			// Logging is not necessary but useful for debugging.
			imp.Log.Enabled = true;
			imp.Log.Filename = @"C:\Temp\log.txt";
			imp.Log.HidePasswords = false;
			imp.Log.Clear();

			imp.Connect("imap.gmail.com");
			imp.Login(null, xoauthKey, AuthenticationMethods.SaslOAuth2,
				MailBee.AuthenticationOptions.None, null);
			imp.SelectFolder("INBOX");
			Console.WriteLine(imp.MessageCount.ToString() + " e-mails in Inbox");
			imp.Disconnect();
		}
		else
		{
			Smtp mailer = new Smtp();
			mailer.SmtpServers.Add("smtp.gmail.com", null, xoauthKey, AuthenticationMethods.SaslOAuth2);

			// Logging is not necessary but useful for debugging.
			mailer.Log.Enabled = true;
			mailer.Log.Filename = @"C:\Temp\log.txt";
			mailer.Log.HidePasswords = false;
			mailer.Log.Clear();

			mailer.From.Email = userEmail;
			mailer.To.Add(userEmail);
			mailer.Subject = "empty email to myself";
			mailer.Send();
			Console.WriteLine("E-mail sent");
		}

		// Set revokeToken to true to invalidate the token if you want to disconnect the user from your application.
		// Revoking a token deletes it from Google server and from local storage. Strictly speaking, Google may remember
		// even revoked tokens so there can be a way to reactivate a revoked token but this sample doesn't go that deep.
		//
		// This code revokes the token which was already retrieved with GoogleWebAuthorizationBroker.AuthorizeAsync method.
		// This can be useful if you want to use it instantly - you retrieved the token, used it, and then want to forget it.
		// In case if you need to revoke an existing token which was retrieved earlier (not in the current session) you can
		// use GmailRevokeToken sample code. It also shows the alternate way of how to refresh a previously stored token.
		//
		// Note: revoking tokens only works for valid tokens. If your token is not known to Google (for instance, you issued
		// more than 25 new tokens since then), it will fail. Use local-only deletion (in the beginning of this sample) for that.
		// Also, revoking won't work for expired tokens. If it's expired and you want to test revoking, refresh it first.
		// You may ask what's the purpose of revoking an access token if it's already expired and cannot be used anyway. However,
		// an expired access token can be refreshed in case if you have its refresh token counterpart. But if the access token is
		// revoked, its refresh token will also be revoked and thus can no longer be used for access token renewal.
		bool revokeToken = false;
		if (revokeToken)
		{
			credential.RevokeTokenAsync(CancellationToken.None).Wait();
		}
	}
}
Imports System.Threading
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Responses
Imports Google.Apis.Util.Store
Imports Google.Apis.Services

' These two are required only if you want to get the e-mail address of the user (with Oauth2Service class).
Imports Google.Apis.Oauth2.v2
Imports Google.Apis.Oauth2.v2.Data

Imports MailBee
Imports MailBee.ImapMail
Imports MailBee.SmtpMail

' This program shows how to issue, refresh, delete, revoke and use Google access tokens for IMAP and SMTP access to a Gmail mailbox.
'
' To run this program, select "GmailLogin" in Project / project's Properties / Application tab / Startup object.
Public Class GmailLogin
	Public Shared Sub Main(args As String())
		' You get these in Google Developer Console.
		Dim yourAppClientID As String = "Your Client ID, like 0123-abcd.apps.googleusercontent.com"
		Dim yourAppClientSecret As String = "Your Client Secret, like 0a1b2c3d"

		' If you want to clear the saved token (if any) to force Google to show authorization screen again,
		' set clearSavedToken to true. Note that the code below only removes the token from the local storage,
		' Google server still remembers it. Deleting a token just locally without revoking it on Google server
		' makes a little practical sense but can be useful for debugging, such as when your local token became
		' invalid: typically happens during development when you issue such many tokens that Google starts to forget
		' them and your local token no longer corresponds to its counterpart on Google server (even revoking such
		' token will fail).
		' If your purpose is to invalidate a working token everywhere (delete from both local storage and Google server),
		' use UserCredential.RevokeTokenAsync (for that, in the end of this sample, set revokeToken to true).
		Dim clearSavedToken As Boolean = False
		If clearSavedToken Then
			' If there is no saved token (e.g. Google authorization screen has never been shown for this user),
			' the code below will do nothing. No exception will occur. If the token is there, Google API will delete
			' C:\Users\<YourWindowsUser>\AppData\Roaming\Google.Apis.Auth\Google.Apis.Auth.OAuth2.Responses.TokenResponse-user
			' file (the exact path may vary depending on your OS and Google API version). Without the stored token
			' subsequent GoogleWebAuthorizationBroker.AuthorizeAsync call will open the browser window asking for
			' authorization.
			' Note that there still be a difference in authorization window between two situations: when
			' you actually request authorization for the first time and when you deleted the previous token locally
			' and now force Google to request authorization again. In second case Google still remembers that it already
			' authorized this application to access your user and it will just ask the user that the application wants
			' Offline access. In the first case Google will ask for the specified scopes (full access to your mailbox
			' and e-mail address).
			Dim ds As New FileDataStore(GoogleWebAuthorizationBroker.Folder)
			ds.DeleteAsync(Of TokenResponse)("user").Wait()
		End If

		' Here you should specify the user's e-mail address if you already know it (for instance, from
		' your user's input). If you don't set the e-mail address here, MailBee.NET will ask Google for it
		' but this will require the user to grant extra permissions to the application during the authorization.
		Dim userEmail As String = Nothing

		' Gmail supports a number of scopes. This one is required for IMAP/SMTP access.
		Dim gmailScope As String = "https://mail.google.com/"

		Dim scopes As String()

		' We set only a Gmail scope if we don't need the user's email address (when it's already set).
		' Otherwise, we need to add another scope to determine the user's email address.
		If userEmail IsNot Nothing Then
			scopes = New String(0) {}
		Else
			scopes = New String(1) {}
		End If
		scopes(0) = gmailScope

		' Adding "https://www.googleapis.com/auth/userinfo.email" causes the app to request more permissions from the user
		' but lets us get the e-mail address automatically. Otherwise, you'll need to get the e-mail address
		' by other means, such as to ask it from the user.
		If userEmail Is Nothing Then
			scopes(1) = "https://www.googleapis.com/auth/userinfo.email"
		End If

		Console.WriteLine("Requesting authorization")
		Dim credential As UserCredential = GoogleWebAuthorizationBroker.AuthorizeAsync(New ClientSecrets() With { _
			.ClientId = yourAppClientID, _
			.ClientSecret = yourAppClientSecret _
		}, scopes, "user", CancellationToken.None).Result
		Console.WriteLine("Authorization granted or not required (if the saved access token already available)")

		If credential.Token.IsExpired(credential.Flow.Clock) Then
			Console.WriteLine("The access token has expired, refreshing it")
			If credential.RefreshTokenAsync(CancellationToken.None).Result Then
				Console.WriteLine("The access token is now refreshed")
			Else
				Console.WriteLine("The access token has expired but we can't refresh it :(")
				Return
			End If
		Else
			Console.WriteLine("The access token is OK, continue")
		End If

		' Here we autodetect the user's email address with Google+ API. This autodetection is not required
		' if you already know the user's email address from other sources (like the user's input).
		' Also, even if you do ask for the email address, it's not required to do this every time you access
		' the Google mailbox of that user. For instance, you can do this just once and then store the e-mail
		' address locally. This will spare you one HTTP request per each mailbox access and increase performance.
		If userEmail Is Nothing Then
			Console.WriteLine("Requesting the e-mail address of the user from Google")

			' Sometimes, you may also need to set Initializer.ApplicationName property.
			' In our tests, setting just Initializer.HttpClientInitializer was enough for Google.
			Dim oauthService As New Oauth2Service(New BaseClientService.Initializer() With { _
				.HttpClientInitializer = credential _
			})

			Dim userInfo As Userinfo = oauthService.Userinfo.Get().ExecuteAsync().Result
			userEmail = userInfo.Email
			Console.WriteLine("E-mail address is " & userEmail)
		End If

		Dim xoauthKey As String = OAuth2.GetXOAuthKeyStatic(userEmail, credential.Token.AccessToken)

		Dim useImap As Boolean = True ' Set to false to use SMTP (send e-mail) instead of IMAP (check Inbox).

		' Uncomment and set your key if you haven't specified it in app.config or Windows registry.
		' MailBee.Global.LicenseKey = "Your MNXXX-XXXX-XXXX key here"

		If useImap Then
			Dim imp As New Imap()

			' Logging is not necessary but useful for debugging.
			imp.Log.Enabled = True
			imp.Log.Filename = "C:\Temp\log.txt"
			imp.Log.HidePasswords = False
			imp.Log.Clear()

			imp.Connect("imap.gmail.com")
			imp.Login(Nothing, xoauthKey, AuthenticationMethods.SaslOAuth2, MailBee.AuthenticationOptions.None, Nothing)
			imp.SelectFolder("INBOX")
			Console.WriteLine(imp.MessageCount.ToString() & " e-mails in Inbox")
			imp.Disconnect()
		Else
			Dim mailer As New Smtp()
			mailer.SmtpServers.Add("smtp.gmail.com", Nothing, xoauthKey, AuthenticationMethods.SaslOAuth2)

			' Logging is not necessary but useful for debugging.
			mailer.Log.Enabled = True
			mailer.Log.Filename = "C:\Temp\log.txt"
			mailer.Log.HidePasswords = False
			mailer.Log.Clear()

			mailer.From.Email = userEmail
			mailer.To.Add(userEmail)
			mailer.Subject = "empty email to myself"
			mailer.Send()
			Console.WriteLine("E-mail sent")
		End If

		' Set revokeToken to true to invalidate the token if you want to disconnect the user from your application.
		' Revoking a token deletes it from Google server and from local storage. Strictly speaking, Google may remember
		' even revoked tokens so there can be a way to reactivate a revoked token but this sample doesn't go that deep.
		'
		' This code revokes the token which was already retrieved with GoogleWebAuthorizationBroker.AuthorizeAsync method.
		' This can be useful if you want to use it instantly - you retrieved the token, used it, and then want to forget it.
		' In case if you need to revoke an existing token which was retrieved earlier (not in the current session) you can
		' use GmailRevokeToken sample code. It also shows the alternate way of how to refresh a previously stored token.
		'
		' Note: revoking tokens only works for valid tokens. If your token is not known to Google (for instance, you issued
		' more than 25 new tokens since then), it will fail. Use local-only deletion (in the beginning of this sample) for that.
		' Also, revoking won't work for expired tokens. If it's expired and you want to test revoking, refresh it first.
		' You may ask what's the purpose of revoking an access token if it's already expired and cannot be used anyway. However,
		' an expired access token can be refreshed in case if you have its refresh token counterpart. But if the access token is
		' revoked, its refresh token will also be revoked and thus can no longer be used for access token renewal.
		Dim revokeToken As Boolean = False
		If revokeToken Then
			credential.RevokeTokenAsync(CancellationToken.None).Wait()
		End If
	End Sub
End Class

Revoking the previously saved token

The option to revoke a token in the previous sample assumed the token is received during the same session. However, in many cases you may want to cancel the token which had been received much earlier, such as in a previous session, and you don't want the user to complete OAuth 2.0 authorization again just to have the token canceled.

To revoke the previously stored token, you should load it directly from local storage rather than with GoogleWebAuthorizationBroker.AuthorizeAsync. Still, you may need to make sure the token is not expired (you cannot revoke an expired token).

You may ask what's the point of revoking an access token if it's already expired? But revoking also invalidates its associated refresh token which (mostly) never expires. Until the token is revoked, the app can renew the expired access token with the refresh token. Revoking truly disconnects the user from the app.

using System;
using System.Threading;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Util.Store;

// This program shows how to load a previously stored Google access token and revoke it.
//
// To run this program, select "GmailRevokeToken" in Project / project's Properties / Application tab / Startup object.
public class GmailRevokeToken
{
	const string gmailFullAccessScope = "https://mail.google.com/";

	public static void Main(string[] args)
	{
		GoogleAuthorizationCodeFlow.Initializer initializer = new GoogleAuthorizationCodeFlow.Initializer
		{
			ClientSecrets = new ClientSecrets
			{
				ClientId = "Your Client ID, like 0123-abcd.apps.googleusercontent.com",
				ClientSecret = "Your Client Secret, like 0a1b2c3d"
			}
		};
		initializer.DataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
		GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(initializer);

		Console.WriteLine("Will load the saved access token");
		TokenResponse token = flow.LoadTokenAsync("user", CancellationToken.None).Result;

		if (token == null)
		{
			Console.WriteLine("No saved token found");
		}
		else
		{
			// Revoking an expired token is not possible, we need to refresh it first.
			if (token.IsExpired(flow.Clock))
			{
				Console.WriteLine("Will renew the access token");
				token = flow.RefreshTokenAsync("user", token.RefreshToken, CancellationToken.None).Result;
			}
			else
			{
				Console.WriteLine("Token renewal not needed");
			}

			Console.WriteLine("Will revoke the saved access token now");
			flow.RevokeTokenAsync("user", token.AccessToken, CancellationToken.None).Wait();
			Console.WriteLine("The token revoked");
		}
	}
}
Imports System.Threading
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Responses
Imports Google.Apis.Util.Store

' This program shows how to load a previously stored Google access token and revoke it.
'
' To run this program, select "GmailRevokeToken" in Project / project's Properties / Application tab / Startup object.
Public Class GmailRevokeToken
	Const gmailFullAccessScope As String = "https://mail.google.com/"

	Public Shared Sub Main(args As String())
		Dim initializer As New GoogleAuthorizationCodeFlow.Initializer() With { _
			.ClientSecrets = New ClientSecrets() With { _
				.ClientId = "Your Client ID, like 0123-abcd.apps.googleusercontent.com", _
				.ClientSecret = "Your Client Secret, like 0a1b2c3d" _
			} _
		}
		initializer.DataStore = New FileDataStore(GoogleWebAuthorizationBroker.Folder)
		Dim flow As New GoogleAuthorizationCodeFlow(initializer)

		Console.WriteLine("Will load the saved access token")
		Dim token As TokenResponse = flow.LoadTokenAsync("user", CancellationToken.None).Result

		If token Is Nothing Then
			Console.WriteLine("No saved token found")
		Else
			' Revoking an expired token is not possible, we need to refresh it first.
			If token.IsExpired(flow.Clock) Then
				Console.WriteLine("Will renew the access token")
				token = flow.RefreshTokenAsync("user", token.RefreshToken, CancellationToken.None).Result
			Else
				Console.WriteLine("Token renewal not needed")
			End If

			Console.WriteLine("Will revoke the saved access token now")
			flow.RevokeTokenAsync("user", token.AccessToken, CancellationToken.None).Wait()
			Console.WriteLine("The token revoked")
		End If
	End Sub
End Class

Using custom storage for Google tokens

All samples in this article use the default file storage where each token is stored as C:\Users\UserName\AppData\Roaming\Google.Apis.Auth\Google.Apis.Auth.OAuth2.Responses.TokenResponse-user file where user is what you pass in Google API methods ("user" string in this article). This is how Google API internally works.

However, you may implement your own custom IDataStore and use it instead of the default FileDataStore (recommended approach). Or, you can do all the work of storing or reading token data separately and manually construct Google token objects - this approach is easier for demonstration purposes. MailBee.NET ASP.NET OAuthMVC5 samples use it to show utilizing AspNetUserClaims database table as tokens storage.

In production apps, you can implement custom IDataStore based on AspNetUserClaims table (useful for ASP.NET apps) or any other storage of your choice. For instance, Microsoft version of this sample (MicrosoftLogin class) stores token data in a JSON-serialized text file.

Sample projects overview

All the code in this tutorial is available in GmailEasyLogin, GmailLogin and GmailRevokeToken classes of OAuthConsoleApps project. WinForms version is available in FormMain class of GoogleOAuthWinForms project.

All samples are available in both C# and VB versions.

GmailEasyLogin provides the common means of using Google OAuth provider: it lets you get, refresh and persist OAuth access tokens, determine the e-mail address of the user, check the user's inbox via IMAP. Google API is quite functional so there is a good chance GmailEasyLogin sample will be enough for your needs.

GoogleLogin adds revoking and deleting persisted access tokens, the ability not to request the e-mail address of the user (useful if you already know it but don't want the user to let your application see their profile), sending e-mail via SMTP.

GoogleOAuthWinForms sample has most features of GoogleLogin class of OAuthConsoleApps but uses async methods instead. Thus, it doesn't block UI during send e-mail or check e-mail operations. All operations can be canceled by hitting ESC or closing the app.

Get source code

All C# and VB samples discussed in this article (GoogleOAuthWinForms and OAuthConsoleApps) are shipped with MailBee.NET Objects installer and get installed into My Documents\MailBee.NET Objects\Samples\WinForms\NET 4.5 OAuth folder.

.NET Core 2.0 C# console sample can be found at My Documents\MailBee.NET Objects\Samples\WinForms\.NET Core 2.0\C#\GoogleRegularAccounts folder.


Send feedback to AfterLogic

Copyright © 2006-2023 AfterLogic Corporation. All rights reserved.