Using ADAL C# as Confidential User /Daemon Server /Server-to-Server - 401 Unauthorized

后端 未结 3 1808
眼角桃花
眼角桃花 2020-11-30 10:59

Refering to not answered Questions:

401- Unauthorized authentication using REST API Dynamics CRM with Azure AD

and

Dynamics CRM Online 2016 - Daemon

3条回答
  •  無奈伤痛
    2020-11-30 11:26

    IntegerWolf's answer definitely pointed me in the right direction, but here's what ended up working for me:

    Discovering the Authorization Authority

    I ran the following code (in LINQPad) to determine the authorization endpoint to use for the Dynamics CRM instance to which I want my daemon/service/application to connect:

    AuthenticationParameters ap =
        AuthenticationParameters.CreateFromResourceUrlAsync(
                                    new Uri(resource + "/api/data/"))
                                .Result;
    
    return ap.Authority;
    

    resource is the URL of your CRM instance (or other app/service that's using ADAL), e.g. "https://myorg.crm.dynamics.com".

    In my case, the return value was "https://login.windows.net/my-crm-instance-tenant-id/oauth2/authorize". I suspect you can simply replace the tenant ID of your instance.

    Source:

    • Discover the authority at run time – Connect to Microsoft Dynamics 365 web services using OAuth

    Manually Authorizing the Daemon/Service/Application

    This was the crucial step for which I failed to find any help.

    I had to open the following URL in a web browser [formatted for easier viewing]:

    https://login.windows.net/my-crm-instance-tenant-id/oauth2/authorize?
       client_id=my-app-id
      &response_type=code
      &resource=https%3A//myorg.crm.dynamics.com
    

    When the page for that URL loaded, I logged in using the credentials for the user for which I wanted to run my daemon/service/app. I was then prompted to grant access to Dynamics CRM for the daemon/service/app as the user for which I logged-in. I granted access.

    Note that the login.windows.net site/app tried to open the 'home page' of my app that I setup in my app's Azure Active Directory registration. But my app doesn't actually have a home page so this 'failed'. But the above still seems to have successfully authorized my app's credentials to access Dynamics.

    Acquiring a Token

    Finally, the code below based on the code in IntegerWolf's answer worked for me.

    Note that the endpoint used is mostly the same as for the 'manual authorization' described in the previous section except that the final segment of the URL path is token instead of authorize.

    string AcquireAccessToken(
            string appId,
            string appSecretKey,
            string resource,
            string userName,
            string userPassword)
    {
        Dictionary contentValues =
            new Dictionary()
            {
                    { "client_id", appId },
                    { "resource", resource },
                    { "username", userName },
                    { "password", userPassword },
                    { "grant_type", "password" },
                    { "client_secret", appSecretKey }
            };
    
        HttpContent content = new FormUrlEncodedContent(contentValues);
    
        using (HttpClient httpClient = new HttpClient())
        {
            httpClient.DefaultRequestHeaders.Add("Cache-Control", "no-cache");
    
            HttpResponseMessage response =
                httpClient.PostAsync(
                            "https://login.windows.net/my-crm-instance-tenant-id/oauth2/token",
                            content)
                .Result
                //.Dump() // LINQPad output
                ;
    
            string responseContent =
                    response.Content.ReadAsStringAsync().Result
                    //.Dump() // LINQPad output
                    ;
    
            if (response.IsOk() && response.IsJson())
            {
                Dictionary resultDictionary =
                    (new JavaScriptSerializer())
                    .Deserialize>(responseContent)
                        //.Dump() // LINQPad output
                        ;
    
                return resultDictionary["access_token"];
            }
        }
    
        return null;
    }
    

    The code above makes use of some extension methods:

    public static class HttpResponseMessageExtensions
    {
        public static bool IsOk(this HttpResponseMessage response)
        {
            return response.StatusCode == System.Net.HttpStatusCode.OK;
        }
    
        public static bool IsHtml(this HttpResponseMessage response)
        {
            return response.FirstContentTypeTypes().Contains("text/html");
        }
    
        public static bool IsJson(this HttpResponseMessage response)
        {
            return response.FirstContentTypeTypes().Contains("application/json");
        }
    
        public static IEnumerable FirstContentTypeTypes(
            this HttpResponseMessage response)
        {
            IEnumerable contentTypes =
                 response.Content.Headers.Single(h => h.Key == "Content-Type").Value;
    
            return contentTypes.First().Split(new string[] { "; " }, StringSplitOptions.None);
        }
    }
    

    Using a Token

    To use a token with requests made with the HttpClient class, just add an authorization header containing the token:

    httpClient.DefaultRequestHeaders.Authorization =
        new AuthenticationHeaderValue("Bearer", accessToken);
    

提交回复
热议问题