I have a nodejs app server attempting to access files on my organisational onedrive. The app is registered in Azure, I can make graph api calls and get back results. When I try to call the onedrive api it hangs without a response, the api is:
https://-my.sharepoint.com/personal//_api/file
The resourceid I have used is "https://-my.sharepoint.com". I have also tried Microsoft.Sharepoint
I am passing the oauth token like so (in the headers):
'Authorization': 'Bearer ' + aToken,
'Accept': 'application/json;odata=minimalmetadata;charset=utf-8'
I have also tried to add the URL in Azure for office 365 sharepoint online in the resoureid (http://office.microsoft.com/sharepoint/) but this comes back with the error resource not registered for the account.
Using a REST tool on chrome request headers like so:
Accept: application/json Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGci... Connection: keep-alive Content-Type: application/xml Origin: chrome-extension: //rest-console-id User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36
Response headers:
Status Code: 200
Date: Mon, 20 Oct 2014 18:53:55 GMT
Content-Encoding: gzip
Vary: Accept-Encoding
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Transfer-Encoding: chunked
P3P: CP="ALL IND DSP COR ADM CONo CUR CUSo IVAo IVDo PSA PSD TAI TELo OUR SAMo CNT COM INT NAV ONL PHY PRE PUR UNI"
X-SharePointHealthScore: 0
X-SP-SERVERSTATE: ReadOnly=0
request-id: 5611c49c-b0b2-1000-8ca2-5acda39588a3
MicrosoftSharePointTeamServices: 16.0.0.3312
X-MS-InvokeApp: 1; RequireReadOnly
Last-Modified: Mon, 20 Oct 2014 18:53:54 GMT
Server: Microsoft-IIS/7.5
SPRequestGuid: 5611c49c-b0b2-1000-8ca2-5acda39588a3
X-FRAME-OPTIONS: SAMEORIGIN
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Cache-Control: private, max-age=0
SPClientServiceRequestDuration: 1642
X-Content-Type-Options: nosniff
Expires: Sun, 05 Oct 2014 18:53:54 GMT
Request:
Request Url: https://xxxx-my.sharepoint.com/personal/satish_ramjee_xxxxx_co_uk/_api/files
Request Method: GET
Status Code: 200
Params: {}
The pertinent code is as follows
'use strict';
var express = require('express');
var request = require('request');
var http = require('http');
var path = require('path');
var passport = require('passport');
var AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2');
var engine = require('ejs-locals');
var app = express();
var config = {
// Enter the App ID URI of your application. To find this value in the Windows Azure Management Portal,
// click Active Directory, click Integrated Apps, click your app, and click Configure.
// The App ID URI is at the bottom of the page in the Single Sign-On section.
realm: 'http://localhost:4000',
// Enter the endpoint to which your app sends sign-on and sign-out requests when using WS-Federation protocol.
// To find this value in the Windows Azure Management Portal, click Active Directory, click Integrated Apps,
// and in the black menu bar at the bottom of the page, click View endpoints.
// Then, copy the value of the WS-Federation Sign-On Endpoint.
// Note: This field is ignored if you specify an identityMetadata url
identityProviderUrl: 'https://login.windows.net/8b87af7d-8647-xxxxxxx/wsfed',
// Enter the logout url of your application. The user will be redirected to this endpoint after
// the auth token has been revoked by the WSFed endpoint.
logoutUrl: 'http:/localhost:4000/',
// Enter the URL of the federation metadata document for your app or the cert of the X.509 certificate found
// in the X509Certificate tag of the RoleDescriptor with xsi:type="fed:SecurityTokenServiceType" in the federation metadata.
// If you enter both fields, the metadata takes precedence
identityMetadata: 'https://login.windows.net/8b87af7d-8647-4dc7-xxxxxxxx/federationmetadata/2007-06/federationmetadata.xml'
};
var graphConfig = {
// Enter the domain for your Active directory subscription, such as contoso.onmicrosoft.com
tenant: '8b87af7d-8647-4dc7-xxxxxxxxxxxxxxx',
// Enter the Client ID GUID of your app.
// In the Windows Azure Management Portal, click Active Directory, click your tenant,
// click Integrated Apps, click your app, and click Configure.
// The Client ID is on this app configuration page.
clientid: '2462ee60-5695-xxxxxxxxxxxxxx',
//Enter the value of the key for the app. You can create the key on the Configure page for the app.
// The value appears only when you first save the key. Enter the saved value.
clientsecret: 'xxxxxxxxxxxxxxxx'
};
// array to hold logged in users
var users = [];
// AAD Graph Client for AAD queries
var graphClient = null;
var aToken;
// use ejs-locals for all ejs templates:
app.engine('ejs', engine);
app.configure(function(){
app.set('port', process.env.PORT || 4000);
app.set('views',__dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser('your secret here'));
app.use(express.session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});
app.configure('development', function(){
app.use(express.errorHandler());
});
var findByEmail = function (email, fn) {
for (var i = 0, len = users.length; i < len; i++) {
var user = users[i];
if (user.email === email) {
return fn(null, user);
}
}
return fn(null, null);
};
// Simple route middleware to ensure user is authenticated.
// Use this route middleware on any resource that needs to be protected. If
// the request is authenticated (typically via a persistent login session),
// the request will proceed. Otherwise, the user will be redirected to the
// login page.
var ensureAuthenticated = function(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login');
};
//var RESOURCE = "https://graph.windows.net";
//var REST_CALL = 'https://graph.windows.net/' + graphConfig.tenant + '/Users()';
var RESOURCE = "https://xxxx-my.sharepoint.com";
var REST_CALL = "https://xxxx-my.sharepoint.com/personal/satish_ramjee_xxxxxx_co_uk/_api/files";
//passport.use(wsfedStrategy);
passport.use(new AzureAdOAuth2Strategy ({
clientID: graphConfig.clientid,
clientSecret: graphConfig.clientsecret,
tenant: graphConfig.tenant,
resource: RESOURCE,
callbackURL: "http://localhost:4000/callback",
},
function(accessToken, refreshToken, params, profile, done) {
console.log("access token ---> " + accessToken);
aToken = accessToken;
console.log("done ---> " + JSON.stringify(done));
var waadProfile = profile || jwt.decode(params.id_token, '', true);
console.log("waad ---> " + JSON.stringify(waadProfile));
// _res.redirect("/ok");
// User.findOrCreate({ id: waadProfile.upn }, function (err, user) {
return done();
// });
}));
var sp_files = function(callback) {
var headers = {
'Authorization': 'Bearer ' + aToken,
'Accept': 'application/json',
};
if (RESOURCE.indexOf("graph") != -1) {
headers[ 'x-ms-dirapi-data-contract-version'] = '0.5';
}
console.log("CALL___________________ "+REST_CALL);
request({
url: REST_CALL,
// qs: qs,
headers: headers
}, function(err, resp, body) {
console.log("Body " + body);
console.log("Err " + err);
// if (err) return callback(err, null);
if (resp && resp.statusCode != 200) {
return callback(new Error(body), null);
}
else if (!resp) {
return callback(null, null);
}
// { results:
// [ { __metadata: [Object],
// Manager: [Object],
// DirectReports: [Object],
var d = JSON.parse(body).d,
users = d.results;
// meta = buildMetadata(d);
console.log("users" + users);
return callback(users);
})
console.log("Sent request---->");
}
http.createServer(app).listen(app.get('port'), function(){
console.log("Express server listening on port " + app.get('port'));
});
var graphQuery = function(res, user) {
graphClient.getUsers(function(err, result) {
if(err) {
res.end('GraphClient.getUsers error:' + err + '\n');
} else {
console.log("User " + JSON.stringify(result) );
//res.render('index', { user: user, data: JSON.stringify(result) });
}
// get user properties (user.DisplayName, user.Mail, etc.)
});
};
var doWaad = function(res, user) {
if(graphClient === null) {
waad.getGraphClientWithClientCredentials2(graphConfig.tenant, graphConfig.clientid, graphConfig.clientsecret, function(err, client) {
if(err) {
res.end('waad.getGraphClientWithClientCredentials2 error:' + err + '\n');
} else {
graphClient = client;
graphQuery(res, user);
}
});
} else {
graphQuery(res, user);
}
};
app.get('/cb', function(req, res) {
console.log("cb");
});
app.get('/fail', function(req, res) {
console.log("fail");
});
app.get('/ok', function(req, res) {
console.log("*************** ok ", req.user);
//doWaad(res, req.user);
});
app.get('/', function(req, res){
if (aToken) {
console.log("T: " + aToken);
sp_files(function(d) {
var mail = [];
if (d)
for (var i=0; i< d.length; i++) {
console.log(i + ">>>> " + d[i]);
// if (d[i].Mail)
// mail.push(d[i].Mail);
if (d[i])
mail.push(JSON.stringify(d[i]));
}
res.render('result', { user: d, data: mail, oauth: aToken});
});
} else {
res.render('index', { user: null});
}
});
app.get('/account', ensureAuthenticated, function(req, res){
res.render('account', { user:req.user });
});
app.get('/login',
passport.authenticate('azure_ad_oauth2', {
failureRedirect: '/fail'
})
);
app.get('/callback',
passport.authenticate('azure_ad_oauth2', { failureRedirect: '/', failureFlash: true }),
function(req, res) {
// Successful authentication, redirect home.
console.log("================= callback");
res.redirect('/ok');
}
);
app.get('/logout', function(req, res){
// clear the passport session cookies
req.logout();
// We need to redirect the user to the WSFED logout endpoint so the
// auth token will be revoked
wsfedStrategy.logout({}, function(err, url) {
if(err) {
res.redirect('/');
} else {
res.redirect(url);
}
});
});
// Passport session setup.
// To support persistent login sessions, Passport needs to be able to
// serialize users into and deserialize users out of the session. Typically,
// this will be as simple as storing the user ID when serializing, and finding
// the user by ID when deserializing.
passport.serializeUser(function(user, done) {
done(null, user.email);
});
passport.deserializeUser(function(id, done) {
findByEmail(id, function (err, user) {
done(err, user);
});
});
This is the main app.js node code, which works with graph api but not with the sharepoint api. Once the token is received the function sp_files is called which makes an https request. The sharepoint call hangs with no response and although this is javascript it is nevertheless server side so cross domain issues should not be relevant here.