How to connect and find a user from Azure AD using Nodejs - node.js

I am trying to find a user from the azure active directory using nodejs. I am using Node-ActiveDirectory for this task. First I tried to connect to the Azure active directory as the given example in the above link. example code as below that I have used.
var ActiveDirectory = require('activedirectory');
var config = { url: 'ldap://myhotmail.onmicrosoft.com',
baseDN: 'dc=myhotmail,dc=onmicrosoft,dc=com',
username: 'roledene#myhotmail.onmicrosoft.com',
password: 'myPassword'
}
var ad = new ActiveDirectory(config);
var username = 'roledene#myhotmail.onmicrosoft.com';
var password = 'myPassword';
ad.authenticate(username, password, function(err, auth) {
if (err) {
console.log('ERROR: '+JSON.stringify(err));
return;
}
if (auth) {
console.log('Authenticated!');
}
else {
console.log('Authentication failed!');
}
});
but it gives the following error. what is wrong with this code ?
ERROR: {"code":"ENOTFOUND","errno":"ENOTFOUND","syscall":"getaddrinfo","hostname":"myhotmail.onmicrosoft.com","host":"myhotmail.onmicrosoft.com","port":389}

as #juunas comment, I tried with the Microsoft Graph client.
I will quote the initial step for setup the ms graph client which is exactly mentioned in here for more details
Installing ms graph client
npm install #microsoft/microsoft-graph-client
connect and query from MS graph api
const MicrosoftGraph = require("#microsoft/microsoft-graph-client");
var client = MicrosoftGraph.Client.init({
authProvider: (iss, sub, profile, accessToken, refreshToken, done) => {
done(null, {
profile,
accessToken,
refreshToken
}); //first parameter takes an error if you can't get an access token
}
});
// Example calling /me with no parameters
client
.api('/me')
.get((err, res) => {
console.log(res); // prints info about authenticated user
});

Related

AWS Cognito: How to delete user by access token in Node.js?

I am managing an application written in Node.js (restful backend) and React (frontend) and use AWS Cognito for user authentication.
In the frontend, I have a form which requests my backend to delete a user. The only information the frontend can send me is the username (email in my case) and an access token which I sent to the frontend after a successful login.
Now I want to delete a user. Until now, I tried it with the deleteUser method (see Use case 13 in the js documentation).:
const poolData = {
UserPoolId: process.env.COGNITO_USER_POOL_ID,
ClientId: process.env.CLIENT_ID
};
AWS.config.region = process.env.AWS_DEFAULT_REGION;
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
// ...
router.post('/deleteUser', (req, res) => {
var userData = {
Username: req.body.email,
Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
if (cognitoUser == null) {
res.status(400).send('User not found');
return;
}
cognitoUser.deleteUser(function(err, result) {
if (err) {
res.status(400).send('Could not delete user');
} else {
res.status(200).send('Deleted user');
}
});
});
This throws an error in the error block of deleteUser section with Error: User is not authenticated. I need to firstly authenticate the user but I won't get the password from the frontend to do so. I just have the access token it can provide me.
How can I authenticate the user with the provided token? Or am I misunderstanding a concept here? If so, what am I doing wrong?
Any help will much appreciated.

LDAP get full name and email nodejs

I am writing app in node js. For login I would like use active directory. I found this package. Authentication is ok, but I cannot get user full name and email. There is my code:
var ActiveDirectory = require('activedirectory2');
var ActiveDirectory = require('activedirectory2');
var _ = require('underscore');
var query = 'cn=*Exchange*';
var opts = {
includeMembership : [ 'all' ],
includeDeleted : false
};
var config = {
url: 'ldap://dc.in.domain.cz',
baseDN: 'CN=Users,DC=in,DC=domain,DC=cz',
bindDN: 'CN=searchuser,CN=users,DC=in,DC=domain,DC=cz'
};
var ad = new ActiveDirectory(config);
var username = 'username #in.domain.cz';
var password = 'my_password';
ad.authenticate(username, password, function (err, auth) {
if (auth) {
console.log('Authenticated!');
ad.find(query, function (err, results) {
if ((err) || (!results)) {
console.log('ERROR: ' + JSON.stringify(err));
return;
}
console.log('Users');
_.each(results.users, function (user) {
console.log(' ' + user.cn);
});
});
} else {
console.log('Authentication failed!');
}
});
I get error:
ERROR: {"lde_message":"000004DC: LdapErr: DSID-0C0907C2, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v2580\u0000","lde_dn":null}
Thank you
The authenticate function is just used for testing if credentials are valid. You would use this if, for example, you have a login page and you want to see if the credentials the user gave you are correct. These credentials are not used for the other operations.
In the documentation, it says under the Usage heading:
The username and password specified in the configuration are what are used for user and group lookup operations.
So when you use find(), it's using the credentials you put in config. You have bindDN there, but you don't have password, which is likely why the bind failed. You will need to add the password for that "searchuser" account:
var config = {
url: 'ldap://dc.in.domain.cz',
baseDN: 'CN=Users,DC=in,DC=domain,DC=cz',
bindDN: 'CN=searchuser,CN=users,DC=in,DC=domain,DC=cz',
password: 'something'
};
Note that the docs say that you can also use the username property instead of bindDN, but don't think there is any functional difference except being able to just specify the username and not the whole DN:
var config = {
url: 'ldap://dc.in.domain.cz',
baseDN: 'CN=Users,DC=in,DC=domain,DC=cz',
username: 'searchuser#in.domain.cz',
password: 'something'
};

Serverside NodeJS - need client windows ID

(intranet application) I'm using ReactJS with NodeJS as a server side api for accessing various resources (such as user active directory profile). I need to integrate with Active Directory, without prompting the user for their credentials.
Using ActiveDirectory for Node (npm i activedirectory) I can query the AD using LDAP for a hard coded sAMAccountName using the code below.
ad.findUser(sAMAccountName, function(err, user) {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
}
if (!user) console.log('User: ' + sAMAccountName + ' not found.');
else {
thisUser = user;
}
});
But what I cant figure out, is how to pick up the current user ID when they call the API.
These 2 examples give the 'Server' user id, rather then the client ID:
const user = process.env;
const user = require('os').userInfo().username;
In .net I would normally do this with
string NTID = HttpContext.Current.User.Identity.Name;
So is there a way in NodeJS, running on a server, to access the client user ID?
---------UPDATE:-----------
I've been trying to implement the solution below using passport-windowsauth but cant quite get it working. I have the web.config file configured like this:
<iisnode watchedFiles="*.js;node_modules\*;routes\*.js;views\*.jade" promoteServerVars="LOGON_USER,AUTH_USER" />
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
Azure configuration is set to:
This is the function I call when I click a button to start the authentication:
activeDirectory = () => {
let url = '';
//Default to current user when in dev
if (
window.location.href.indexOf('localhost') > -1
) {
url = 'http://localhost:5000/express-passport';
} else {
url = '/express-passport';
}
axios({
method: "GET",
url: url,
withCredentials: true
})
.then(response => {
console.log("Done: ", response.data);
})
.catch(error => {
console.log("An error occurred - ", error);
});
};
And this is the NodeJs server route code:
const passport = require("passport");
const WindowsStrategy = require("passport-windowsauth");
module.exports = app => {
let thisUser = {};
passport.use(
new WindowsStrategy(
{
ldap: {
url: "ldap://somethingldap.somewhere.com/CN=,DC=,DC=,DC=,DC=",
base: "CN=,DC=,DC=,DC=,DC=",
bindDN: "serviceAccountDetailsHere",
bindCredentials: "masked"
}
},
function(profile, done) {
thisUser = profile;
console.log("Profile:", profile);
}
)
);
app.get(
"/express-passport",
function(req, res, next) {
passport.authenticate("WindowsAuthentication", function(
error,
user,
info
) {
// log everything to console
console.log(error);
console.log(user);
console.log(info);
if (error) {
res.status(401).send(error);
} else if (!user) {
res.status(401).send(info);
} else {
next();
}
res.status(401).send(info);
})(req, res);
},
// function to call once successfully authenticated
function(req, res) {
res.status(200).send("logged in!");
}
);
};
So now, when the page opens I click a button, expecting it to authenticate. A popup appears asking for my credentials, which I enter, but the popup just disappears and reappears continuously. I'm obviously doing something incorrect...
Your biggest problem will be getting this working on your MacOS dev machine in a way that can be seamlessly transferred to a Windows machine and work the same way.
The node-sspi package is probably easiest, but it's also Windows-only.
I think your best bet would be to use the passport-windowsauth module for Passport.js and use it with LDAP, since you can make LDAP queries to AD from any OS.
On the server, put it behind IIS and follow their instructions under Integrated Authentication to setup IIS with Windows Authentication to make it a seamless login for users.
On your MacOS dev machine, you can't do that of course. I think the best you can do for MacOS is that it will ask you for credentials. You will have to test this out. I'm not sure if it will do authentication challenge without IIS like it normally would with (I don't think it will). If not, you can create a separate login page by following their Non-integrated authentication instructions and you only use that on your dev machine.
In the actual call to passport.use(), the only difference between the two is integrated: false. So you may find you will have to set that differently for local dev vs. the server (hopefully you can have your code detect if you're on your dev machine and set it to false automatically).
So yes, you'll have different login behaviour between your dev machine and the server, but at least you should be able to promote your code as-is and have it work in both places.
Below is the solution I ended up using for this. It's a combination of 2 packages -
express-ntlm
and
ActiveDirectory
ntlm is used to pick up the user credentials from the browser (some users may have to provide those credentials if prompted). Once ntlm has userID, I use that to query the active directory to get more details for the user. The reason for 2 modules is that ntlm only provides 3 fields - UserName, DomainName and Workstation. In my case, I needed email address, first name, last name etc.
https://www.npmjs.com/package/express-ntlm
https://www.npmjs.com/package/activedirectory
const ntlm = require('express-ntlm');
const ActiveDirectory = require('activedirectory');
module.exports = app => {
const config = {
url: 'ldap://something.somewhere.com',
baseDN: 'CN=a,DC=b,DC=c',
username: '',
password: ''
};
app.use(
//This is for getting the users MS ID only
ntlm()
);
app.get(‘/apiName/’, (req, res) => {
let sAMAccountName = req.ntlm.UserName;
const ad = new ActiveDirectory(config);
var userDetails = {
Email: '',
FirstName: '',
LastName: ''
};
ad.findUser(sAMAccountName, (err, user) => {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
}
if (!user) console.log('User: ' + sAMAccountName + ' not found.');
else {
userDetails.Email = user.mail;
userDetails.FirstName = user.firstName;
userDetails.LastName = user.lastName;
}
res.json(userDetails);
res.end();
});
});
});
};
To answer your question as you mentioned above But what I can't figure out, is how to pick up the current user ID when they call the API.
It seems the above code is wrapped under function which is used as route handler callback fn. by saying this I mean you will definitely have access to req and res aka request and response API function argument.
for example:-
module.exports = {
routeHanlder: function(req, res) {
ad.findUser(sAMAccountName, function(err, user) {
if (err) {
console.log('ERROR: ' + JSON.stringify(err));
}
if (!user) console.log('User: ' + sAMAccountName + ' not found.');
else {
thisUser = user;
}
});
}
}
so in above example, the req argument contains all the client information which you want like from where the request comes, what are the request headers and so on.
as you mentioned in your question that string NTID = HttpContext.Current.User.Identity.Name; by using HttpContext you can get user identity. By using req you will get client information.

