I would send mails through SMTP (and read through IMAP) over Azure using OAuth for a Desktop App (C# WinForms .NET Framework 4.8). The account has MFA and it's not the windows account. I've read some articles, threads and examples, but no one applies, and it seems to be impossible to complete the User Authentication flow to get the token for the account if the application is not web.
The following code is working, but I can't get an accessToken for a specific account:
public static readonly string[] SCOPES = new string[] { "https://outlook.office.com/IMAP.AccessAsUser.All", "https://outlook.office.com/SMTP.Send", "https://outlook.office.com/User.Read" };
public static readonly string[] SCOPES_IMAP = { "https://outlook.office.com/IMAP.AccessAsUser.All" };
string oAuthTenantId = this.txtOAuthTenantId.Text;
string oAuthClientId = this.txtOAuthClientId.Text;
string oAuthSecret = this.txtOAuthSecret.Text;
IConfidentialClientApplication oAuthApp = ConfidentialClientApplicationBuilder.Create(oAuthClientId)
.WithClientSecret(oAuthSecret)
.WithAuthority(new Uri($"https://login.microsoftonline.com/{oAuthTenantId}"))
.WithRedirectUri("https://localhost")
.Build();
var authUrl = oAuthApp.GetAuthorizationRequestUrl(SCOPES).ExecuteAsync().Result;
var accessToken = oAuthApp.AcquireTokenForClient(SCOPE_DEFAULT).ExecuteAsync().Result;
I'm using a web app too and there it's "easy", I use the authUrl to redirect the user to the MS authentication flow, but in a desktop app, I don't know how to do it.
To generate token using OAuth for a Desktop App (C# WinForms .NET Framework 4.8), you need to switch to delegated authentication flow like interactive flow.
For interactive flow, make sure to add redirect URI as http://localhost in Mobile & desktop applications
platform and enable public client flow option in your app registration like this:
In my case, I created one Desktop App (C# WinForms .NET Framework 4.8) and use below sample code files for generating token:
OAuthHelper.cs:
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
namespace OAuthWinFormsApp
{
public class OAuthHelper
{
private static readonly string[] Scopes = new string[]
{
"https://outlook.office.com/IMAP.AccessAsUser.All",
"https://outlook.office.com/SMTP.Send",
"https://outlook.office.com/User.Read"
};
private IPublicClientApplication _pca;
public OAuthHelper(string clientId, string tenantId)
{
_pca = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(AzureCloudInstance.AzurePublic, tenantId)
.WithRedirectUri("http://localhost")
.Build();
}
public async Task<string> GetAccessTokenAsync()
{
AuthenticationResult result = null;
try
{
var accounts = await _pca.GetAccountsAsync();
result = await _pca.AcquireTokenSilent(Scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
result = await _pca.AcquireTokenInteractive(Scopes)
.WithPrompt(Prompt.SelectAccount)
.ExecuteAsync();
}
return result.AccessToken;
}
}
}
Form1.cs:
using System;
using System.Windows.Forms;
namespace OAuthWinFormsApp
{
public partial class Form1 : Form
{
private OAuthHelper _oauthHelper;
public Form1()
{
InitializeComponent();
_oauthHelper = new OAuthHelper("appId", "tenantId");
}
private async void btnAuthenticate_Click(object sender, EventArgs e)
{
try
{
string token = await _oauthHelper.GetAccessTokenAsync();
txtToken.Text = token;
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}", "Authentication Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void txtToken_TextChanged(object sender, EventArgs e)
{
}
}
}
Output:
When I decoded the token in jwt.ms website, it has aud
and scp
claim values as below: