Reputation: 1204
I developed a bare bones express app to test auth with Dynamics Business Central and ADAL in NodeJS. I'm getting the following 401 error. The authentication works as expected in Postman and I'm able to call the Dynamics REST endpoint in that context. In the JavaScript below I am using the same AAD tenant, client id, and client secret in Postman, but I'm not able to authenticate.
Compared the auth tokens given over Postman and in NodeJs using https://jwt.io/ and the only difference is the header values and the uti
in the payload.
When I hit me getcompanies
route I get the following error. I've listed my node package versions at the bottom of the post.
Error { error: { code: '401', message: 'Unauthorized' } }
Source code
var AuthenticationContext = require('adal-node').AuthenticationContext;
var crypto = require('crypto');
var express = require('express');
var request = require('request');
require('dotenv').config()
var clientId = process.env.CLIENT_ID;
var clientSecret = process.env.CLIENT_SECRET;
var authorityHostUrl = 'https://login.windows.net';
var azureAdTenant = 'grdegr.onmicrosoft.com';
var dynBusinessCentralCommonEndpoint = 'https://api.businesscentral.dynamics.com/v1.0/' + azureAdTenant + '/api/beta';
var bcRedirectUri = 'http://localhost:1337/getbctoken';
var dynBusinessCentralAuthUrl = authorityHostUrl + '/' +
azureAdTenant +
'/oauth2/authorize?response_type=code&client_id=' +
clientId +
'&redirect_uri=' +
bcRedirectUri +
'&state=<state>&resource=' +
'https://api.businesscentral.dynamics.com';
var app = express();
var port = 1337;
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
app.get('/bcauth', function(req, res) {
crypto.randomBytes(48, function(ex, buf) {
var bcToken = buf.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
res.cookie('bcauthstate', bcToken);
var dynBusinessCentralAuthUrlauthorizationUrl = dynBusinessCentralAuthUrl.replace('<state>', bcToken);
console.log('redirecting to auth url: ' + dynBusinessCentralAuthUrlauthorizationUrl);
res.redirect(dynBusinessCentralAuthUrlauthorizationUrl);
});
});
var bcAccessToken = '';
app.get('/getbctoken', function(req, res) {
var authorityUrl = authorityHostUrl + '/' + azureAdTenant;
var authenticationContext = new AuthenticationContext(authorityUrl);
console.log('getting bc auth context');
authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code,
bcRedirectUri,
'https://api.businesscentral.dynamics.com/',
clientId,
clientSecret,
function(err, response) {
var message = '';
if (err) {
message = 'error: ' + err.message + '\n';
return res.send(message)
}
bcAccessToken = response.accessToken;
console.log('bc token\n' + bcAccessToken);
res.send('bc access token updated');
}
);
});
app.get('/getcompanies', (req, res) => {
var body = '';
var options = {
url: 'https://api.businesscentral.dynamics.com/v1.0/grdegr.onmicrosoft.com/api/beta/companies',
method: 'GET',
headers: {
Authorization: 'Bearer ' + bcAccessToken
},
json: JSON.stringify(body)
};
request(options, (err, response, body) => {
res.send(response || err);
if (response) {
console.log(body);
}
else {
console.log('response is null');
}
});
});
Node Packages
"devDependencies": {
"adal-node": "^0.1.28",
"request": "^2.87.0",
"webpack": "^4.12.0",
"webpack-cli": "^3.0.8"
},
"dependencies": {
"dotenv": "^6.1.0"
}
Upvotes: 0
Views: 649
Reputation: 14356
Some services are very strict when checking the aud
(audience) value of an access token. Dynamics 365 Business Central expects the access token audience to be exactly https://api.businesscentral.dynamics.com
. In your code, you are asking for, and getting an access token for https://api.businesscentral.dynamics.com/
. That trailing slash at the end is what is making Dynamics 365 reject your access token invalid.
Change the token request to:
authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code,
bcRedirectUri,
'https://api.businesscentral.dynamics.com', // <-- No trailing slash!
clientId,
clientSecret,
// ...
...and it should work.
However, there are two important things to note in your sample:
/getcompanies
will be able to do so calling on behalf of the user who originally signed in, instead of signing in themselves. If you are looking to have users sign in with Azure AD and as part of that, call Dynamics 365 on behalf of the signed-in user, I suggest looking at passport-azure-ad
.Upvotes: 1