Authenticate a connection using OAuth 2.0 in C# and VB.NET

GemBox.Email supports OAuth 2.0 authentication to connect with POP, IMAP, SMTP or EWS protocols and access email data.

The OAuth 2.0 authorization framework is a protocol that enables a third-party application to obtain limited access to the user's protected resources, without using the resource owner's credentials. Instead of using the resource owner's credentials to access protected resources, the client obtains an access token - a string denoting a specific scope, lifetime, and other access attributes.

The following example shows how you can connect to a remote POP3 server and authenticate using the OAuth 2.0 mechanism with PopClient.

using System;
using GemBox.Email;
using GemBox.Email.Pop;

class Program
{
    static void Main()
    {
        // If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        // Create a new POP client.
        using (var pop = new PopClient("<ADDRESS> (e.g. pop.gmail.com)"))
        {
            //  Connect and sign to POP server using OAuth 2.0.
            pop.Connect();
            pop.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", PopAuthentication.XOAuth2);
            Console.WriteLine("Authenticated.");
        }
    }
}
Imports System
Imports GemBox.Email
Imports GemBox.Email.Pop

Module Program

    Sub Main()

        ' If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

        ' Create a new POP client.
        Using pop As New PopClient("<ADDRESS> (e.g. pop.gmail.com)")

            '  Connect and sign to POP server using OAuth 2.0.
            pop.Connect()
            pop.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", PopAuthentication.XOAuth2)
            Console.WriteLine("Authenticated.")
        End Using
    End Sub
End Module

This next example does the same, but with ImapClient.

using System;
using GemBox.Email;
using GemBox.Email.Imap;

class Program
{
    static void Main()
    {
        // If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        // Create a new IMAP client.
        using (var imap = new ImapClient("<ADDRESS> (e.g. imap.gmail.com)"))
        {
            //  Connect and sign to IMAP server using OAuth 2.0.
            imap.Connect();
            imap.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", ImapAuthentication.XOAuth2);
            Console.WriteLine("Authenticated.");
        }
    }
}
Imports System
Imports GemBox.Email
Imports GemBox.Email.Imap

Module Program

    Sub Main()

        ' If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

        ' Create a new IMAP client.
        Using imap As New ImapClient("<ADDRESS> (e.g. imap.gmail.com)")

            '  Connect and sign to IMAP server using OAuth 2.0.
            imap.Connect()
            imap.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", ImapAuthentication.XOAuth2)
            Console.WriteLine("Authenticated.")
        End Using
    End Sub
End Module

This next example does the same, but with SmtpClient.

using System;
using GemBox.Email;
using GemBox.Email.Smtp;

class Program
{
    static void Main()
    {
        // If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        // Create a new SMTP client.
        using (var smtp = new SmtpClient("<ADDRESS> (e.g. smtp.gmail.com)"))
        {
            //  Connect and sign to SMTP server using OAuth 2.0.
            smtp.Connect();
            smtp.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", SmtpAuthentication.XOAuth2);
            Console.WriteLine("Authenticated.");
        }
    }
}
Imports System
Imports GemBox.Email
Imports GemBox.Email.Smtp

Module Program

    Sub Main()

        ' If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

        ' Create a new SMTP client.
        Using smtp As New SmtpClient("<ADDRESS> (e.g. smtp.gmail.com)")

            '  Connect and sign to SMTP server using OAuth 2.0.
            smtp.Connect()
            smtp.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", SmtpAuthentication.XOAuth2)
            Console.WriteLine("Authenticated.")
        End Using
    End Sub
End Module

This next example does the same, but with ExchangeClient.

using System;
using GemBox.Email;
using GemBox.Email.Exchange;

class Program
{
    static void Main()
    {
        // If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        // Create a new Exchange client.
        var exchangeClient = new ExchangeClient("<HOST> (e.g. https://outlook.office365.com/EWS/Exchange.asmx)");
        // Authenticate the client using OAuth 2.0.
        exchangeClient.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", ExchangeAuthentication.OAuth2);
    }
}
Imports System
Imports GemBox.Email
Imports GemBox.Email.Exchange

