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:
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);
}