Office 365 SMTP sign in with App Password using C#

Viewed 542

My C# WPF program authenticated and sent mails successfully before 2-factor authentication was implemented in our Office 365 company account.I used SmtpClient library, but now I must find another solution, because it doesn't work any more. I can't find any working example with O365 App Password. Is there anyone who can help me to solve the problem?

2 Answers

I already found a solution.

It seems that there is some configuration that blocks legacy authentication on Azure AD (try to find Block Legacy Authentication conditional access policy on google). This configuration probably blocks all access over SMTP.

However, during my research, I later switched to the usage of Microsoft API and it works like a charm. I can recommend it. Below is more detailed information on how to use it. The reason on my side is that the whole IT world is slowly (or quickly?) moving towards more secure technologies and spending energy to let SMTP with 2FA work seems to me as a thrown away time. So I stopped myself in the middle of the process, enabled 2FA on the tenant, and implemented a connection to API. The future-proof solution in my eyes. At least for a few years again. Hopefully.

I used specifically Microsoft Graph REST API (docs here). On Azure, you need to go to your Active Directory, register a new app, setup app permissions and create a client secret. For the API you will need tenant ID, app ID, and your generated client secret what you created.

In your C# project, you need to install NuGet packages Azure.Identity and Microsoft.Graph.

Following code work at on .NET Core 3.1 and .NET 5.0 but I suppose it will work like a charm also for .NET 6.0 (not tested).

Here is part of the code on how to use the API (simplified):

public async Task SendEmail(string senderEmail, string recipientEmail, string messageSubject, string messageBody)
{
    // The client credentials flow requires that you request the
    // /.default scope, and preconfigure your permissions on the
    // app registration in Azure. An administrator must grant consent
    // to those permissions beforehand.
    var scopes = new[] { "https://graph.microsoft.com/.default" };

    // using Azure.Identity;
    var options = new TokenCredentialOptions
    {
        AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
    };

    // Multi-tenant apps can use "common",
    // single-tenant apps must use the tenant ID from the Azure portal
    var tenantId = "your tenant ID from Azure will be right here";

    // At Azure->Azure Active Directory->App Registrations->(your app) you can find all the information about your registered app.
    // ID used here is "Application (client) ID" what you can see at this app page in Azure.
    //
    // Notice also that used App needs to have setup proper privileges in (Azure->Azure Active Directory->App Registration->(your app)->API permissions)
    // Necessary privileges for this method and email sending are following privileges:
    //     Permission type                             Permissions (from least to most privileged)
    //     Delegated(work or school account)           [Mail.Send]
    //     Delegated(personal Microsoft account)       [Mail.Send]
    //     Application                                 [Mail.Send]
    var azureActiveDirectoryAppClientId = "your app client ID from Azure will be right here";

    // At (Azure->Azure Active Directory->App Registrations->(your app)->Certificates & secrets) you can manage credentials here.
    // You can find ClientSecret here at mentioned path. 
    // Be aware that this credentials can not be valid for more than 24 months.
    var azureActiveDirectoryAppClientSecret = "your client secret from Azure will be right here";

    // https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
    var clientSecretCredential = new ClientSecretCredential(tenantId,
        azureActiveDirectoryAppClientId, azureActiveDirectoryAppClientSecret, options);

    var graphClient = new GraphServiceClient(clientSecretCredential, scopes);


    var message = new Message
    {
        Subject = messageSubject,
        Body = new ItemBody
        {
            ContentType = BodyType.Html,
            Content = messageBody
        },
        ToRecipients = new List<Recipient>()
        {
            new Recipient
            {
                EmailAddress = new EmailAddress
                {
                    Address = recipientEmail
                }
            }
        }
    };

    await graphClient.Users[senderEmail].SendMail(message, null).Request().PostAsync();
}

Also, in the code graphClient.Users[*] you can use directly user ID from Azure (in the place of '*' char).

Hopefully, it will help. I focused more on the code snippet because it can say a lot and you can reuse it quite quickly.

The whole solution seems to be trivial in the end. The tricky part was in my eyes to find all the information.

The other answer about using Graph API isn't an option for me or most people as you cannot send mail externally on a free account and requires you to upgrade from azure free to premium otherwise you have to mail them to add the server your hosted on to a whitelist otherwise all mail gets blocked on everyones outlook including the smtp mail address your sending from

so to do this with office 365 smtp what i had to do is use the following function

 public static void SendEmail(string pToEmail, string pCC, string pFromEmail,
        string pSubject, string pBody, Boolean pIsHTML, string pSMTP, string pUser, string pPassword)
    {
        //create the mail message
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
        MailMessage mail = new MailMessage(pFromEmail, pToEmail);

        mail.Subject = pSubject;
        mail.IsBodyHtml = pIsHTML;
        mail.Body = pBody;

        if (pCC != "")
        {
            mail.CC.Add(pCC);
        }

        SmtpClient smtp = new SmtpClient(pSMTP);
                    smtp.EnableSsl = true;
        smtp.Port = 25;
            
            smtp.UseDefaultCredentials = false;
        if (pUser != "")
        {
            System.Net.NetworkCredential smtpUser = new System.Net.NetworkCredential(pUser, pPassword);
            smtp.Credentials = smtpUser;
        }

        smtp.Send(mail);
        mail.Dispose();

        smtp = null;
        mail = null;
    }

then i had to provide the mx to pSMTP

got this from

Step 2: Find the MX record value for email and more In the Microsoft 365 admin center, go to the Settings > Domains page. On the Domains page, select your domain.

Choose Manage DNS, select More Options > Add your own DNS and select Continue to see the DNS records to add.

You'll want to keep this information available while you make changes at your DNS host, so you can copy and paste the values.

The groups of DNS records that are listed on the page depend on your choices listed under Domain purpose.

Go to Add DNS records to connect your domain, and follow the steps to add records at your DNS host's website.

Follow the steps for creating the records at your DNS host.

https://docs.microsoft.com/en-us/microsoft-365/admin/get-help-with-domains/information-for-dns-records?view=o365-worldwide

i was then able to use an app password (also only worked with port 25)

Related