Authenticate with Dynamics 365 from an Azure Function

笑着哭i 提交于 2019-12-08 03:49:33

I wouldn't have thought you can sensibly use the 'real' users credentials to connect to CRM.

I would use a service account to connect back into CRM. Create a new CRM user especially for this purpose, if you make the user non-interactive you shouldn't consume a license. You can then use the credentials of that service account to connect to CRM using CrmServiceClient. Alternatively have a look at Server to Server authentication.

If you are able to deliver a user id to your Function App, you use the service account to impersonate 'real' users via the CRM web services.

To impersonate a user, set the CallerId property on an instance of OrganizationServiceProxy before calling the service’s Web methods.

I have done something similar recently, but without relying on the Azure subscription authentication functionality for connecting back into D365. In my case calls were coming to Azure functions from other places, but the connection back is no different. Authentication does NOT pass through in any of these cases. If an AAD user authenticates to your Function application, you still need to connect to D365 using an application user, and then impersonate the user that called you.

First, make sure that the application you registered in Azure AD under App Registrations is of the type "Web app / API" and not "Native". Edit the settings of the registered app and ensure the following:

  1. Take not of the Application ID, which I'll refer to later as appId.
  2. Under "API Access - Required Permissions", add Dynamics CRM Online (Microsoft.CRM) and NOT Dynamics 365.
  3. Under "API Access - Keys", create a key with an appropriate expiry. You can create multiple keys if you have multiple functions/applications connecting back as this "App". I'll refer to this key as "clientSecret" later.

If the "Keys" option isn't available, you've registered a Native app.

I stored the appId and clientSecret in the application configuration section of the Function App, and accessed them using the usual System.Configuration.ConfigurationManager.AppSettings collection.

The below examples use a call to AuthenticationParameters to find the authority and resource URLs, but you could just as easily build those URLs manually using the countless examples online. I find this will just update itself if they ever change, so less work later.

These are simple examples and I'm glossing over the need to refresh tokens and all those things.

Then to access D365 using OData:

string odataUrl = "https://org.crm6.dynamics.com/api/data/v8.2/"; // trailing slash actually matters
string appId = "some-guid";
string clientSecret = "some key";

AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;

using (HttpClient client = new HttpClient()) {
  client.TimeOut = TimeSpan.FromMinutes (2);
  client.DefaultRequestHeaders.Add("Authorization", authRes.CreateAuthorizationHeader ());
  using (HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, $"{odataUrl}accounts?$select=name&$top=10")) {
    using (HttpResponseMessage res = client.SendAsync(req).Result) {
      if (res.IsSuccessStatusCode) {
        Console.WriteLine(res.Content.ReadAsStringAsync().Result);
      }
      else {
        // cry
      }
    }
  }
}

If you want to access D365 using the Organization service, and LINQ, use the following. The two main parts that took me a while to find out are the format of that odd looking organization.svc URL, and using Microsoft.Xrm.Sdk.WebServiceClient.OrganizationWebProxyClient instead of Tooling:

string odataUrl = "https://org.crm6.dynamics.com/xrmservices/2011/organization.svc/web?SdkClientVersion=8.2"; // don't question the url, just accept it.
string appId = "some-guid";
string clientSecret = "some key";

AuthenticationParameters authArg = AuthenticationParameters.CreateFromResourceUrlAsync(new Uri(odataUrl)).Result;
AuthenticationContext authCtx = new AuthenticationContext(authArg.Authority);
AuthenticationResult authRes = authCtx.AcquireTokenAsync(authArg.Resource, new ClientCredential(appId, clientSecret)).Result;

using (OrganizationWebProxyClient webProxyClient = new OrganizationWebProxyClient(new Uri(orgSvcUrl), false)) {
  webProxyClient.HeaderToken = authRes.AccessToken;
  using (OrganizationServiceContext ctx = new OrganizationServiceContext((IOrganizationService)webProxyClient)) {
    var accounts = (from i in ctx.CreateQuery("account") orderby i["name"] select i).Take(10);
    foreach (var account in accounts)
      Console.WriteLine(account["name"]);
  }
}

Not sure what context you get back in your Webhook registration, not tried that yet, but just making sure that there's a bearer token in the Authorization header generally does it, and the two examples above inject it in different ways so you should be able to splice together what's needed from here.

This is something I'm curious about as well but I have not had the opportunity to experiment on this.

For your second option have you registered the application and granted consent in the target AAD?

https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/use-multi-tenant-server-server-authentication

When they grant consent, your registered application will be added to the Azure AD Enterprise applications list and it is available to the users of the Azure AD tenant.

Only after an administrator has granted consent, you must then create the application user in the subscriber’s Dynamics 365 tenant.

I believe the root of the access issue is related to the Application's Service Principal Object (the Object local to the target Tenant)

Service Principal Object

https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects#service-principal-object

In order to access resources that are secured by an Azure AD tenant, the entity that requires access must be represented by a security principal. This is true for both users (user principal) and applications (service principal). The security principal defines the access policy and permissions for the user/application in that tenant. This enables core features such as authentication of the user/application during sign-in, and authorization during resource access.

Consider the application object as the global representation of your application for use across all tenants, and the service principal as the local representation for use in a specific tenant.

HTH

-Chris

Using S2S you can use AcquireToken to retrieve the Bearer

  var clientcred = new ClientCredential(clientId, clientSecret);
                AuthenticationContext authContext = new AuthenticationContext(aadInstance, false);
                AuthenticationResult result = authContext.AcquireToken(organizationUrl, clientcred);


                token = result.AccessToken;
                ExpireDate = result.ExpiresOn.DateTime;


                client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!