OAuth 2.0 authentication with Gmail and Outlook in C#

Email is a widespread communication channel for both personal and business purposes. However, as email services become more sophisticated and advanced, so do the security mechanisms for accessing them.

The OAuth 2.0 authorization framework is a secure and standardized protocol that allows a third-party application to access a user's protected resources without requiring the resource owner's credentials.

Two popular email clients, Outlook and Gmail, require this authorization framework when accessing them programmatically via APIs. In this article, we'll explore how to obtain access tokens for both Outlook and Gmail accounts in C#, using the GemBox.Email component.

You can browse through the following sections of this guide:

What is OAuth 2.0, and why should developers know about it

Unlike basic authorization methods that require an application (your API) to know the user's login and password for accessing a third-party service (such as Gmail or Outlook), OAuth2 enables the user to obtain and share an access token with limited and specific access to the protected third-party resources. This token is a unique string that defines the scope, lifetime, and other attributes of the client's access to the user's resources. It helps enhancing security and privacy while allowing seamless and efficient access to resources.

GemBox.Email supports OAuth 2.0 authorization protocol for connecting with POP, IMAP, SMTP, or EWS servers and accessing emails.

Install and configure the GemBox.Email Library

For this tutorial, you will need to install GemBox.Email, so let's start by adding the NuGet Package to your project:

  1. Add the GemBox.Email component as a package using the following command from the NuGet Package Manager Console:

    Install-Package GemBox.Email

  2. After installing the GemBox.Email library, you must call the ComponentInfo.SetLicense method before using any other member of the library.

    ComponentInfo.SetLicense("FREE-LIMITED-KEY");

In this tutorial, by using "FREE-LIMITED-KEY", you will be using GemBox's free mode. This mode allows you to use the library without purchasing a license, but with some limitations. If you purchased a license, replace "FREE-LIMITED-KEY" with your serial key.

You can check this page for a complete step-by-step guide to installing and setting up GemBox.Email in other ways.

How to set up and authenticate using OAuth 2.0 in Outlook

You can use two types of tokens with OAuth while using GemBox.Email: a user token (delegated auth) and an application token (in-app auth).

Both grant access to resources and services (such as retrieving an user's mailbox), but user tokens will generally serve only to access data for a specific user. The data will be retrieved by the application's final user, who will have access to a screen where they can enter their credentials directly in the third-party application (similar to logging into a site with the "Sign In with Google" option). On the other hand, application tokens will generally serve to access data related to multiple users and will be retrieved without user interaction.

The idea of these tokens is, within Outlook, to generate some information such as client id, client secret, and tenant id. They are mainly for developers working on an application that automatically reads or manipulates emails.

Before generating a token, you need to set some configuration options on the Microsoft platform. To do so, just follow the tutorials below:

After following the tutorials, you will need to retrieve the application's TenantID, ClientID, and ClientSecret created for the application from the Azure platform.

Then, the best way to retrieve a token is by making a POST request to the URL: https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token

The request must contain grant type, scope, client id, and client secret.

You can follow the next tutorial to learn how to do an OAuth 2.0 authorization flow for Outlook using GemBox.Email:

  1. To begin, define a class you will use to serialize the request's result and retrieve the token. The result will be in JSON format, with an object containing the following properties: "access_token", "token_type", "expires_in", and "refresh_token". Note that the token_type needs to be fixed as 'Bearer', while expires_in specifies when the token can be used, and refresh_token is a secondary token that you can use to generate a new token once the primary one expires.
    internal class Token
    {
        [JsonProperty("access_token")]
        internal string AccessToken { get; set; }
    
        [JsonProperty("token_type")]
        internal string TokenType { get; set; }
    
        [JsonProperty("expires_in")]
        internal int ExpiresIn { get; set; }
    
        [JsonProperty("refresh_token")]
        internal string RefreshToken { get; set; }
    }
  2. Then, create a HttpClient for sending the POST request.
    using (var httpClientHandler = new HttpClientHandler() { SslProtocols = SslProtocols.Tls12 })
        using (var client = new HttpClient(httpClientHandler))
        {
            // The rest of the code goes here
        }
  3. As mentioned before, the request body must contain 4 parameters: grant_type, client_id, client_secret, and scope. In the code below, you just need to fill in client_id and client_scope with generated values, while the other parameters are predefined (fixed).
    var @params = new FormUrlEncodedContent(new Dictionary<string, string>()
    {
        {"grant_type", "client_credentials"},
        {"client_id", "{clientId}"},
        {"client_secret", "{clientSecret}"},
        {"Scope", "https://outlook.office365.com/.default"}
    });
  4. Make the POST request.
    var response = await client.PostAsync("https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", @params);
  5. Get the JSON formatted response and serialize it to the Token instance so that you can access its values.
    var json = await response.Content.ReadAsStringAsync();
    var result = System.Text.Json.JsonSerializer.Deserialize<Token>(json);
    var accessToken = result.AccessToken;
  6. Now that you have the token, you can access the service, but first, you need to set the GemBox.Email license key.
    ComponentInfo.SetLicense("FREE-LIMITED-KEY");
  7. To access the Exchange server, create an instance of the ExchangeClient passing the service URL and then authenticate with the token.
    var ews = new ExchangeClient("https://outlook.office365.com/EWS/Exchange.asmx");
    ews.Authenticate("user e-mail", accessToken);
  8. Now that you are authenticated with an access token it is also necessary to tell the Exchange exactly which user's data you will access, or which user the application will impersonate. Note that this is not necessarily the same user used to authenticate. If you are authenticating with the e-mail address of an administrator account on the Azure AD, you can use the ImpersonateUser method to impersonate one of the members of your AD.
    ews.ImpersonateUser("target user e-mail");

Setting up and authenticating with OAuth 2.0 in Gmail

Gmail servers support the OAuth 2.0 protocol for IMAP, POP, and SMTP. When accessing Gmail accounts using OAuth 2.0, all applications follow a standard pattern involving the following 4 steps:

1. Set up Google OAuth data

In this step you will obtain OAuth 2.0 credentials from the Google API Console. For that you will need to:

a) Go to https://console.cloud.google.com/apis/dashboard, to set a project with an "OAuth consent screen", and create an OAuth client ID credential in the tab Credentials.

