(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.
Related
I have a big CMS built with Meteor that until now used basic-auth to login as we didn't have more than one editor. However, more people will start working with it, so I am trying to create a login functionality through LDAP so any authorised employee can login with their username/password
I tried to do my best given poor documentation of WebApp.connectHandlers and the lack of content integrating passport with Meteor. I based my LDAP login code on express examples (assuming WebApp.connectHandlers.use() was the Meteor equivalent of Express' app.use())
The login part works, that is to query and verify the user through LDAP.
However I cannot figure how to identify the logged-in user when they make a Meteor.call( ).
What I have in mind at the moment is to send a JWT token back to the authenticated user, store it in a cookie or something, and whenever a Meteor.call( ) is made, the client passes the token as a parameter. Then I would be able to identify the caller and can for example store the username in the database as the person who made a certain change.
Good to mention that I am trying to avoid using Meteor's accounts system as the requirement is to use LDAP without creating any extra tables (that's why the complication of using those several packages together).
Here's my code, with working login but no idea how to pass back the token to the user.
Maybe my whole JWT logic is wrong, I would appreciate any help/suggestions.
var basicAuth = require("basic-auth");
var passport = require("passport");
var bodyParser = require("body-parser");
var LdapStrategy = require("passport-ldapauth");
var jwt = require("jsonwebtoken");
// Example of the real LDAP config
var OPTS = {
server: {
url: "ldap://address:port",
bindDN: "admin",
bindCredentials: "adminPassword",
searchBase: "OU=Users,DC=example,DC=com",
searchFilter: "(&(objectClass=user)(sAMAccountName={{username}}))"
},
credentialsLookup: basicAuth
};
Meteor.startup(() => {
passport.use(new LdapStrategy(OPTS));
WebApp.connectHandlers.use(bodyParser.json());
WebApp.connectHandlers.use(bodyParser.urlencoded({ extended: false }));
WebApp.connectHandlers.use(passport.initialize());
WebApp.connectHandlers.use(
"/",
(req, res, next) => {
// This part before the else is to trigger a basic auth popup to enter username/password
let credentials = basicAuth(req);
if (!credentials) {
res.statusCode = 401;
res.setHeader("WWW-Authenticate", "Basic");
res.end("Access denied");
} else {
passport.authenticate(
"ldapauth",
{
session: false
},
function(err, user, info) {
if (err) {
return next(err);
}
if (user) {
var token = jwt.sign(
{ username: user.sAMAccountName },
"someSecretString"
);
console.log("token", token);
next();
}
}
)(req, res, next);
}
},
function(req, res) {
console.log("debug point#2");
res.send({ status: "ok" });
}
);
}
I need to auth windows AD users recently. The scenario is below
Web pages runs at Server A ( Vue + vue-router )
Api interface runs at Server B ( node + express )
User input AD username & pwd on Web pages (Server A)
pass the username & pwd to the api interface on Server B to auth
Server B auth username & pwd via LDAP(windwos AD)
api on Server B returns the feedback to Web pages (Server A)
So, is there any solution could be implemented on Server B to auth username & pwd via LDAP?
Great thx!
I have used nodesspi https://github.com/abbr/nodesspi.
But it is only for windows env. And it seems that u can get the results via browser only to visit server B directly. Not passing param to call api on server B.
Anyway, it is a good scenario to study for me.
I found the solution. refer to:
Node JS LDAP Auth User
var ldap = require('ldapjs');
var client = ldap.createClient({
url: 'ldap://ldapserver:port/',
timeout: 5000,
connectTimeout: 10000
});
var opts = {
filter: '(&(cn=*))',
scope: 'sub',
// This attribute list is what broke your solution
attributes:['SamAccountName','dn']
};
console.log('--- going to try to connect user ---');
try {
client.bind(username, password, function (error) { //first need to bind
if(error){
console.log(error.message);
client.unbind(function(error) {if(error){console.log (error.message);} else{console.log('client disconnected');}});
} else {
console.log('connected');
client.search('ou=users, ou=compton, dc=batman, dc=com', opts, function(error, search) {
console.log('Searching.....');
search.on('searchEntry', function(entry) {
if(entry.object){
console.log('entry: %j ' + JSON.stringify(entry.object));
}
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
search.on('error', function(error) {
console.error('error: ' + error.message);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
}
});
} catch(error){
console.log(error);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
}
remember if you get 'error~~~: Size Limit Exceeded' error, use paged and sizeLimit param.
var opts = {
filter: '(objectclass=commonobject)',
scope: 'sub',
paged: true,
sizeLimit: 200
};
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
});
I got this error when I make subsequent request using mwbot on node.
response:
{ login:
{ result: 'Aborted',
reason: 'Cannot log in when using MediaWiki\\Session\\BotPasswordSessionProvider sessions' } } }
I am reading pages from mediawiki by providing a title. I thought that every request would need to login to read, but it seemed that I was wrong because this error seemed to complain that I already have logged in. But I don't know how the session can be read or how to find out that I already logged in or not.
the route:
router.get('/wikipage/:title', function(req, res, next) {
let title = req.params.title;
const MWBot = require('mwbot');
const wikiHost = "https://wiki.domain.com";
let bot = new MWBot();
let pageContent = "wiki page not created yet, please create";
bot.login({
apiUrl: wikiHost + "/api.php",
username: "xxx#apiuser",
password: "xxxxx"
}).then((response) => {
console.log("logged in");
return bot.read(title);
}).then((response) => {
for(let prop in response.query.pages) {
pageContent = response.query.pages[prop]['revisions'][0]['*'];
console.log("pageContent:", pageContent);
break;
}
res.json({
data: pageContent
});
}).catch((err) => {
// Could not login
console.log("error", err);
});
});
module.exports = router;
I presume you are running this in a browser, in which case the browser takes care of session cookie handling. You can check it the usual way via document.cookie.
I have a node site using Usergrid as the backend. I have created a login form screen, however when one user logs in it then shows that user being logged in to all other users who are on the site. If someone else logs in then it will overwrite the previously logged in user. How can I prevent the authenticated session from being shared across all users? I want each user to have their own authenticated session while browsing the site.
Login Code:
app.post("/login", function(req, res) {
if (client.isLoggedIn()) {
console.log("already logged in");
res.send({"status": "success"});
} else {
client.login(req.body.username, req.body.password, function(err) {
logger.debug("After Log In");
if (err) {
logger.error('Login Failed');
logger.error(err);
} else {
logger.debug(client.token);
client.authType = Usergrid.AUTH_APP_USER;
var options = {
method: 'GET',
endpoint: 'users/me'
};
client.request(options, function(err,data) {
if (err) {
console.log(err);
} else {
req.session['current_user'] = data.entities[0];
console.log(data);
console.log("SESSION");
console.log(req.session);
}
res.send({"status": "success"});
});
}
});
}
});
I think the problem is that you are using one instance of the Usergrid.Client object to serve many users. Instead, you should do what Usergrid does: when a user logs in, you give them the Usergrid access_token. You could send it back in a cookie, or in JSON data or whatever you choose.
Then you would expect subsequent HTTP request from the user to include the access_token in the URL or in a cookie, or whatever. On each request you create a new instance of the Usergrid.Client and pass in the token from the user, e.g.
var client = new Usergrid.Client({'token':'abcd5764adf...');