{"id":8008,"date":"2021-04-07T19:18:38","date_gmt":"2021-04-07T19:18:38","guid":{"rendered":"https:\/\/blogs.aaddevsup.xyz\/?p=8008"},"modified":"2021-04-08T15:18:44","modified_gmt":"2021-04-08T15:18:44","slug":"using-msal-in-a-vb-net-winforms-application","status":"publish","type":"post","link":"https:\/\/blogs.aaddevsup.xyz\/2021\/04\/using-msal-in-a-vb-net-winforms-application\/","title":{"rendered":"Using MSAL in a VB.Net Winforms application"},"content":{"rendered":"\n

All of our MSAL samples are for either Web, mobile client or console applications in c#. This blog post will show how you can also use MSAL in vb.net in a Winforms desktop application.<\/p>\n\n\n\n

When creating a winforms application, the thing to remember is that code in your form will run under the UI thread, which, for the most part is ok. However, when MSAL prompts for credentials, it will get thread-locked by the UI or visa versa if you just run that under the UI thread. You must make the authentication and token requests on a separate thread. In this sample, I show you one way you can handle that complex issue with a task and a delegate method and retrieve the tokens from the call. <\/p>\n\n\n\n

This sample project can be found on my gitHub here<\/a>. You will need to create an app registration in your tenant for this project and then update the Form1.vb variables with the client_id, client_secret and tenant_id. Please refer to the readme file in the gitHub for specifics about the reply URI.<\/p>\n\n\n\n

The sample will demonstrate how to: <\/p>\n\n\n\n

  1. log in a user interactively, prompting for credentials and getting the id_token and an access_token for the signed in user <\/li>
  2. log in as an application using the client credentials grant flow<\/a> and getting only an access token as you cannot get an id token using this flow since you’re not logging in as a user. <\/li><\/ol>\n\n\n\n

    For the interactive flow, there is a method called “LoginInteractively” that will prompt the user for credentials. For the client credentials grant flow, there is a method called “LoginClientCredentials”. In either case, on the UI thread, when you need to sign-in using one of those methods, you simply need to create a new Task(AddressOf {method}). Like so:<\/p>\n\n\n\n

    Dim task = New Task(AddressOf LoginClientCredentials)\n        task.Start()\n        task.Wait()<\/pre>\n\n\n\n

    You also need a way of capturing errors to display back on the main thread and I am doing this by simply pushing errors to a stack of strings and the poping them back off after the task completes to build an error message to display to the user.<\/p>\n\n\n\n

    If errors.Count > 0 Then\n            Dim error_messages As New StringBuilder()\n            Do Until errors.Count <= 0\n                If error_messages.Length > 0 Then\n                    error_messages.Append(ControlChars.NewLine)\n                End If\n                error_messages.Append(errors.Pop())\n            Loop\n            MessageBox.Show($\"Errors encountered: {error_messages.ToString()}\")\n        End If<\/pre>\n\n\n\n

    In each method, to populate the text boxes on the form with the tokens, there is a delegate sub used since one thread cannot update ui elements on another thread. This is accomplished like so:<\/p>\n\n\n\n

    Private Async Sub LoginClientCredentials()\n\n        Dim authResult As AuthenticationResult = Nothing\n\n        Try\n            Dim app As IConfidentialClientApplication = ConfidentialClientApplicationBuilder.Create(client_id).WithClientSecret(client_secret).WithTenantId(tenant_id).WithAuthority(AadAuthorityAudience.AzureAdMyOrg).Build()\n            authResult = Await app.AcquireTokenForClient(scopes).ExecuteAsync()\n        Catch ex As Exception\n            errors.Push(ex.Message)\n            Exit Sub\n        End Try\n\n        accessToken = authResult.AccessToken\n        idToken = \"No id token given for this auth flow.\"\n\n        'Since this thread runs under the ui thread, we need a delegate method to update the text boxes\n        txtBoxAccessToken.BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))\n    End Sub\n\n    Private Delegate Sub InvokeDelegate()\n    Private Sub InvokeMethod()\n        txtBoxAccessToken.Text = accessToken\n        txtboxIDToken.Text = idToken\n    End Sub<\/pre>\n\n\n\n

    Here is the full code for Form1<\/p>\n\n\n\n

    Imports System.Text\nImports Microsoft.Identity.Client\n\nPublic Class Form1\n\n    Private accessToken As String = String.Empty\n    Private idToken As String = String.Empty\n\n    Private client_id As String = \"{enter_client_id_here}\"\n    Private client_secret As String = \"{enter_client_secret_here}\"\n    Private tenant_id As String = \"{enter_tenant_id_here}\"\n    Private redirect_uri As String = \"http:\/\/localhost\"\n    Private scopes() As String = New String() {\"openid offline_access profile \"}\n\n    Private errors As New Stack(Of String)\n\n    Private isLoggedIn As Boolean = False\n\n    Private Sub btnSignIn_Click(sender As Object, e As EventArgs) Handles btnSignIn.Click\n\n        txtboxIDToken.Text = String.Empty\n        txtBoxAccessToken.Text = String.Empty\n\n        idToken = String.Empty\n        accessToken = String.Empty\n\n        'we need a task to get MSAL to log us in\n        If (txtBoxScopes.Text.Length > 0) Then\n            Try\n                Dim _scopes() As String = txtBoxScopes.Text.Split(\" \")\n                scopes = _scopes\n            Catch ex As Exception\n                MessageBox.Show(\"Invalid scopes parameter... resetting to openid offline_access profile\")\n                txtBoxScopes.Text = \"openid offline_access profile\"\n                txtBoxScopes.Focus()\n                txtBoxScopes.SelectAll()\n                Exit Sub\n            End Try\n        End If\n\n        Dim task = New Task(AddressOf LoginInteractively)\n        task.Start()\n        task.Wait()\n\n        If errors.Count > 0 Then\n            Dim error_messages As New StringBuilder()\n            Do Until errors.Count <= 0\n                If error_messages.Length > 0 Then\n                    error_messages.Append(ControlChars.NewLine)\n                End If\n                error_messages.Append(errors.Pop())\n            Loop\n            MessageBox.Show($\"Errors encountered: {error_messages.ToString()}\")\n        End If\n\n    End Sub\n\n    Private Sub btnClientCredentials_Click(sender As Object, e As EventArgs) Handles btnClientCredentials.Click\n        txtboxIDToken.Text = String.Empty\n        txtBoxAccessToken.Text = String.Empty\n\n        idToken = String.Empty\n        accessToken = String.Empty\n\n        'we need a task to get MSAL to log us in\n        If (txtBoxScopes.Text.Length > 0) Then\n            Try\n                Dim _scopes() As String = txtBoxScopes.Text.Split(\" \")\n                scopes = _scopes\n            Catch ex As Exception\n                MessageBox.Show(\"Invalid scopes parameter... resetting to https:\/\/graph.microsoft.com\/.default\")\n                txtBoxScopes.Text = \"https:\/\/graph.microsoft.com\/.default\"\n                txtBoxScopes.Focus()\n                txtBoxScopes.SelectAll()\n                Exit Sub\n            End Try\n        End If\n\n        Dim task = New Task(AddressOf LoginClientCredentials)\n        task.Start()\n        task.Wait()\n\n        If errors.Count > 0 Then\n            Dim error_messages As New StringBuilder()\n            Do Until errors.Count <= 0\n                If error_messages.Length > 0 Then\n                    error_messages.Append(ControlChars.NewLine)\n                End If\n                error_messages.Append(errors.Pop())\n            Loop\n            MessageBox.Show($\"Errors encountered: {error_messages.ToString()}\")\n        End If\n\n    End Sub\n\n    Private Async Sub LoginInteractively()\n        Try\n            Dim app As IPublicClientApplication = PublicClientApplicationBuilder.Create(client_id).WithRedirectUri(redirect_uri).WithTenantId(tenant_id).WithAuthority(AadAuthorityAudience.AzureAdMyOrg).Build()\n            Dim authResult As AuthenticationResult = Nothing\n\n            Dim accounts As IEnumerable(Of IAccount) = Await app.GetAccountsAsync()\n            Dim performInterativeFlow As Boolean = False\n\n            Try\n                authResult = Await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync()\n            Catch ex As MsalUiRequiredException\n                performInterativeFlow = True\n            Catch ex As Exception\n                errors.Push(ex.Message)\n            End Try\n\n            If performInterativeFlow Then\n                authResult = Await app.AcquireTokenInteractive(scopes).ExecuteAsync()\n            End If\n\n            If authResult.AccessToken <> String.Empty Then\n                accessToken = authResult.AccessToken\n                idToken = authResult.IdToken\n            End If\n\n        Catch ex As Exception\n            errors.Push(ex.Message)\n            Exit Sub\n        End Try\n\n        'Since this thread runs under the ui thread, we need a delegate method to update the text boxes\n        txtBoxAccessToken.BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))\n\n        Return\n    End Sub\n\n    Private Async Sub LoginClientCredentials()\n\n        Dim authResult As AuthenticationResult = Nothing\n\n        Try\n            Dim app As IConfidentialClientApplication = ConfidentialClientApplicationBuilder.Create(client_id).WithClientSecret(client_secret).WithTenantId(tenant_id).WithAuthority(AadAuthorityAudience.AzureAdMyOrg).Build()\n            authResult = Await app.AcquireTokenForClient(scopes).ExecuteAsync()\n        Catch ex As Exception\n            errors.Push(ex.Message)\n            Exit Sub\n        End Try\n\n        accessToken = authResult.AccessToken\n        idToken = \"No id token given for this auth flow.\"\n\n        'Since this thread runs under the ui thread, we need a delegate method to update the text boxes\n        txtBoxAccessToken.BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))\n    End Sub\n\n    Private Delegate Sub InvokeDelegate()\n    Private Sub InvokeMethod()\n        txtBoxAccessToken.Text = accessToken\n        txtboxIDToken.Text = idToken\n    End Sub\n\n\nEnd Class\n\n<\/pre>\n\n\n\n

    Running the project, you will get the main form displayed. You can click on the “Sign In Interactive” to be prompted for credentials: <\/p>\n\n\n\n

    \"\"<\/figure>\n\n\n\n

    and, once you’re signed in, the text boxes will populate with the id_token and access_token like so:<\/p>\n\n\n\n

    \"\"<\/figure>\n\n\n\n

    Clicking the “Sign in Client Credentials” will authenticate and get an access token using the scopes defined in the Scopes text box above it and then get an access token only:<\/p>\n\n\n\n

    \"\"<\/figure>\n\n\n\n
    \"\"<\/figure>\n\n\n\n

    Our MSAL.Net GitHub is located here <\/a>and our official documentation is here<\/a>.<\/p>\n\n\n\n

    My other VB.Net posts:<\/p>\n\n\n\n