b) When creating these credentials, you must include a redirect URI on the list of "Authorized redirect URIs". This URI should be from your system (like the IP from where the back-end of your system is running), but in this article, we will use "http://127.0.0.1:5050/", a local port from the computer executing the code.

Note that you can generate more client ids and secrets, and manipulate the URIs at any time. So you can feel free to create multiple test credentials.

2. Get an authorization code from Google

The method described below will open a window in your browser with Google's authentication screen, where you can log in with your Gmail account to generate an authorization code.

a) For starters, you need to create an HttpListener that will listen on the redirect URI specified in "Authorized redirect URIs". After the user logs in, they will be redirected to this URI. In this article, we will use the port 5050 from the computer where the code is being executed. If you are doing this on a production server, you should use port 80 or 443.

static string GetAuthorizationCode(string clientID, string redirectURI)
{
    var http = new HttpListener() { Prefixes = { redirectURI } };
    http.Start();

    // The rest of the code goes here
}

An important detail is that not only will the user be redirected to that page, but some data will also be sent to that page after logging in with Gmail.

When implementing a login with Gmail functionality, after the user successfully logs in with their Gmail account, certain data can be passed to the designated page as query parameters or in the request body. This data can be used to customize the user experience or perform specific actions on the page.

b) Compose a request URI including information about who the application is (client ID and secret), where Google should send the result data (redirect URI), the desired scope (or permissions), and a state parameter. The state parameter is a code that we will use to identify which result corresponds to which request.

var scope = "https://mail.google.com/";
var state = Guid.NewGuid().ToString();
var uri = $"http://accounts.google.com/o/oauth2/v2/auth?response_type=code&scope={scope}&redirect_uri={redirectURI}&client_id={clientID}&state={state}";

c) Open the browser with Google's authentication screen.

Process.Start(new ProcessStartInfo(uri) { UseShellExecute = true });

d) Now, we wait for a response to come on port 5050.

var context = http.GetContext();

e) When the data arrives, we stop the listener waiting for more information from port 5050 and retrieve the desired data.

http.Stop();

var code = context.Request.QueryString.Get("code");
var incomingState = context.Request.QueryString.Get("state");

f) Check for errors.

if (context.Request.QueryString.Get("error") != null)
{
    Console.WriteLine(String.Format("OAuth authorization error: {0}.", context.Request.QueryString.Get("error")));
    return null;
}

g) Check if the response came without the desired code or if there is no state parameter, as both can indicate that an error has occurred.

When handling the response, you can validate if the expected code and state parameters are present to ensure the response is valid since either of them missing can indicate an error in the authentication process.

