2

We are planning to move from Organization Service to Common Data Service Web API so we could utilize OAuth 2.0 authentication instead of a service account which customer has some security concerns.

Once we did some prototype, we discovered that the Web API authentication is a little different from typical Graph API authentication. It only supports Delegated Permission. Thus a user credential must be presented for acquiring the access token.

Here is the Azure AD Graph API permission for CRM Web API: enter image description here

Here is the code in acquiring the access token for the sample code at Web API Global Discovery Service Sample (C#)

  string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
  AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com", false);

  UserCredential cred = new UserCredential(username, password);
  AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, clientId, cred);

Here is another similar post Connect to Dynamics 365 Customer Engagement web services using OAuth although it is more than one year old.

Do you know when MS would support Application permission to completely eliminate the user from authentication? Or there is any particular reason to keep the user here. Thanks for any insights.

[Update 1] With below answer from James, I did the modification for the code, here is my code

        string clientId = "3f4b24d8-61b4-47df-8efc-1232a72c8817";
        string secret = "xxxxx";

        ClientCredential cred = new ClientCredential(clientId, secret);
        string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
        AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);


        AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, cred);

        HttpClient client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
        client.Timeout = new TimeSpan(0, 2, 0);
        client.BaseAddress = new Uri(GlobalDiscoUrl);

        HttpResponseMessage response = client.GetAsync("api/discovery/v1.0/Instances", HttpCompletionOption.ResponseHeadersRead).Result;


        if (response.IsSuccessStatusCode)
        {
            //Get the response content and parse it.
            string result = response.Content.ReadAsStringAsync().Result;
            JObject body = JObject.Parse(result);
            JArray values = (JArray)body.GetValue("value");

            if (!values.HasValues)
            {
                return new List<Instance>();
            }

            return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
        }
        else
        {
            throw new Exception(response.ReasonPhrase);
        }
    }

so I am able to acquire the access token, but it still could not access the global discovery services.

Here is what the access token looks like:

    {
  "aud": "https://globaldisco.crm.dynamics.com/",
  "iss": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
  "iat": 1565802457,
  "nbf": 1565802457,
  "exp": 1565806357,
  "aio": "42FgYEj59uDNtwvxTLnprU0NYt49AA==",
  "appid": "3f4b24d8-61b4-47df-8efc-1232a72c8817",
  "appidacr": "1",
  "idp": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
  "tid": "f8cdef31-a31e-4b4a-93e4-5f571e91255a",
  "uti": "w8uwKBSPM0y7tdsfXtAgAA",
  "ver": "1.0"
}

By the way, we did already create the application user inside CRM by following the instruction.

Anything I am missing here?

[Update 2] For WhoAmI request, there are different results. If I am using latest MSAL and with authority "https://login.microsoftonline.com/AzureADDirectoryID/oauth2/authorize", I would be able to get the correct result. If I am using MSAL with "https://login.microsoftonline.com/common/oauth2/authorize", it won't work, I would get unauthorized error. If I am using ADAL 2.29, it is not working for both authority. Here is the working code:

            IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create("3f4b24d8-61b4-47df-8efc-1232a72cxxxx")
            .WithClientSecret("xxxxxx")
          //  .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://crmxxxxx.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://crm525842.api.crm5.dynamics.com/");

        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;
            Console.WriteLine(result);
        }
        else
        {
            throw new Exception(response.ReasonPhrase);
        }
windfly2006
  • 1,703
  • 3
  • 25
  • 48

1 Answers1

1

The documentation isn't the easiest to follow, but from what I understand you should start with Use OAuth with Common Data Service.

You then have two subtle options when registering your app. The second does not require the Access Dynamics 365/Common Data Service as organization users permission

Giving access to Common Data Service

If your app will be a client which allows the authenticated user to perform operations, you must configure the application to have the Access Dynamics 365 as organization users delegated permission.

Or

If your app will use Server-to-Server (S2S) authentication, this step is not required. That configuration requires a specific system user and the operations will be performed by that user account rather than any user that must be authenticated.

This is elaborated further.

Connect as an app

Some apps you will create are not intended to be run interactively by a user. ... In these cases you can create a special application user which is bound to an Azure Active Directory registered application and use either a key secret configured for the app or upload a X.509 certificate. Another benefit of this approach is that it doesn't consume a paid license.

Register your app

When registering an app you follow many of the same steps ... with the following exceptions:

  • You do not need to grant the Access Dynamics 365 as organization users permission.

You will still have a system user record in Dynamics to represent the application registration. This supports a range of basic Dynamics behaviours and allows you to apply Dynamics security to you app.

As opposed to a username and password you can then use the secret to connect.

string serviceUrl = "https://yourorg.crm.dynamics.com";
string clientId = "<your app id>";
string secret = "<your app secret>";

AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
ClientCredential credential = new ClientCredential(clientId, secret);

AuthenticationResult result = authContext.AcquireToken(serviceUrl, credential);

string accessToken = result.AccessToken;

Or a certificate.

string CertThumbPrintId = "DC6C689022C905EA5F812B51F1574ED10F256FF6";
string AppID = "545ce4df-95a6-4115-ac2f-e8e5546e79af";
string InstanceUri = "https://yourorg.crm.dynamics.com";

string ConnectionStr = $@"AuthType=Certificate;
                        SkipDiscovery=true;url={InstanceUri};
                        thumbprint={CertThumbPrintId};
                        ClientId={AppID};
                        RequireNewInstance=true";
using (CrmServiceClient svc = new CrmServiceClient(ConnectionStr))
{
    if (svc.IsReady)
    {
    ...
    }
}

You may also want to check out Build web applications using Server-to-Server (S2S) authentication which appears to be a similar (but different).

Use server-to-server (S2S) authentication to securely and seamlessly communicate with Common Data Service with your web applications and services. S2S authentication is the common way that apps registered on Microsoft AppSource use to access the Common Data Service data of their subscribers. ... Rather than user credentials, the application is authenticated based on a service principal identified by an Azure AD Object ID value which is stored in the application user record.

Aside; if you are currently using the Organization Service .NET object, that is being migrated to using the Web API internally.

Microsoft Dynamics CRM 2011 endpoint

The Dynamics 365 SDK assemblies will be updated to use the Web API. This update will be fully transparent to you and any code written using the SDK itself will be supported.

Community
  • 1
  • 1
James Wood
  • 17,286
  • 4
  • 46
  • 89