How to auth windows AD users by node.js? - node.js

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
};

Related

"ReplyError: ERR unkown command 'json.set' ? Using redis rejson, google oauth

Using NodeJS express server to handle GOOGLE oauth via OpenID protocol on the server-side, I am trying to have the callback run this checklist after google redirects the user to the API with the auth code + in query string.
Only allow emails with special domain of school to pass (This is a school project).
Make exchange for Token Object containing Auth Token, Refresh Token, etc.
Store Auth token via cookie
Store whole token object via redis cache in JSON OBJECT... THIS IS WHERE I AM CAUGHT. (using the redis module REJSON. REJSON because user data from database can be added to object in memory for CACHING USER DATA...)
Next steps in logic include checking if user exists in mongodb, creating user in mongodb if not, redirecting to browser, carrying cookie, using auth token to check against redis cache as maintaining a persistenting session... will eventually be secure via hash, httpOnly, https etc...
Hopefully that lets you in on the picture of my logic, but this is seemingly a REDIS OR REDIS RELATED MODULE ISSUE.
To use the Redis REJSON module, I am running a redis-redisjson docker image which is exposing to PORT 6379, and the express server(running on PORT 4000) is to be the client of the redis cache server...
Node module Redis-rejson allows redisJSON commands to be mapped to javascript-friendly names.
And Node module Redis is a redis client library for node.
//This is a minimalist web-framework for NodeJS
var express = require("express");
//axios - handles HTTP requests
const axios = require("axios");
//redis - use for caching
//rejson - store JSON via redis
const redis = require("redis"),
rejson = require("redis-rejson");
rejson(redis);
//set ports
const PORT = process.env.PORT || 4000;
const REDIS_PORT = process.env.PORT || 6379;
const client = redis.createClient(REDIS_PORT);
// Init Express app
var app = express();
//callback route from google
app.get("/signin/callback", (req, res, next) => {
//declare vars from query string api return for later use
//console.log(req.query);
let hd = req.query.hd;
let authCode = req.query.code;
//Only allow GUHSD email domains
if(hd !== 'guhsd.net') {
console.log('you are shall not pass')
// The return is for stopping execution of controller
return res.redirect(301, 'http://localhost:3000/?error=invalid_domain');
}
//GET client_id and client_secret FROM JSON
//Make POST REQ to exchange AuthCode for JSON token object
axios.post('https://oauth2.googleapis.com/token', {
client_id: '<taken out as to not expose>',
client_secret: '<taken out as to not expose>',
code: authCode,
grant_type: 'authorization_code',
redirect_uri: 'http://localhost:4000/signin/callback'
})
.then((response) => {
console.log('Your token must to be here');
// Logic after exchange
//1. Start Sessions - Client HTTP only Cookie & Server Redis Cache
//2. Check if ACC exists - if so redirect client :)
//3. if ACC DOES NOT exist, create new acc!! then redirect client :)
async function passOver() {
console.log(response.data);
// example:
// {
// access_token: 'ya29.a0AfH6SMBSm3D99XAhmlxHtYOVVm7gZT9h8Bd8I9RgpYPhK0qXQwGT4fuMWhDsqDpcTfQuIg6bvY-tFUISX4Wm2Is-jprW2sstjmD4hjc2cQh5uIYODg1Re6v80FKENDdEAinTm9kid0QaKiaRxJQHt1deap-Ik1mcy2s',
// expires_in: 3599,
// refresh_token: '1//06qroj95zjugXCgYIARAAGAYSNwF-L9IrzWEi6q0wDpeUVKmnPZYZjZpsMS-KVT4fY9NzHgZCUxgY8fg2J_Nil2vJpMz52y-2pyY',
// scope: 'openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email',
// token_type: 'Bearer',
// id_token: 'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk2MGE3ZThlODM0MWVkNzUyZjEyYjE4NmZhMTI5NzMxZmUwYjA0YzAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIyNDM1NjY4NDcxMDItdTRkazg1Y21qcjEybWgya25ycHYzaW5zMnRjcnBzOHUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIyNDM1NjY4NDcxMDItdTRkazg1Y21qcjEybWgya25ycHYzaW5zMnRjcnBzOHUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTA1MzUzMTYxMjcxODg4Mjg5NjYiLCJoZCI6Imd1aHNkLm5ldCIsImVtYWlsIjoiMzMwMjk0QGd1aHNkLm5ldCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoidEhoVEl6eWp3MHcyRFpVaVpUMmxCQSIsIm5hbWUiOiJERVZJTiBQUk9WRU5DRSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vLVhTaV9oU2JsVWo0L0FBQUFBQUFBQUFJL0FBQUFBQUFBQUFBL0FNWnV1Y25HXzBwS3N0eVo3cEI0Wk0yZlpMQkNIUEU3UkEvczk2LWMvcGhvdG8uanBnIiwiZ2l2ZW5fbmFtZSI6IkRFVklOIiwiZmFtaWx5X25hbWUiOiJQUk9WRU5DRSIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTkwNjEwODE4LCJleHAiOjE1OTA2MTQ0MTh9.Nt2eGzCsKrUmyztqYWiZ16-1S7OCRcUFlSFvhVAy9HusYfLqp0nz3diUkuP-D_27BCtBsZCQ0JC1evPISwLX9H1hJs_GKYSD12s-ovJ8S0AzghY0M-AOuYxhGvKautusmXYfHvhfcPj7IKhPo_IXBl3x-ryOtRRrpJR6c30QPl1JUae74sAcLk8H1stLgqptrrRTgJWYdTXxJHSrZcR8RsLw2aY4GwuPGEX-AD6h51IZlNTl_6qNpaIt_7mFSUV-iF1PECAotfhKHdAryZCFRBq4XE6uJnYq3WnOVAMAEYwqT543pxarXOmLuAwhDqqewyuCXsjlbyhBys-4iEhYAg'
// }
//expires_in is in seconds... cookie takes milliseconds as expires arg.
//send to client, store in cookie(browser's session starts)
res.cookie(
"tokenResponse", `${response.data.access_token}`, {
maxAge: `${response.data.expires_in}`
}
)
var keyObject = JSON.stringify(response.data);
//set redis key to user
var user = 'user';
//store response.data in redis cache(server's session starts)
client.json_set(user, '.', keyObject, function (err) {
if (err) { throw err; }
console.log('Set JSON at key ' + user + '.');
client.json_get(user, '.access_token', function (err, value) {
if (err) { throw err; }
console.log('value of .', value); //outputs JSON
client.quit();
});
});
res.json(keyObject);
//redirect from server to frontend
// await res.redirect('http://localhost:3000');
}
//call passover
passOver();
})
//if err, log & redirect user to client
.catch((error) => {
console.log(error.request);
console.log(error.response.data);
return res.redirect(301, 'http://localhost:3000/?error=google_failed_exchange');
});
});
//start the express server
app.listen(4000, () =>
console.log(`App started on port ${PORT}`)
);
module.exports = app;
The err im getting is
if (err) { throw err; }
^
ReplyError: ERR unknown command 'json.set'
at parseError (E:\docker-node-react-nginx\backend\node_modules\redis-parser\lib\parser.js:179:12)
at parseType (E:\docker-node-react-nginx\backend\node_modules\redis-parser\lib\parser.js:302:14) {
command: 'JSON.SET',
args: [
'user',
'.',
'{"access_token":"ya29.a0AfH6SMCSN3_0fXFexXYUqrkfhlJ5FmdkO-eqKeiXeSnRzlwD5aBDpBF7y-pXMDBY1YFqXLf-JU0mc5FXVo8nZER-6wB-hAm7qW_0w4Z3TcfoQfOT7ZXg8ZqK2DPyW8TnsZELWe9eDBYvFqM0lTWyWe3Z9ZqUXgv6Kz4","expires_in":3599,"refresh_token":"1//06FRrH2GkKtP2CgYIARAAGAYSNwF-L9IrnL1ZpMhxaiYWYuSnI7p6DG0uFIO3Vu2qt40Scio5SAlGT0mvBZ8hWvaPcJEEnjaunDw","scope":"https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile","token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImZiOGNhNWI3ZDhkOWE1YzZjNjc4ODA3MWU4NjZjNmM0MGYzZmMxZjkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIyNDM1NjY4NDcxMDItdTRkazg1Y21qcjEybWgya25ycHYzaW5zMnRjcnBzOHUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIyNDM1NjY4NDcxMDItdTRkazg1Y21qcjEybWgya25ycHYzaW5zMnRjcnBzOHUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTA1MzUzMTYxMjcxODg4Mjg5NjYiLCJoZCI6Imd1aHNkLm5ldCIsImVtYWlsIjoiMzMwMjk0QGd1aHNkLm5ldCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiV2kwRVdlSl9fQmVsaGV1RF9SVEl6USIsIm5hbWUiOiJERVZJTiBQUk9WRU5DRSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vLVhTaV9oU2JsVWo0L0FBQUFBQUFBQUFJL0FBQUFBQUFBQUFBL0FNWnV1Y25HXzBwS3N0eVo3cEI0Wk0yZlpMQkNIUEU3UkEvczk2LWMvcGhvdG8uanBnIiwiZ2l2ZW5fbmFtZSI6IkRFVklOIiwiZmFtaWx5X25hbWUiOiJQUk9WRU5DRSIsImxvY2FsZSI6ImVuIiwiaWF0IjoxNTkwNjI0NDIyLCJleHAiOjE1OTA2MjgwMjJ9.IyF5uB2ldLBQLu3CVMDpXf8szEK_BbR8SPrtdpJR_Y3bHklJ8e3JYGQT9AWjkcSy0I4DNUhkXiFk25HvZ06u2ekGd_adSknUVNwZe_N1IQTlMF1m-oqWbaRtnr4oxerQg_YunZDD4z_Lh5ecSDVz4X8H39uO7jrAvY1CdnZfZ4D2Je8aV1Zns5JahKhOTopPcy5sE1dSBNPqqGvUiY9h0MQHne9byUz9jMvog3YI-8-uexjC_JWsbzMFjE65ze5_cUpApYB5tUrNTjqvhiYgcimIPOXoto_VIHHEEoho5uHOkUQw_UVXleUa9vI77W1j7U7HnH-h_3C5ylx7UEDm6Q"}'
],
code: 'ERR'
}
[nodemon] app crashed - waiting for file changes before starting...
import Redis from 'ioredis'
const connOpts = {
port: process.env.REDIS_PORT,
host: process.env.REDIS_HOST
}
const client = new Redis(connOpts)
const sets = (id, keyPaths, input) => {
return client.send_command('JSON.SET', id, keyPaths, JSON.stringify(input)).then((res) => {
return res
}).catch((e) => {
console.error('redis insertion error', e)
})
}
sets("134", ".", {"access_token":"ya29"}

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.

How to connect and find a user from Azure AD using Nodejs

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
});

Consuming DynamicsNAV WebService with Node.JS node-soap

I want to consume the WebService from Microsoft Dynamics NAV 2009 with a small node.js application. The Service itself works fine, I am using it with a c# application, now I want to get data into my nodejs/expressjs application but, I always get Invalid WSDL URL as an error message.
Here is the WSDL as my Browser sees it.
Now I tried to connect with node-soap, following the documentation, by normal and by basic auth, but everytime I get an Invalid WSDL URL error.
Here are the methods I tried for a test connection:
var url = "http://navsrv:7047/DynamicsNAV2/WS/Produktiv/Page/WDCETA";
var auth = "Basic " + new Buffer("*********" + ":" + ****************").toString("base64");
soap.createClient(url, function(err, client) {
console.log('Without basic out:');
if (err)
{
console.log('[ERROR] -> ');
console.log(err);
}
console.log(client);
});
soap.createClient(url, {wsdl_headers: {Authorization: auth} }, function(err, client) {
console.log('With basic out:');
if (err)
{
console.log('[ERROR] -> ');
console.log(err);
}
console.log(client);
});
And this is the response I get:
Without basic out:
[ERROR] ->
[Error: Invalid WSDL URL: http://navsrv:7047/DynamicsNAV2/WS/Produktiv/Page/WDDCETA
Code: 401
Response Body: ]
undefined
With basic out:
[ERROR] ->
[Error: Invalid WSDL URL: http://navsrv:7047/DynamicsNAV2/WS/Produktiv/Page/WDDCETA
Code: 401
Response Body: ]
undefined
As it turned out, the build in HTTP-Server from DyanmicsNAV requires SPNEGO or NTLM as authentication. After some tries creating a proper SPNEGO request with nodejs/node-soap I turned off SPNEGO and enabled NTLM.
With the help of soap-ntlm and httpntlm I could retrieve the wsdl.
This is some testing code how I could manage to retrieve the WSDL file. For now I am happy, but I guess when it comes to invoke function there will be some other issues :)
var soap = require('soap-ntlm');
var fs = require('fs');
var httpntlm = require('httpntlm');
var url = 'http://navsrv:7047/DynamicsNAV2/WS/Produktiv/Page/WDCETA';
var username = '*******';
var password = '***********';
httpntlm.get({
url: url,
password: password,
username: username
}, function(err, wsdl) {
if (err)
{
console.log('ERR: -> ');
console.log(err);
return;
}
fs.writeFile('wsdl_cache/WDCETA.wsdl', wsdl.body, function() {
soap.createClient(__dirname + '/wsdl_cache/WDCETA.wsdl', function(err, client) {
if (err) {
console.log('SOAP ERR: ->');
console.log(err);
return;
}
client.setSecurity(new soap.NtlmSecurity(username, password));
console.log(client);
});
})
});

How to retrieve user's additional information from Azure Mobile/App Services?

I need to get the user's extra information from social accounts like Facebook and Google+. When I first read about Azure Mobile Services I thought it to be the holy grail of social authentication. Well, after a full week of hair pulling I'm starting to reconsider my first impression. It does authenticate as easily as it could possibly do. I configured Google+ and FB to work with Azure, configured Azure to use the key/secret from each provider and it all just worked. I was able to login perfectly. The problem started when I tried to get information from the logged user, which I honestly think is basic!
Azure Mobile Services returns the UserId and a Token that you can not use to request the extra info on the selected provider. So even if I were to create a second request using FB's graph API for instance, that wouldn't work (I've tried!). That token is Azure's own token. So I found out from several Carlos Figueira (SE at Azure) posts that I should customize my Azure script, make a request to Azure and then I'd be able to get it working.
I've also read several posts from Carlos Figueira on how to implement that extra functionality and even though that was not what I was looking for (customizing the server) I decided to work with that. But my return type is a MobileServiceUser and that type only has 2 properties: UserId and MobileServiceAuthenticationToken. So even after adding the server script from Carlos I couldn't retrieve the extra information from my Xamarin App.
I've read a lot of things, researched a lot and couldn't find an answer =/ By the way this is not the answer:
How to get user name, email, etc. from MobileServiceUser?
Did anyone manage to make it work?
PS: I'm not posting any code here because it's working. If you think checking some part of my code would help decipher the problem just let me know.
Thanks in advance!
EDIT:
Script
function insert(item, user, request) {
item.UserName = "<unknown>"; // default
user.getIdentities({
success: function (identities) {
var url = null;
var oauth = null;
if (identities.google) {
var googleAccessToken = identities.google.accessToken;
url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
} else if (identities.facebook) {
var fbAccessToken = identities.facebook.accessToken;
url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
} else if (identities.microsoft) {
var liveAccessToken = identities.microsoft.accessToken;
url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken;
} else if (identities.twitter) {
var userId = user.userId;
var twitterId = userId.substring(userId.indexOf(':') + 1);
url = 'https://api.twitter.com/1.1/users/show.json?user_id=' + twitterId;
var consumerKey = process.env.MS_TwitterConsumerKey;
var consumerSecret = process.env.MS_TwitterConsumerSecret;
oauth = {
consumer_key: consumerKey,
consumer_secret: consumerSecret,
token: identities.twitter.accessToken,
token_secret: identities.twitter.accessTokenSecret
};
}
if (url) {
var requestCallback = function (err, resp, body) {
if (err || resp.statusCode !== 200) {
console.error('Error sending data to the provider: ', err);
request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
} else {
try {
var userData = JSON.parse(body);
item.UserName = userData.name;
request.execute();
} catch (ex) {
console.error('Error parsing response from the provider API: ', ex);
request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
}
}
}
var req = require('request');
var reqOptions = {
uri: url,
headers: { Accept: "application/json" }
};
if (oauth) {
reqOptions.oauth = oauth;
}
req(reqOptions, requestCallback);
} else {
// Insert with default user name
request.execute();
}
}
});
}
You're talking about the token on the client side correct? That token is specific only to the client. If you're using Server Side flow, the server is the only one with that token. If you want to send that to the client, you need to do that via a custom API you create.
This class you're talking about does only contain those two properties. But on your server side, your ServiceUser can access the different identity provider tokens in order to speak to those servers APIs. Your linked post is correct in how you access the token, you're mistaken on where you can access that token, it's only on the server side (if you use the server directed login flow).
Here is the custom API Script I had working in Mobile Services to return the profile of the logged in user. I am working on updating to Mobile Apps as some environment variables appear to have changed. Would love to know if anyone has gotten it to work with Mobile Apps.
exports.get = function (request, response) {
var user = request.user;
user.getIdentities({
success: function (identities) {
var req = require('request');
var url = null;
var oauth = null;
var userId = user.userId.split(':')[1];
console.log('Identities: ', identities);
if (identities.facebook) {
url = 'https://graph.facebook.com/me?access_token=' +
identities.facebook.accessToken;
} else if (identities.google) {
url = 'https://www.googleapis.com/oauth2/v3/userinfo' +
'?access_token=' + identities.google.accessToken;
} else if (identities.microsoft) {
url = 'https://apis.live.net/v5.0/me?access_token=' +
identities.microsoft.accessToken;
} else if (identities.twitter) {
var consumerKey = process.env.MS_TwitterConsumerKey;
var consumerSecret = process.env.MS_TwitterConsumerSecret;
oauth = {
consumer_key: consumerKey,
consumer_secret: consumerSecret,
token: identities.twitter.accessToken,
token_secret: identities.twitter.accessTokenSecret
};
url = 'https://api.twitter.com/1.1/users/show.json?' +
'user_id=' + userId + '&include_entities=false';
} else {
response.send(500, { error: 'No known identities' });
return;
}
if (url) {
var reqParams = { uri: url, headers: { Accept: 'application/json' } };
if (oauth) {
reqParams.oauth = oauth;
}
req.get(reqParams, function (err, resp, body) {
if (err) {
console.error('Error calling provider: ', err);
response.send(500, { error: 'Error calling provider' });
return;
}
if (resp.statusCode !== 200) {
console.error('Provider call did not return success: ', resp.statusCode);
response.send(500, { error: 'Provider call did not return success: ' + resp.statusCode });
return;
}
try {
var userData = JSON.parse(body);
response.send(200, userData);
} catch (ex) {
console.error('Error parsing response: ', ex);
response.send(500, { error: ex });
}
});
} else {
response.send(500, { error: 'Not implemented yet', env: process.env });
}
}
});
};

Resources