if (code == null || incomingState == null)
{
    Console.WriteLine("Malformed authorization response. " + context.Request.QueryString);
    return null;
}

h) Check if the state you sent is the same as the one returned. Note that this step is optional.

if (incomingState != state)
{
    Console.WriteLine(String.Format("Received request with invalid state ({0})", incomingState));
    return null;
}

i) At the end, return the authorization code.

return code;

3. Generate an access token for the user

To receive an access token from Google using the authorization code generated in the previous step, you need to write a method that will do the following:

a) First, let's set the URI to which we will send the data. The data to be sent (which is the authorization code from step 2, the redirect URL, client ID, client secret, and the fixed grant_type) will be put in the form of a string and then converted to bytes.

static string GetAccessToken(string code, string clientID, string clientSecret, string redirectURI)
{
    string requestUri = "https://www.googleapis.com/oauth2/v4/token";
    string requestData = $"code={code}&redirect_uri={Uri.EscapeDataString(redirectURI)}&client_id={clientID}&client_secret={clientSecret}&grant_type=authorization_code";
    byte[] requestBody = Encoding.ASCII.GetBytes(requestData);

    // The rest of the code goes here
}

b) Then we create a POST request using the composed URI. We also need to define headers, including Content-Type, Accept, and Content-Length.

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
request.ContentLength = requestBody.Length;

c) Then we write the body data into the request's stream.

Stream stream = request.GetRequestStream();
stream.Write(requestBody, 0, requestBody.Length);
stream.Close();

d) Send the request and get a response.

WebResponse tokenResponse = request.GetResponse();

e) Then we read the content of the response, which is in JSON format, and deserialize it into a dictionary from which we can extract the data.

Dictionary<string, string> response;

using (StreamReader reader = new StreamReader(tokenResponse.GetResponseStream()))
{
    var responseJSON = reader.ReadToEnd();
    response = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(responseJSON);
}

f) At the end, we return the access token from the dictionary.

return response["access_token"];

It is recommended that you wrap the code listed above in a try-catch block. If there are any exceptions, you can write the error response to the console for easier debugging.

try
{
	// Code goes here
}  
catch (WebException ex)
{
    if (ex.Status == WebExceptionStatus.ProtocolError && ex.Response is HttpWebResponse response)
    {
        Console.WriteLine("HTTP: " + response.StatusCode);
        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            // reads response requestData
            string responseText = reader.ReadToEnd();
            Console.WriteLine(responseText);
        }
    }
    return null;
}

4. Authenticate using the access token

The following code demonstrates how to use the code (methods) above to connect to Google's SMTP server using the SmtpClient class. Note that the same process can be used for connecting via IMAP and POP.

var clientId = "Client ID";
var clientSecret = "Client secret";
var redirectUri = "Redirect URI";
var code = GetAuthorizationCode(clientId, redirectUri);
var accessToken = GetAccessToken(code, clientId, clientSecret, redirectUri);

using (var smtp = new SmtpClient("smtp.gmail.com"))
{
    smtp.Connect();
    smtp.Authenticate("[user-email]", accessToken, SmtpAuthentication.XOAuth2);
    Console.WriteLine("Authenticated!");
}

Best practices for OAuth 2.0 authentication

Here are some best practices for implementing OAuth 2.0 authentication for Gmail and Outlook:

  1. Use the OAuth 2.0 protocol to obtain user authorization to access their Gmail or Outlook account.
  2. Follow the Google OAuth 2.0 or Microsoft OAuth 2.0 documentation to ensure that you have properly implemented the protocol.
  3. Use a secure storage mechanism to store access tokens and refresh tokens, such as a secure database or a key vault.
  4. Implement token revocation and expiration policies to ensure that access tokens are not used beyond their lifetime or can be revoked by the user.
  5. Use the appropriate OAuth 2.0 scopes to limit the access granted to your application to only what is necessary.
  6. Implement proper error handling to successfully handle any authentication errors or issues.

Overall, it is important to follow the OAuth 2.0 protocol and associated documentation for the specific service you are integrating with to ensure the highest level of security and protect user data.

Conclusion

In this article, you saw how to use GemBox.Email for authenticating OAuth 2.0 in your email applications. For more information regarding the GemBox.Email API, and check the documentation pages. We also recommend checking our GemBox.Email examples to examine the component's features.

See also


Next steps

GemBox.Email is a .NET component that enables you to read, write, receive, and send emails from your .NET applications using one simple API.

Download Buy