web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Microsoft Dynamics CRM (Archived)

CRM2016 AFDS Authentication

(0) ShareShare
ReportReport
Posted on by 85

We have an Azure hosted ‘on-premise’ instance of Dynamics 2016 running as an IFD utilising ADFS authentication using ADFS 3.0 on Windows Server 2102. We now have a requirement for an Azure hosted API to communicate with the Dynamics instance using the CRM Web API. To achieve this we need to authenticate using OAuth authentication and are trying to achieve this using ADAL as outlined here https://msdn.microsoft.com/en-gb/library/gg327838.aspx , utilising the Microsoft.IdentityModel.Clients.ActiveDirectory library. We have the following code to retrieve a token from ADFS

var resource = "https://reosurce.com/";
var clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var authProvider = "adfs.server.com/.../token";
var redirectUri = "https://crm2Environment.com/";

var authContext = new AuthenticationContext(authProvider, false);
var authToken = authContext.AcquireTokenAsync(resource, clientId, new 
Uri(redirectUri), new PlatformParameters(PromptBehavior.Always)). 
Result.AccessToken;

The code example runs successfully with in visual Studio, however is for use in an interactive flow situation and as soon as the AcquireTokenAsync method is called login dialog appears (makes sense as how else does ADFS know whether its ok to Authenticate), however this is obviously not going to work on the API which is domain agnostic and there is no way of passing credentials to ADFS. None of the alternative overloads to the AcquireTokenAsync method appear to be applicable to ADFS in the situation outlined (but open to suggestions). Are we missing something, it appears ludicrous that it is not possible for a server hosted application such as an API to retrieve an oAuth token from ADFS without being prompted for username / password  ? Is there another way to retrieve the token with a non-interactive flow / without using Domain Account authentication - is it possible to use httpClient to call adfs with logon details? Is there some configuration we can set up on ADFS (currently only set up with Relying Party Trusts for IFD) . Bear in mind that the examples available for Azure AD do not appear to work in the ADFS scenario 

*This post is locked for comments

