OAuth 2.0 authentication with Microsoft 365 and Gmail 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 servers, Microsoft 365 (formerly Office 365 or Outlook) and Gmail, require this authorization framework when accessed programmatically via APIs. In this article, we'll explore how to obtain access tokens for both Microsoft 365 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 Microsoft 365 or Gmail), 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 to enhance 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, EWS, or Microsoft Graph protocols and retrieving 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 Microsoft 365

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 a user's mailbox), but user tokens 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 to provide a safe way for developers working on an application that automatically reads or manipulates emails to access this information.

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

  • Authentication and authorization basics, which describes how you can register an application on Azure and how to set it to work properly for both delegated auth and app-only auth, allowing the application to generate a token for use with Microsoft Graph API.

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

You can use Azure.Identity library to obtain your access token, either for user token (delegated auth) or application token (in-app auth). You can learn more about how to use the Azure.Identity library here.

The code below is a simple example of how you can generate an in-app auth token using Azure.Identity library:

// Create the context with the scope of authorization
var context = new Azure.Core.TokenRequestContext(["https://graph.microsoft.com/.default"], null);

// Create credentials to obtain in-app auth token
var credentials = new Azure.Identity.ClientSecretCredential("{tenantId}", "{clientId}", "{clientSecret}");

// Retrieve the access token
var accessToken = credentials.GetToken(context).Token;

You can also use Azure.Identity library to generate a user delegated auth token, as shown in code below. Please note that this procedure will open a new window in the browser to let the user enter his login information. Also, additional configuration may be necessary to enable this authorization flow.

// Create the context with the scope of authorization
var context = new Azure.Core.TokenRequestContext(["https://graph.microsoft.com/.default"], null);

// Create credentials to obtain user delegated auth token
var credentials = new Azure.Identity.InteractiveBrowserCredential(new Azure.Identity.InteractiveBrowserCredentialOptions() { TenantId = tenantId, ClientId = clientId });

// Retrieve the access token
var accessToken = credentials.GetToken(context).Token;

If you prefer, you can implement the token retrieval on your own, 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 in-app authorization flow for Microsoft 365 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 an HttpClient to send 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 four 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://graph.microsoft.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 Microsoft 365 server, create an instance of the GraphClient passing the service URL and then authenticate with the token.
    var client = new GraphClient();
    client.Authenticate(accessToken);
  8. Now that you are authenticated with an access token, it is also necessary to tell the Microsoft Graph API 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. Suppose you are authenticating with the e-mail address of an administrator account on the Azure AD. In that case, you can use the ImpersonateUser method to impersonate one of the members of your AD.
    // Use this method in case you're using an application token
    client.ImpersonateUser("target user e-mail");

You can check the complete code of this implementation on Github

How to set up and authenticate using OAuth 2.0 in Exchange

In case you are using an Exchange Server instead of Microsoft 365, the process is similar to the steps described in the Microsoft 365 tutorial above. It will be described in a shortened version below, with the necessary changes for Exchange Server.

First of all, 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.

You can use Azure.Identity library to obtain your access token, either for user token (delegated auth) or application token (in-app auth). Or, if you prefer, you can implement the token retrieval on your own, making a POST request to the URL: https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token

You can follow the sample code below to learn how to do an OAuth 2.0 in-app authorization flow for Exchange using GemBox.Email. In case you have any doubt, please check Microsoft 365 tutorial for a step-by-step guide for this implementation. Please note the following differences that must be considered for Exchange:

  1. When making the HTTP request for token generation, you need to use the following scope: https://outlook.office365.com/.default
  2. To interact with the Exchange Server, you need to use ExchangeClient instead of GraphClient.
ComponentInfo.SetLicense("FREE-LIMITED-KEY");

var ews = new ExchangeClient("https://outlook.office365.com/EWS/Exchange.asmx"); 

ews.Authenticate("user e-mail", accessToken);
ews.ImpersonateUser("target user e-mail");

You can check the complete code of this implementation on Github

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 four 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 "Authorized redirect URIs" list. 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, specific 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 using 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 (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 response's content, 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. You can write the error response to the console for easier debugging if there are any exceptions.

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 you can use the same process to connect 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!");
}

You can check the complete code of this implementation on Github

Best practices for OAuth 2.0 authentication

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

  1. Use the OAuth 2.0 protocol to obtain user authorization to access their Microsoft 365 or Gmail 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 to authenticate OAuth 2.0 in your email applications. For more information regarding the GemBox.Email API, check the documentation pages. We also recommend checking our GemBox.Email examples to examine the component's features.


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