Module Program

    Sub Main()

        ' If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY")

        ' Create a new Exchange client.
        Dim exchangeClient = New ExchangeClient("<HOST> (e.g. https://outlook.office365.com/EWS/Exchange.asmx)")
        ' Authenticate the client using OAuth 2.0.
        exchangeClient.Authenticate("<USERNAME>", "<ACCESS-TOKEN>", ExchangeAuthentication.OAuth2)
    End Sub
End Module

Using OAuth 2.0 to Access Gmail

The Gmail IMAP, POP, and SMTP servers have been extended to support the OAuth 2.0 protocol for authentication and authorization.

All applications follow a basic pattern when accessing Gmail accounts using OAuth 2.0. At a high level, you follow these steps:

1. Obtain OAuth 2.0 credentials from the Google API Console.

Visit the Google API Console to obtain OAuth 2.0 credentials such as a client ID and client secret that are known to both Google and your application.

2. Obtain an access token from the Google Authorization Server.

Before your application can access protected resources from Gmail servers, it must obtain an access token that grants access to those protected resources. A single access token can grant varying degrees of access to multiple protected resources. A variable parameter called scope controls the set of resources and operations that an access token permits. During the access-token request, your application sends one or more values in the scope parameter.

There are several ways to make this request, and they vary based on the type of application you are building. For example, a JavaScript application might request an access token using a browser redirect to Google, while an application installed on a device that has no browser uses web service requests.

Some requests require an authentication step where the user logs in with their Google account. After logging in, the user is asked whether they are willing to grant one or more permissions that your application is requesting. This process is called user consent.

If the user grants at least one permission, the Google Authorization Server sends your application an access token (or an authorization code that your application can use to obtain an access token) and a list of scopes of access granted by that token. If the user does not grant the permission, the server returns an error.

3. Uses the access token to authenticate to a user's Gmail account.

After an application obtains an access token, it uses the token to authenticate to a user's Gmail account.

The following code shows how to do an OAuth 2.0 authorization flow from a Windows Console application.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using GemBox.Email;
using GemBox.Email.Smtp;

class Program
{
    static void Main()
    {
        // If using Professional version, put your serial key below.
        ComponentInfo.SetLicense("FREE-LIMITED-KEY");

        const string clientID = "<CLIENT-ID>";
        const string clientSecret = "<CLIENT-SECRET>";

        // Generates code verifier value.
        string codeVerifier = RandomDataBase64Url(32);

        // Creates a redirect URI using an available port on the loopback address.
        string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
        Console.WriteLine("redirect URI: " + redirectURI);

        // Extracts the authorization code.
        var authorizationCode = GetAuthorizationCode(clientID, codeVerifier, redirectURI);

        // Obtains the access token from the authorization code.
        string accessToken = GetAccessToken(authorizationCode, clientID, clientSecret, codeVerifier, redirectURI);

        // Uses the access token to authenticate to a user's Gmail account
        using (var smtp = new SmtpClient("<ADDRESS> (e.g. smtp.gmail.com)"))
        {
            smtp.Connect();
            smtp.Authenticate("<USERNAME>", accessToken, SmtpAuthentication.XOAuth2);
            Console.WriteLine("Authenticated.");
        }
    }

    static string GetAuthorizationCode(string clientID, string codeVerifier, string redirectURI)
    {
        // Generates state and PKCE values.
        string state = RandomDataBase64Url(32);
        string codeChallenge = Base64UrlEncodeNoPadding(Sha256(codeVerifier));
        const string codeChallengeMethod = "S256";

        // Creates an HttpListener to listen for requests on that redirect URI.
        var http = new HttpListener();
        http.Prefixes.Add(redirectURI);
        Console.WriteLine("Listening..");
        http.Start();

        // Creates the OAuth 2.0 authorization request.
        string authorizationRequestURI = "https://accounts.google.com/o/oauth2/v2/auth";
        string scope = "https://mail.google.com/";

        string authorizationRequest = string.Format("{0}?response_type=code&scope={6}&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
            authorizationRequestURI,
            Uri.EscapeDataString(redirectURI),
            clientID,
            state,
            codeChallenge,
            codeChallengeMethod,
            Uri.EscapeDataString(scope)
            );

        // Opens request in the browser.
        System.Diagnostics.Process.Start(authorizationRequest);

        // Waits for the OAuth authorization response.
        var context = http.GetContext();

        // Sends an HTTP response to the browser.
        var response = context.Response;
        string responseString = string.Format("<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please return to the app.</body></html>");
        var buffer = Encoding.UTF8.GetBytes(responseString);
        response.ContentLength64 = buffer.Length;
        using (var responseOutput = response.OutputStream)
            responseOutput.Write(buffer, 0, buffer.Length);
        http.Stop();
        Console.WriteLine("HTTP server stopped.");

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

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

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

        // Compares the receieved state to the expected value, to ensure that
        // this app made the request which resulted in authorization.
        if (incomingState != state)
        {
            Console.WriteLine(String.Format("Received request with invalid state ({0})", incomingState));
            return null;
        }

        Console.WriteLine("Authorization code: " + code);
        return code;
    }

