How to send an email in .Net according to new security policies?

C#.Net

C# Problem Overview


To better protect your users, GMail and others mail providers recommends to upgrade all of our applications to OAuth 2.0.

Am I right that this means that System.Net.Mail don't work anymore and we need to use another library like MailKit?

In general I'm trying to understand how to send an email without allowing "Access for less secure apps"?

Because I have System.Net.Mail.SmtpException: The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.5.1 Authentication Required. When smtpClient.Send(message); executed.

If the only way to solve this problem is using MailKit, I think that this question will be a good practical step-by-step switching tutorial from System.Net.Mail to using MailKit and Google.Apis.Auth.OAuth2. I don't know maybe general solution will be using DotNetOpenAuth?

I have the following class in my application that correspond to send an email to any address(gmail, yandex and others):

public class EmailSender
{
    public void SendEmail(SmtpServerSettings serverSettings, SendEmailRequest emailRequest)
    {
        // Usually I have 587 port, SmtpServerName = smtp.gmail.com 
        _logger.Trace("Sending message with subject '{0}' using SMTP server {1}:{2}",
                      emailRequest.Subject,
                      serverSettings.SmtpServerName,
                      serverSettings.SmtpPort);

        try
        {
            using (var smtpClient = new SmtpClient(serverSettings.SmtpServerName, (int)serverSettings.SmtpPort))
            {
                smtpClient.EnableSsl = serverSettings.SmtpUseSsl; // true
                if (!string.IsNullOrEmpty(serverSettings.UserName) || !string.IsNullOrEmpty(serverSettings.EncryptedPassword))
                {
                    smtpClient.Credentials = new NetworkCredential(serverSettings.UserName, serverSettings.EncryptedPassword);
                }

                smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
                smtpClient.Timeout = (int)serverSettings.SmtpTimeout.TotalMilliseconds;

                using (var message = new MailMessage())
                {
                    message.From = new MailAddress(serverSettings.FromAddress);

                    emailRequest.To.ForEach(message.To.Add);
                    emailRequest.CC.ForEach(message.CC.Add);
                    emailRequest.Bcc.ForEach(message.Bcc.Add);

                    message.Subject = emailRequest.Subject.Replace('\r', ' ').Replace('\n', ' ');
                    message.Body = emailRequest.Body;
                    message.BodyEncoding = Encoding.UTF8;
                    message.IsBodyHtml = false;

                    smtpClient.Send(message);
                }
            }

            _logger.Trace("Sent message with subject '{0}' using SMTP server {1}:{2}",
                          emailRequest.Subject,
                          serverSettings.SmtpServerName,
                          serverSettings.SmtpPort);
        }
        catch (SmtpFailedRecipientsException e)
        {
            var failedRecipients = e.InnerExceptions.Select(x => x.FailedRecipient);
            LogAndReThrowWithValidMessage(e, EmailsLocalization.EmailDeliveryFailed, failedRecipients);
        }
   }
}

It works fine until the new Google security policies.

I know that System.Net.Mail does not support OAuth2. I decided to use MailKit's SmtpClient to send messages.

After the investigation I understand that my initial code not change so much, because MailKit's API looks very similar(with System.Net.Mail).

Except one detail: I need to have the user's OAuth access token (MailKit does not have code that will fetch the OAuth token, but it can use it if I have it).

So in the future I will have the following line:

smtpClient.Authenticate (usersLoginName, usersOAuthToken);

I have an idea to add GoogleCredentials as new parameter to the SendEmail method:

public void SendEmail(SmtpServerSettings serverSettings, SendEmailRequest emailRequest, 
                      GoogleCredentials credentials)
{
    var certificate = new X509Certificate2(credentials.CertificateFilePath,
                                           credentials.PrivateKey,
                                           X509KeyStorageFlags.Exportable);

     var credential = new ServiceAccountCredential(
                      new ServiceAccountCredential.Initializer(credentials.ServiceAccountEmail)
                             {
                                 Scopes = new[] { "https://mail.google.com/" },
                                 User = serverSettings.UserName
                             }.FromCertificate(certificate));

    ....
    //my previous code but with MailKit API
}

How to get usersOAuthToken? Is it the best practice technique to use Google.Apis.Auth.OAuth2?

The code I posted above is for GMail ONLY and WILL NOT work for yandex.ru or other mail providers. To work with others, I will probably need to use another OAuth2 librarys. But I don't want to have many authentication mechanisms in my code for many possible mail providers. I would like to have ONE GENERAL SOLUTION for every mail providers. And one library that can send email (like .net smtpclient did)

C# Solutions


Solution 1 - C#

The general solution is https://galleryserverpro.com/use-gmail-as-your-smtp-server-even-when-using-2-factor-authentication-2-step-verification/

  1. Use a browser to log in to your Google account and go to your Sign-in & security settings. Look for the 2-step verification setting.

  2. If 2-step verification is off and you want to keep it that way that's mean that you will need to implement many auth mechanism as you said.

Solution: Turn it on and then generate and use google app password. It should work! You don't need to use other libraries like mailkit.

Solution 2 - C#

> How to get usersOAuthToken?

