Since Microsoft announced it is removing basic auth smtp support, I am trying to find a different way of sending emails from my application. My applications are backend applications so therefore it has to be a non-interactive flow.
When I use the password flow, everything works perfectly, however, password grant flow is legacy since it also exposes the user password:
static async Task<string> GetAccessToken(FormUrlEncodedContent content, string tokenEndpoint)
{
var client = new HttpClient();
var response = await client.PostAsync(tokenEndpoint, content).ConfigureAwait(continueOnCapturedContext: false);
var jsonString = await response.Content.ReadAsStringAsync();
client.Dispose();
var doc = JsonDocument.Parse(jsonString);
JsonElement root = doc.RootElement;
if (root.TryGetProperty("access_token", out JsonElement tokenElement))
return tokenElement.GetString()!;
throw new Exception("Failed to get access token");
}
static void SendO365(SaslMechanism accessToken, string host, int port, string from, string to)
{
using (var client = new SmtpClient())
{
client.ServerCertificateValidationCallback = (s, c, h, e) => true;
try
{
client.Connect(host, port, SecureSocketOptions.Auto);
client.Authenticate(accessToken);
var msg = new MimeMessage();
msg.From.Add(MailboxAddress.Parse(from));
msg.To.Add(MailboxAddress.Parse(to));
msg.Subject = "Testing SMTP";
msg.Body = new TextPart("plain") { Text = "This is a test message." };
client.Send(msg);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"),
new KeyValuePair<string, string>("client_secret", "yyy"),
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("resource", "https://outlook.office365.com"),
new KeyValuePair<string, string>("scope", ".default"),
new KeyValuePair<string, string>("username", "[email protected]"),
new KeyValuePair<string, string>("password", "zzz"),
});
string tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
string tokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/token";
var accessToken = GetAccessToken(content, tokenEndpoint).Result;
var userEmail = "[email protected]";
var smtpServer = "smtp.office365.com";
var smtpPort = 587;
var toEmail = "[email protected]";
SendO365(new SaslMechanismOAuth2(userEmail, accessToken), smtpServer, smtpPort, userEmail, toEmail);
In Entra I have set the Mail.Send
scope.
I tried switching to the client_credentials
flow since it is more secure but I always get an error Authentication unsuccessful
on client.Authenticate(accessToken)
.
Anyone has an idea?