Search code examples
c#azure-active-directorydynamics-crmmulti-factor-authentication

Updating class to use MFA for accessing Dynamics 365


The system administrator enabled 2FA so I'm having to go through and update some programs to utilizes this for accessing the Dynamics API. Otherwise, we received the following:

{ 

   "error":"interaction_required",
   "error_description":"AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '00000007-0000-0000-c000-000000000000'.\r\nTrace ID: 24822bc6-9e93-476d-8580-fd04e3889300\r\nCorrelation ID: efd5dbc5-dead-4665-a5a6-570ae15a55fb\r\nTimestamp: 2020-02-24 20:35:15Z",
   "error_codes":[ 

      50076
   ],
   "timestamp":"2020-02-24 20:35:15Z",
   "trace_id":"24822bc6-9e93-476d-8580-fd04e3889300",
   "correlation_id":"efd5dbc5-dead-4665-a5a6-570ae15a55fb",
   "error_uri":"https://login.windows.net/error?code=50076",
   "suberror":"basic_action"
}

This article makes it sound pretty straight forward and is the process we had to use for Outlook and other apps. Basically, generating an App Password.

However, I'm trying to use the App Password instead of the Default password we've used for a while and still am unable to get an access token for the program to use.

Here is what we've been using:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace CrmQbInvoiceSync
{
  class CrmAuthorization
  {

    // Serialize the JSON response for the access_token
    public class AccessToken
    {
      public string access_token { get; set; }
    }

    public static async Task<string> GetCrmAccessToken()
    {
      var values = new Dictionary<string, string>
      {
        // Connection parameters
        {"client_id", ConfigurationManager.AppSettings["clientId"]},
        {"client_secret", ConfigurationManager.AppSettings["clientSecret"]},
        {"resource", ConfigurationManager.AppSettings["crmOrg"]},
        {"username", ConfigurationManager.AppSettings["username"]},
        {"password", ConfigurationManager.AppSettings["userPassword"]},
        {"grant_type", "password"}
      };

      // Console.WriteLine(values);

      // Convert to x-www-form-urlencoded
      var content = new FormUrlEncodedContent(values);
      try
      {
        // Send the x-www-form-urlencoded info to the OAuth2 end point
        HttpResponseMessage response = await Services.Client.PostAsync(ConfigurationManager.AppSettings["crmUri"], content);
        // Get the body from the response
        var responseContent = await response.Content.ReadAsStringAsync();

        // Extract out the access token from the response
        AccessToken responseBody = JsonConvert.DeserializeObject<AccessToken>(responseContent);

        // Test if there is an access token present
        if (responseBody.access_token != null)
        {
          // If there is an access token, take it and use it in
          // generating the query
          var accessToken = responseBody.access_token;
          return accessToken;
        }
        else
        {
          var accessToken = "Could not get the access token.";
          Services.WriteLogFile(accessToken);
          Console.WriteLine(accessToken);
          return null;
        }

      }
      catch (Exception e)
      {
        var error = e;
        Services.WriteLogFile(error.ToString());
        Console.WriteLine(error);
        throw;
      }
    }
  }
}

The {"password", ConfigurationManager.AppSettings["userPassword"]} line is what should be affected so I updated the AppSettings with the new App Password. Get this error, but seems like it should be working given I'm using the App Password:

Formatted JSON Data
{ 

   "error":"invalid_grant",
   "error_description":"AADSTS50126: Error validating credentials due to invalid username or password.\r\nTrace ID: 17bf1365-32a0-439e-bd99-9eaf8e3bab00\r\nCorrelation ID: 4d24cac1-dae9-49b7-961f-c7c739f885f4\r\nTimestamp: 2020-02-24 20:33:43Z",
   "error_codes":[ 

      50126
   ],
   "timestamp":"2020-02-24 20:33:43Z",
   "trace_id":"17bf1365-32a0-439e-bd99-9eaf8e3bab00",
   "correlation_id":"4d24cac1-dae9-49b7-961f-c7c739f885f4",
   "error_uri":"https://login.windows.net/error?code=50126"
}

Really, not sure if I should be updating something else in the program to accommodate MFA, but articles I've read indicate I should just be generating the App Password and it should be good. Suggestions?


Solution

  • I suggest you use a refresh token to refresh the access token. With refresh token, you can bypass this limitation of MFA.

    To get a refresh token, you need to follow Azure AD OAuth2 auth code flow to get a refresh token interactively. And then you can get a new token with the refresh token you got.

    Notice that the refresh token should be kept in secret. If it was compromised, you can revoke all refresh tokens of a specific use with PowerShell Revoke-AzureADUserAllRefreshToken