Google Suite - Google API access - Client is unauthorized to retrieve access tokens using this method

I am struggling for days with the set up in trying to access GMail Google API from a node.js script using googleapis lib. I succeeded once but I cannot remember how I did it , I tried to reset a project, service-account and G-Suite Domain wide delegation following the Google doc ..
Here is what I did :
In my GCP Console console,
1. Existing organisation : lechorodescharentes.org
2. In this organisation , I created a project : choro-dev
3. In this project I created a service account : choro-dev-postoffice
with choro-dev-postoffice with role TokenGenerator
and enabled the Google Apps Domain-wid Delegation
downloaded the new private key ( JSON file )
4. I enabled the GMail API ( from Libray menu)
In my G-Suite domain's admin console,
5. I added the following copes for this service account's ClientID
"https://www.googleapis.com/auth/admin.directory.user",
"https://www.googleapis.com/auth/admin.directory.group"
Node.js client
I am trying to access the GMail API with the following Firebase function code using the node.js googleapis library
with server -server authentication using service account
see node.js client code
In this code, I have 2 authentication functions
connect() : to a JSON Web Token
authorize() : to request an access token from the Google OAuth 2.0 Authorization Server
Deployed the Firebase function
Run the function
Got the JWT client displayed
Function ended with error :
{"infos":"unauthorized_client: Client is unauthorized to retrieve access tokens using this method."}
node.js client code
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const {google} = require('googleapis');
const nodemailer = require('nodemailer')
const _ = require('lodash');
const KEY = require('./service-key.json');
function connect () {
return new Promise((resolve, reject) => {
const jwtClient = new google.auth.JWT(
KEY.client_email,
null,
KEY.private_key,
_.values(KEY.scopes), // scopes as authorized in G-Suite admin
KEY.admin_email . // impersonated user
);
jwtClient.authorize((err) => {
if(err) {
reject(err);
} else {
resolve(jwtClient); // returns client
}
});
});
}
// Send a message to the contact user
function sendMessage (client, sender, msg) {
return new Promise((resolve, reject) => {
var transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
type: 'OAuth2',
user: KEY.admin_email,
serviceClient: KEY.client_id,
privateKey: KEY.private_key,
accessToken: client.access_token,
refreshToken: client.refresh_token,
expires: client.expiry_date
}
});
const mailOptions = {
from: 'SITE CONTACT<' + sender + '>',
to: KEY.contact_email,
subject: 'Message',
text: 'From: ' + sender + '\n\n' + msg,
html: '<h1>Message</h1><p>From: ' + sender + '</p><p>' + msg + '</p>'
};
transporter.sendMail(mailOptions, (err, response) => {
if (err) {
reject(err);
return;
}
resolve(response);
});
});
}
function newContactMessage (from, msg) {
return connect()
.then(client => {
return sendMessage(client, from, msg);
});
}
exports.sendContactMessage = functions.https.onRequest((req, res) => {
const sender_email = 'dufourisabelle#orange.fr';
const sender_msg = 'just a test message to contact the site owner.'
newContactMessage(sender_email, sender_msg).then(() => {
return {status: 200};
}, error => {
return {status: error.status, infos: error.message};
}).then(response => {
return res.send(response);
}).catch(console.error);
});
What could I add to it ? I'll try to re-initiate the all process and pray ... ??