The first thing you need to do is follow Google's instructions for obtaining OAuth 2.0 credentials for your application.

Once you've done that, the easiest way to obtain an access token is to use Google's Google.Apis.Auth library:

using System;
using System.Threading;
using System.Security.Cryptography.X509Certificates;

using Google.Apis.Auth.OAuth2;

using MimeKit;
using MailKit.Net.Smtp;
using MailKit.Security;

namespace Example {
	class Program
	{
		public static async void Main (string[] args)
		{
			var certificate = new X509Certificate2 (@"C:\path\to\certificate.p12", "password", X509KeyStorageFlags.Exportable);
			var credential = new ServiceAccountCredential (new ServiceAccountCredential
				.Initializer ("[email protected]") {
					// Note: other scopes can be found here: https://developers.google.com/gmail/api/auth/scopes
					Scopes = new[] { "https://mail.google.com/" },
					User = "[email protected]"
				}.FromCertificate (certificate));

			// Note: result will be true if the access token was received successfully
			bool result = await credential.RequestAccessTokenAsync (CancellationToken.None);

			if (!result) {
				Console.WriteLine ("Error fetching access token!");
				return;
			}

			var message = new MimeMessage ();
			message.From.Add (new MailboxAddress ("Your Name", "[email protected]"));
			message.To.Add (new MailboxAddress ("Recipient's Name", "[email protected]"));
			message.Subject = "This is a test message";

			var builder = new BodyBuilder ();
			builder.TextBody = "This is the body of the message.";
			builder.Attachments.Add (@"C:\path\to\attachment");

			message.Body = builder.ToMessageBody ();

			using (var client = new SmtpClient ()) {
				client.Connect ("smtp.gmail.com", 587, SecureSocketOptions.StartTls);

				// use the access token as the password string
				client.Authenticate ("[email protected]", credential.Token.AccessToken);

				client.Send (message);

				client.Disconnect (true);
			}
		}
	}
}

> Is it the best practice technique to use Google.Apis.Auth.OAuth2?

Why wouldn't you use their API for getting an authentication token? It seems like the best way to get it to me...

> Will I can send an emails to other not gmail accounts?

Yes, any email you send through GMail can be sent to any other email address - it doesn't have to only be to other GMail addresses.

Solution 3 - C#

Authentication errors happen when using the Gmail smtp server from applications not implementing Google’s specific security requirements. In the Gmail account settings, turn on: "Sign-in & security" > "Connected apps & sites" > “Allow less secure apps” > On

Solution 4 - C#

using System;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Threading;
using System.ComponentModel;
namespace Examples.SmtpExamples.Async
{
public class SimpleAsynchronousExample
{
    static bool mailSent = false;
    private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
    {
        // Get the unique identifier for this asynchronous operation.
         String token = (string) e.UserState;

        if (e.Cancelled)
        {
             Console.WriteLine("[{0}] Send canceled.", token);
        }
        if (e.Error != null)
        {
             Console.WriteLine("[{0}] {1}", token, e.Error.ToString());
        } else
        {
            Console.WriteLine("Message sent.");
        }
        mailSent = true;
    }
    public static void Main(string[] args)
    {
        // Command-line argument must be the SMTP host.
        SmtpClient client = new SmtpClient(args[0]);
        // Specify the email sender.
        // Create a mailing address that includes a UTF8 character
        // in the display name.
        MailAddress from = new MailAddress("[email protected]",
           "Jane " + (char)0xD8+ " Clayton",
        System.Text.Encoding.UTF8);
        // Set destinations for the email message.
        MailAddress to = new MailAddress("[email protected]");
        // Specify the message content.
        MailMessage message = new MailMessage(from, to);
        message.Body = "This is a test email message sent by an application. ";
        // Include some non-ASCII characters in body and subject.
        string someArrows = new string(new char[] {'\u2190', '\u2191', '\u2192', '\u2193'});
        message.Body += Environment.NewLine + someArrows;
        message.BodyEncoding =  System.Text.Encoding.UTF8;
        message.Subject = "test message 1" + someArrows;
        message.SubjectEncoding = System.Text.Encoding.UTF8;
        // Set the method that is called back when the send operation ends.
        client.SendCompleted += new
        SendCompletedEventHandler(SendCompletedCallback);
        // The userState can be any object that allows your callback
        // method to identify this send operation.
        // For this example, the userToken is a string constant.
        string userState = "test message1";
        client.SendAsync(message, userState);
        Console.WriteLine("Sending message... press c to cancel mail. Press any other key to exit.");
        string answer = Console.ReadLine();
        // If the user canceled the send, and mail hasn't been sent yet,
        // then cancel the pending operation.
        if (answer.StartsWith("c") && mailSent == false)
        {
            client.SendAsyncCancel();
        }
        // Clean up.
        message.Dispose();
        Console.WriteLine("Goodbye.");
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser5515846View Question on Stackoverflow
Solution 1 - C#AnatolyView Answer on Stackoverflow
Solution 2 - C#jstedfastView Answer on Stackoverflow
Solution 3 - C#user713836View Answer on Stackoverflow
Solution 4 - C#nfyuh hyrruyikView Answer on Stackoverflow