I have the same question (0)
  • mppowe Profile Picture
    70 on at

    Did you ever figure this out?  I'm dealing with a similar issue trying to access the CRM Web Api for 2016 with on-prem ADFS and on-prem CRM using IFD.

  • Verified answer
    cirrus42 Profile Picture
    85 on at

    Yes I did find a solution, in the end I hade to read all the ADFS developer notes and fully understand how the ADFS api worked. It is not possible at all to use the out of the box library provided my Microsoft, I ended up developing my own.

    I am assuming you are going to be hitting the Dynamics api from c#?

    If you are not using c# the same principles will still apply.

    So firstly you will need the following prerequisites

    1. An active directory user setting up for use by the api, this user will need to be set up on ADFS with access to the IFD facing dynamics Uri. This user will also need to be set up in dynamics and have the necessary read/ write / create permissions to entities that the api will be manipulating. You will need to know both the user name and password for this user

    2. Your infrastructure team will need to add a record to ADFS for your application - this is done through a PowerShell command, a client id will be required (Guid) and redirect Uri will be required (we used the IFD Uri) - There are tech net article on this process

    Retrieving an access token from ADFS is a 2 stage process and I would suggest wrapping this process up into at least its own class, I developed and entirely separate c# project which has made the code easily reusable. The 2 stages are Authorize and Token retrieval

    Authorize Process

    So before you can claim a token form ADFS you need to Authorise the user against it. The ADFS api documentation outlines this as a 2 stage process, a http GET request followed by a http POST request which when completed will give you the authorization code you will need to retrieve the ADFS token . I discovered through a process of making various http requests to AFDS and analysis the response data, that it is actually possible to complete the authorization process with 1 http POST request.

    You will need to make a post request to your ADFS server with a very specific query string and content and will need the following

    1. {authProvider}  -  ADFS Uri - something like https://adfs.mycompany.com/adfs/oauth2/
    2. {ClientId} - The Guid used to by your infrastructure team to add your application to ADFS
    3. {RedirectUri} - The IFD Uri for dynamics - should match the redirect Url  used to by your infrastructure team to add your application to ADFS
    4. username - The User set up on ADFS and in Dynamics
    5. password - The password for the above user

    The following will retrieve the token 

     var uri = $"{authProvider}authorize?response_type=code&client_id={clientId}&resource={redirectUri}&redirect_uri={redirectUri}";
    
                var content = new FormUrlEncodedContent(new[] {
                    new KeyValuePair<string,string>("username",username),
                    new KeyValuePair<string,string>("password",password),
                });
    
    var responseResult = _httpManager.PostAsync(uri, content).Result;


    N.B the above example uses PostAsync(uri, content).Result using await _httpManager.PostAsync(uri, content); does work but I did encounter some odd behaviour where ADFS did not always return anything.

    The Response result is actually a html form which you will need to intercept and read.

    There are some hidden input tags within this html the 3rd  ( name = wctx) having a value that will look something like 

    ru=https%3a%2f%2fcrmDynamicsUri.com%2fdefault.aspx%3fcode%3xxxxcodexxxxx%2fdefault.aspx

    you will need to extract the value xxxxcodexxxxx which will be a 300+ alphanumeric string.

    I used the HtmlAgilityPack to aid extracting the input tag out of the Html response and some good old fashion string manipulation to get the code form the inputs value.

    Once you have the code you can make the retrieve Token request to ADFS - Note the Authorization Code has a very short lifespan - I found it did not appear to be valid for more than a few minutes  when I was analysing this process.

    Retrieve Token Process


    Once you have your authorization code the second request to ADFS is more straight forward

     var uri = $"{authProvider}token";
                var content = new FormUrlEncodedContent(new[] {
                    new KeyValuePair<string,string>("grant_type","authorization_code"),
                    new KeyValuePair<string,string>("client_id",clientId),
                    new KeyValuePair<string,string>("redirect_uri",redirectUri),
                    new KeyValuePair<string,string>("code",code)
                });
    
    var response = await _httpManager.PostAsync(uri, content);
             

    will return a json response containing your ADFS token , the token type and an expires in value, you only need to token which needs to be included in the header of any request made to the Dynamics api.

    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token);
    httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
    httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");


    That's pretty much it. It is worth noting that the token has a 30 min lifespan and should be reused during that period. It was discovered that if repeated requests where made to ADFS it would stop sending the authorization code required to get the next token - often 15 requests within 5 seconds was sufficient for ADFS to stop responding - This is likely inbuilt security to prevent 'spamming' ADFS. With that in mind I included caching within the c# project I developed so if the code was called within the lifespan of the last token it returned that token rather than making a fresh call to ADFS - Microsoft.Extensions.Caching.Memory would be a good starting point.

    Hopefully that will be enough to get you going. 

    many regards

    T

  • mppowe Profile Picture
    70 on at

    Wow, that's awesome!  What great detail, I really appreciate it.  I've been trying to do it via Postman, actually, so that I can provide the collection to our vendors as the "standard" way to interact with our CRM.  I was able to get the initial authorize step and get a login page pop-up in ADFS, enter the credentials, and get back an access token.  But, of course, I wanted to find a way to bypass that login page so their back-end APIs could call into the CRM Web Api without that hassle.

    I thought I might need to establish a Client Secret somehow, but ADFS wasn't allowing it (not allowed on public client).  So I had started to read about confidential clients on ADFS 2016.  But it sounds like you didn't need to go that route, which is exciting.  I'll look into your steps again late next week when I can get back into this project.

    Thanks again for the time you took that write out those details!

  • cirrus42 Profile Picture
    85 on at

    No problem, I am happy to share what I discovered. I just need to point out we are using ADFS 3, although I believe the principle is still the same on 2016, you are essentially attempting to circumnavigate the pop up login which is essentially what the Authorize process achieves by Posting the username and password. Off course the difficult bit was working out how to extract the code required to retrieve the token, I think there are enough pointers for you above without giving you too much - where's the challenge otherwise ;-). And just to confirm with _httpManager.PostAsync(uri, content); httpManager is a wrapper for the httpclient.

    regards 

    T

  • Nicksoft Profile Picture
    205 on at

    Thanks cirrus42  for some assistance on this. I have couple of questions on this instructions though.  We are doing tons of investigation, and we did able to get the token from AD FS 4.0, but AD FS do not redirect call to CRM server, rather ask for login page.

    we are struggling some this as AD FS 4.0 has 6 different types of application registration and depending upon the calling program service nature, this registration will change, So I know you mention that you use AD FS 3.0, but it has similar kind of setup, what kind of APP Registration you did?? Also in ADFS, are their any specific Claim Rules needs to be set up, so AD FS redirect those calls with proper token to CRM Engine for execution??

    Also in the instructions, you mention that 3 digit code, well, we are seeing that 3 digit code any where, can you please elaborate that step?? I know this are the tricks that when we apply call in certain way, AD FS does the job, but still its confusing as there are no articles on that explanation.

    Can you please provide more detail when you get a chance??

  • mppowe Profile Picture
    70 on at

    Cirrus42,

    Thank you again for the assistance thus far.  I was able to get the code and then use that to get the access_token returned in the JSON.  But then when making a call to the web api itself, I'm getting a 401.  I feel like I'm right on the cusp!  I had made successful calls to the web api previously when I was getting the token via interactive logins... so I'm not sure why it's not working now.

    I'm using:

    https://<orgname>.<domain>.edu/api/data/v8.1/contacts?$select=firstname,lastname&$top=3

    headers:

    Accept: application/json

    OData-Version: 4.0

    OData-MaxVersion: 4.0

    Authorization: Bearer <access_token>

    I tried making the above manually and using Postman's "OAuth2" authorization, and all result in "HTTP Error 401 - Unauthorized: Access is denied" 

    I tried logging into CRM with the same credentials, then pointing the browser to the api endpoint and it returned a JSON of the contact results just fine.  So the user seems to have proper access.

    Did you run into anything like that...?

  • mppowe Profile Picture
    70 on at

    Never mind, it's working for me now.  Not sure what's different, I'll just chalk it up to user error on getting the 401.  :)

    Thanks!

  • Nicksoft Profile Picture
    205 on at

    mppowe, i am glad that worked for you. Infact i made CRM WebAPI work from console application using the ADAL package which require only one call to get token and utilize that token to call API. Well, i see application group registration flow differs in certain application scenarios.

    can you please share in this particular instance, what is the application group registration model on your AD FS Server?? i mean did you register the application as Native application or Server Application?? Also can you please share what was your authorization end point vs token endpoint??

  • cirrus42 Profile Picture
    85 on at

    Nick - I just want to point out that the ADAL approach does not work with ADFS 3, hence the http request hack.

  • cirrus42 Profile Picture
    85 on at

    mppowe - Sorry for not getting back to you, I have been taking a much needed break.

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Neeraj Kumar – Community Spotlight

We are honored to recognize Neeraj Kumar as our Community Spotlight honoree for…

Leaderboard > 🔒一 Microsoft Dynamics CRM (Archived)

#1
SA-08121319-0 Profile Picture

SA-08121319-0 4

#1
Calum MacFarlane Profile Picture

Calum MacFarlane 4

#3
Alex Fun Wei Jie Profile Picture

Alex Fun Wei Jie 2

Last 30 days Overall leaderboard

Featured topics

Product updates

Dynamics 365 release plans