0
votes

I cannot connect Dynamics CRM online to discover organization URL using a Web API. The sample code from Microsoft is not working.

I am developing a console application, and want to connect to CRM-online using Web API(we are not using Organization Service since we would like to move to Web API), In the application, I need to discover all available organizations and then query the data from each organization. I am using the example from Microsoft, but it didn't work as expected. Here is sample code.

I followed the Quick Start guide in the Azure AD. using MSAL.NET.

Active Directory Dotnet Core Daemon

I tried both global discovery URL and data center discovery URL, same fault.

Here are my code using MASL.NET.

static void Main(string[] args)
{

    IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create("3f4b24d8-61b4-47df-8efc-1232a72c8817")
        .WithClientSecret("of+Ozx_ARRX?ONj+QCzWXf84eTQABA17")
        .WithAuthority("https://login.microsoftonline.com/common/oauth2/authorize", false)
        //.WithAuthority("https://login.microsoftonline.com/3a984a19-7f55-4ea3-a422-2d8771067f87/oauth2/authorize", false)
        .Build();


    var authResult = app.AcquireTokenForClient(new String[] { "https://disco.crm5.dynamics.com/.default" }).ExecuteAsync().Result;
    //var authResult = app.AcquireTokenForClient(new String[] { "https://globaldisco.crm.dynamics.com/.default" }).ExecuteAsync().Result;

    //var authResult = app.AcquireTokenForClient(new String[] { "https://crm525842.crm5.dynamics.com/.default" }).ExecuteAsync().Result;

    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
    client.Timeout = new TimeSpan(0, 2, 0);
    client.BaseAddress = new Uri("https://disco.crm5.dynamics.com/");
    //client.BaseAddress = new Uri("https://crm525842.api.crm5.dynamics.com");

    HttpResponseMessage response = client.GetAsync("api/discovery/v9.1/Instances()", HttpCompletionOption.ResponseHeadersRead).Result;
    //HttpResponseMessage response = client.GetAsync("api/data/v9.1/WhoAmI()", HttpCompletionOption.ResponseHeadersRead).Result;

    if (response.IsSuccessStatusCode)
    {
       //Get the response content.
        string result = response.Content.ReadAsStringAsync().Result;
    }
    else
    {
        //401 occurred
        throw new Exception(response.ReasonPhrase);
    }
}

I can get the access token, I expect this work but the response is:

{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
  X-CrmServer: HK2CRMWOSDISW11
  Strict-Transport-Security: max-age=31536000; includeSubDomains
  Date: Mon, 05 Aug 2019 05:47:12 GMT
  Set-Cookie: crmf5cookie=!DTRY0s4VpQxjLnMqwoaZ9AdJzXkdU6Vfto/+0KmA1KJeDxXIK15WxYQUdDi6BwLXgIrzskdvtk0u7A==;secure; path=/
  Server: Microsoft-IIS/8.5
  WWW-Authenticate: Bearer error=invalid_token, error_description=Error during token validation!. Ticket id feef1832-8a12-4d3d-a426-099176943f35, authorization_uri=https://login.microsoftonline.com/common/oauth2/authorize, resource_id=https://disco.crm5.dynamics.com/
  Content-Length: 1293
  Content-Type: text/html
}}
1

1 Answers

0
votes

I know this post is old, but I experience this exact issue and it has taken me more than 3 day of reading countless articles, samples and answers to finally get my request to be successful. Here is my code that gets an access code and then uses the token to make a request to gets the contacts from Dynamics CRM.

I believe the main difference betrween my code and the OP's is that I use OAuth v2 as my authority URL. I am also using PublicClientAppkicationBuilder vs the Confidential version. I also set the Tenant to force the login to be within the same organisation.

private static HttpClient _client = new HttpClient
{
    BaseAddress = new Uri("https://<your org>.crm11.dynamics.com")
};

public static async Task Main(string[] args)
{
    _client.SetClientAuthentication(await Authenticator.GetAccessTokenAsync());

    var whoami = await _client.GetJsonAsync("api/data/v9.1/WhoAmI");
    Console.WriteLine(whoami);

    var contacts = await _client.GetJsonAsync("api/data/v9.1/contacts?$select=fullname");
    Console.WriteLine(contacts);
}
public static void SetClientAuthentication(this HttpClient client, string accessToken)
{
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}
public static class Authenticator
{
    public static async Task<string> GetAccessTokenAsync()
    {
        var scopes = new string[] { "https://<your org>.crm11.dynamics.com/.default" };
        var app = PublicClientApplicationBuilder.Create("<your client id>")
            .WithAuthority("https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize", false)
            .WithTenantId("<your tenant id>")
            .WithDefaultRedirectUri()
            .Build();

        AuthenticationResult result;

        try
        {
            var accounts = await app.GetAccountsAsync();
            result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
        }
        catch (MsalUiRequiredException)
        {
            result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
        }

        return result?.AccessToken;
    }
}

Required App registration ids

Required API permissions for Dynamics CRM

Add HTTP localhost as a Mobile/App redirect URL in the Authentication blade

Set public client to true in the Manifest

UPDATE - 26/11/2020

I found <your org> a couple of ways. Both are done from the Dynamics 365 Home Page (https://home.dynamics.com/).

  1. You should have a couple apps either under the My Apps or Pinned Apps headings even if they are just the default ones provided by Dynamics 365. If you hover over one of these apps, the URL will point to a CRM address. The first part is your organisation.

Hovering over an app will display the organisation in the URL

  1. Look for the HTTP request to /apps using the developer tools. The response will contain the list of apps which contain the organisation in the naming convention.

HTTP Request to /apps will have a response containing your organisation

N.B. Your orgnaisation will not always start with "org" but will appear in the URL in the aforementioned places.