Been banging my head against a few walls with this so hoping some CRM/Dynamics experts can give me a hand!
I'm trying to programatically obtain data out of our Dynamics CRM instance, using a single set of admin credentials within a Node powered Express app. This Express app is hosted on a separate server outside of our network where CRM is hosted. The app will then request, process and serve CRM data back to any logged in user who has access (controlled by roles/permissions within the app), meaning an end user only has to login into the Express app, and not have to also login via ADFS in order for the app to access the CRM instance.
Our CRM set up is an on premise server configured to be internet facing (IFD). This uses Active Directory Federation services. We have Web Application Proxy Servers running federation services on the perimeter of the network that communicate with ADFS servers on the internal network. ADFS authenticates users connecting from outside the network (from internet) against the on prem AD. Once authenticated the proxy allows users to connect through to CRM.
Our on prem active directory is synced with Azure AD as we have a hybrid deployment. Any O365 service (exchange online, sharepoint etc) uses Azure AD in the background. We synchronise the Active directory so we only have to manage users in one place.
The CRM has an endpoint, e.g. https://my.crm.endpoint
and I have registered an app (called CRM App) in the Azure Portal, with the homepage set to the CRM endpoint https://my.crm.endpoint
.
Question Is setting the app's Homepage to https://my.crm.endpoint
enough to "link" it to our on premise CRM instance?
I've written a script (crm.js) that successfully requests an access token for my CRM App registered in Azure Portal, using it's App ID.
Example Token
eyJ0dWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzE5ZTk1...
Using the bearer token, I then attempt to get some contacts out of Dynamics via the the usual endpoint: https://my.crm.endpoint/api/data/v8.2/contacts?$select=fullname,contactid
This fails and I get a 401 Unauthorised
error message.
Question Can anyone suggest what the problem could be? And/or provide details of how you can hook up a Web App (Express in my case) to make authenticated requests to a Dynamics CRM running on an on-premise server (IFD) that uses ADFS?
crm.js
let util = require('util');
let request = require("request");
let test = {
username: '<[email protected]>',
password: '<my_password>',
app_id: '<app_id>',
secret: '<secret>',
authenticate_url: 'https://login.microsoftonline.com/<tenant_id>/oauth2/token',
crm_url: 'https://<my.crm.endpoint>'
};
function CRM() { }
CRM.prototype.authenticate = function () {
return new Promise((resolve, reject) => {
let options = {
method: 'POST',
url: test.authenticate_url,
formData: {
grant_type: 'client_credentials',
client_id: test.app_id, // application id
client_secret: test.secret, // secret
username: test.username, // on premise windows login (admin)
password: test.password, // password
resource: test.app_id // application id
}
};
// ALWAYS RETURNS AN ACCESS_TOKEN
request(options, function (error, response, body) {
console.log('AUTHENTICATE RESPONSE', body);
resolve(body);
});
})
};
CRM.prototype.getContacts = function (token) {
return new Promise((resolve, reject) => {
let options = {
method: 'GET',
url: `${test.crm_url}/api/data/v8.2/contacts?$select=fullname,contactid`,
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
'OData-MaxVersion': 4.0,
'OData-Version': 4.0,
'Content-Type': 'application/json; charset=utf-8'
}
};
request(options, (error, response, body) => {
console.log('getContacts', util.inspect(error), util.inspect(body));
resolve(body);
});
});
};
let API = new CRM(); // instantiate the CRM object
API.authenticate() // call authenticate function
.then(response => {
if (response) {
let json = JSON.parse(response);
let token = json.access_token;
console.log('TOKEN', token);
API.getContacts('token')
.then(contacts => {
// DO SOMETHING WITH THE CONTACTS
console.log('CONTACTS', contacts);
})
}
});
module.exports = CRM;
Error Response
HTTP Error 401 - Unauthorized: Access is denied
ADDITIONAL INFO
My current solution is based off these docs...
UPDATE
Following @andresm53's comment, I think I do need to authenticate against ADFS directly. I've found this blog post that describes generating a shared secret in ADFS that can be used with OAuth.
"Using this form of Client Authentication, you would POST your client identifier (as client_id) and your client secret (as client_secret) to the STS endpoint. Here is an example of such an HTTP POST (using Client Credentials Grant, added line breaks only for readability):"
resource=https%3a%2f%2fmy.crm.endpoint
&client_id=**2954b462-a5de-5af6-83bc-497cc20bddde ** ???????
&client_secret=56V0RnQ1COwhf4YbN9VSkECTKW9sOHsgIuTl1FV9
&grant_type=client_credentials
UPDATE 2
I have now created the Server Application in ADFS and am POSTing the above payload with the correct client_id and client_secret.
However, I get an Object moved
message.
RESOLVED BODY: '<html><head><title>Object moved</title></head><body>\r\n<h2>Object moved to <a href="https://fs.our.domain.name/adfs/ls/?wa=wsignin1.0&wtrealm=https%3a%2f%2fmy.crm.endpoint%2f&wctx=http%253a%252f%252f2954b462-a5de-5af6-83bc-497cc20bddde%252f&wct=2018-04-16T13%3a17%3a29Z&wauth=urn%3afederation%3aauthentication%3awindows">here</a>.</h2>\r\n</body></html>\r\n'
QUESTION Can anyone please describe what I am doing wrong and what I should be doing in order to authenticate against ADFS/CRM correctly?
NB: When I'm in my browser and visit https://my.crm.endpoint
, I get prompted to enter my username and password. Entering my creds works and I get access to CRM. Have noticed in the network tab that it's using NTLM to do this? Does this change what approach I need to take?
UPDATE 3
Please see new question here
https://my.crm.endpoint
is just an example. In reality, it will be the endpoint to our dynamics instance. I changed it for this post ;) – An0nC0d3r