Search code examples
c#azureoauthmulti-factor-authentication

Azure Authenticatication Flow in a Desktop App (WinForms C#)


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.


Solution

  • 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:

    enter image description here

    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:

    enter image description here

    When I decoded the token in jwt.ms website, it has aud and scp claim values as below:

    enter image description here