Search code examples
dynamics-crmazure-active-directory

Why does CRM Online return 401 unauthorized in my scenario?


I am trying to implement server-to-server integration with Dynamics CRM Online 2016 and BizTalk 2013 R2. I am using the WebHttpBinding to call the CRM web API, which requires a bearer token supplied as an http header:

Authorization: Bearer [base64string]

I have written a client message inspector which calls Azure AD using ADAL to acquire an access token. This is secured with a client assertion certificate, which is assigned to the registered app in our AD tenant:

var token = context.AcquireTokenAsync(this.ResourceUri, assertionCert).Result;

  • ResourceUri is https://[myorganisation].crm4.dynamics.com
  • assertionCert is a ClientAssertionCertificate created using the app registration application ID and an x509 certificate in the machine certificate store that is registered to the app as a KeyCredential

This 'works' in that it returns a token and I can decode this token to inspect the claims - there are a fair number of them, I have no way of telling whether this is the set of claims that CRM requires.

The AD app registration is configured with delegated permissions to the CRM instance.

I have set the application ID in the CRM local user to that of the app registration.

Upon calling the webAPI and supplying this token, CRM responds with 401 unauthorized.

I have repeated the same process in a powershell script and in PostMan, all of which appear to show the same behaviour.

What else am I supposed to do to make CRM accept my access token?

edit #1: Tried hardcoding the authority URI to https://login.windows.net/[my-tenant-id]/oauth2/token rather than what comes out of dynamically acquiring the authority through AuthenticationParameters - this is the same value except ending with /authorization instead of /token. This makes zero difference.

edit #2: An administrator I am working with pointed out to me that the application user I am expecting to use had no user roles assigned - this has been amended to have a role which should allow API access, but this also made no difference.

edit #3: Set oauth2AllowImplicitFlow to true in the manifest for the app registration. This doesn't make any difference.

edit #4: Made some progress by creating a new app registration, this time as a Native app rather than a web app. I managed to get a token using a client secret, and this was accepted - BUT when assigning a certificate to the app, and presenting a ClientAssertionCertificate as before, I get the response from the authority:

Error validating credentials. AADSTS50012: Client is public so a client_assertion' should not be presented.

WHY? What does 'Client is public' mean? Just work!


Solution

  • Hrrrmph!

    Turns out that the original situation I had tried and failed with, now works.

    • Web application registration with delegated permissions to CRM Online
    • Install a client certificate on the client machine, and register this same certificate to the app using New-AzureADApplicationKeyCredential
    • Link the app registration to a CRM Application User created for this purpose (they are fundamentally different to interactive users) - n.b. this screen is not easy to find
    • Call AcquireTokenAsync() from ADAL
    • Just works

    I am at a loss to explain why this didn't work the first time I tried it, as CRM doesn't supply any information as to why token validation failed.