users.list returns 403 Error: Not Authorized to access this resource/api

I am trying to retrieve a list of users using the node.js googleapis library and a service account.
I followed this guide to 'Perform Google Apps Domain-Wide Delegation of Authority'. There are examples for Java and Python, but unfortunately not for node.js, which seems to work rather differently.
I tried following the quickstart and completed the first two steps, but then it uses a manual OAuth flow instead of a service account.
So I tried to follow the example here to authorize using a service account. That all seems to work until I send the request, then I get an error: Error: Not Authorized to access this resource/api with code: 403.
Here's my code:
var google = require('googleapis'),
GoogleAuth = require('google-auth-library'),
authFactory = new GoogleAuth(),
admin = google.admin('directory_v1')
authFactory.getApplicationDefault(function (err, authClient) {
console.log('GOT APPLICATION DEFAULT', authClient)
if (err) {
console.log('Authentication failed because of ', err);
return;
}
if (authClient.createScopedRequired && authClient.createScopedRequired()) {
console.log('SCOPE REQUIRED')
var scopes = ['https://www.googleapis.com/auth/admin.directory.user'];
authClient = authClient.createScoped(scopes);
}
var request = {
auth: authClient,
domain: 'mydomain.com'
};
console.log('request:', request)
admin.users.list(request, function (err, result) {
if (err) {
console.log('admin.users.list error', err);
} else {
console.log(result);
}
});
});
What have I missed please?
After several hours of experimenting I came to the conclusion that this particular API cannot be accessed with a service account. Although it is not explicitly stated in the docs anywhere that I could find, the quickstart seems to overcome this limitation by using an OAuth process and then storing in a file the tokens required to authorize future requests. If I'm wrong please add a better answer!
My solution is to use the quickstart project to generate those tokens and then add the credentials and tokens from the quickstart to my project and use them whenever my server starts, something like:
let tokens = require('./credentials/tokens.json'),
credentials = require('./credentials/oauth_credentials.json'),
clientSecret = credentials.installed.client_secret,
clientId = credentials.installed.client_id,
redirectUrl = credentials.installed.redirect_uris[0],
google = require('googleapis'),
GoogleAuth = require('google-auth-library'),
authFactory = new GoogleAuth(),
admin = google.admin('directory_v1'),
oauth2Client = new authFactory.OAuth2(clientId, clientSecret, redirectUrl);
oauth2Client.credentials = tokens;
let request = {
auth: oauth2Client,
domain: 'coachaxis.com'
};
console.log('request:', request)
admin.users.list(request, function (err, result) {
if (err) {
console.log('admin.users.list error', err);
} else {
console.log(result);
}
});
It's not elegant but it works.

Resources