    static string GetAccessToken(string code, string clientID, string clientSecret, string codeVerifier, string redirectURI)
    {
        Console.WriteLine("Exchanging code for tokens...");

        // builds the  request
        string tokenRequestURI = "https://www.googleapis.com/oauth2/v4/token";
        string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&grant_type=authorization_code",
            code,
            Uri.EscapeDataString(redirectURI),
            clientID,
            codeVerifier,
            clientSecret
            );

        // sends the request
        HttpWebRequest tokenRequest = (HttpWebRequest)WebRequest.Create(tokenRequestURI);
        tokenRequest.Method = "POST";
        tokenRequest.ContentType = "application/x-www-form-urlencoded";
        tokenRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
        byte[] tokenRequestBytes = Encoding.ASCII.GetBytes(tokenRequestBody);
        tokenRequest.ContentLength = tokenRequestBytes.Length;
        Stream stream = tokenRequest.GetRequestStream();
        stream.Write(tokenRequestBytes, 0, tokenRequestBytes.Length);
        stream.Close();

        try
        {
            // gets the response
            WebResponse tokenResponse = tokenRequest.GetResponse();
            using StreamReader reader = new StreamReader(tokenResponse.GetResponseStream());
            // reads response body
            string responseText = reader.ReadToEnd();
            Console.WriteLine(responseText);

            // converts to dictionary
            Dictionary<string, string> tokenEndpointDecoded = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(responseText);

            string accessToken = tokenEndpointDecoded["access_token"];
            return accessToken;
        }
        catch (WebException ex)
        {
            if (ex.Status == WebExceptionStatus.ProtocolError)
            {
                if (ex.Response is HttpWebResponse response)
                {
                    Console.WriteLine("HTTP: " + response.StatusCode);
                    using StreamReader reader = new StreamReader(response.GetResponseStream());
                    // reads response body
                    string responseText = reader.ReadToEnd();
                    Console.WriteLine(responseText);
                }
            }
            return null;
        }
    }

    private static int GetRandomUnusedPort()
    {
        var listener = new TcpListener(IPAddress.Loopback, 0);
        listener.Start();
        var port = ((IPEndPoint)listener.LocalEndpoint).Port;
        listener.Stop();
        return port;
    }

    private static string RandomDataBase64Url(uint length)
    {
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
        byte[] bytes = new byte[length];
        rng.GetBytes(bytes);
        return Base64UrlEncodeNoPadding(bytes);
    }

    private static byte[] Sha256(string inputStirng)
    {
        byte[] bytes = Encoding.ASCII.GetBytes(inputStirng);
        SHA256Managed sha256 = new SHA256Managed();
        return sha256.ComputeHash(bytes);
    }

    private static string Base64UrlEncodeNoPadding(byte[] buffer)
    {
        string base64 = Convert.ToBase64String(buffer);

        // Converts base64 to base64url.
        base64 = base64.Replace("+", "-");
        base64 = base64.Replace("/", "_");
        // Strips padding.
        base64 = base64.Replace("=", "");

        return base64;
    }
}

Want more?

Next example GitHub

Check the next example or select an example from the menu. You can also download our examples from the GitHub.


Like it?

Download Buy

If you want to try the GemBox.Email yourself, you can download the free version. It delivers the same performance and set of features as the professional version, but with some operations limited. To remove the limitation, you need to purchase a license.