Unauthorized request in Sharepoint API with MSAL token - azure

Background
I'm developing a mobile app which authenticate with Azure Active Directory (Microsoft Authentication), and have access to some information in Sharepoint. My first choice was to use Ionic Cordova, but Cordova-MSAL integration seems to be not supported. So we have chosen Xamarin, in order to develop an cross plataform app, but compatible with Microsoft authentication.
Situation
I am trying to connect with Sharepoint API.
1- I have registered a new app in Azure (Active Directory), and
given permissions.
2- I am getting the bearer token with MSAL
library (in web and with Xamarin), after logging in myself (like in
the link below):
https://learn.microsoft.com/es-es/azure/active-directory/develop/quickstart-v2-javascript
3- Now I'm making the following request to Sharepoint API.
url: http://site url/_api/web/lists(guid'list GUID'),
method: GET
Headers:
Authorization: "Bearer " + accessToken
accept: "application/json;odata=verbose"
BUT I'm always getting the following error:
{"error_description":"Invalid JWT token. No certificate thumbprint specified in token header."}
I'm reading a lot of people talking about errors with MSAL, but is the official way (ADAL looks like about to be deprecated).
Any ideas will be appreciated, thanks a lot.

I too was facing this issue, when utilizing MSAL.js to authenticate and acquire an access token to make successful calls to the SharePoint Online API.
The following documentation from Microsoft provided an awesome example and explanation of authentication via MSAL: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa
However, I needed to move forward with utilizing the acquired tokens to call the SharePoint API directly--rather than consuming the services via the Graph API.
To resolve the problem, I had to define my scopes as follows:
scopes: [https://{tenantName}.sharepoint.com/.default]
Using the example from the Microsoft article, I made the necessary changes. I was able to utilize the acquired access token to successfully make calls to the SharePoint API:
<!DOCTYPE html>
<html>
<head>
<title>Quickstart for MSAL JS</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.0/js/msal.js">
</script>
</head>
<body>
<h2>Welcome to MSAL.js Quickstart</h2><br />
<h4 id="WelcomeMessage"></h4>
<button id="SignIn" onclick="signIn()">Sign In</button><br /><br />
<pre id="json"></pre>
<script>
var msalConfig = {
auth: {
clientId: "{clientID}",
authority: "https://login.microsoftonline.com/organizations"
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
var sharePointConfig = {
sharePointFileEndpoint: "https://{tenantName}.sharepoint.com/sites/{siteName}/_api/web/GetFolderByServerRelativeUrl('Shared Documents/{folderName}')/Files('testFile.txt')/$value"
}
//the defined scopes were updated for making calls to the SharePoint API.
var requestObj = {
scopes: ["https://{tenantName}.sharepoint.com/.default"]
};
var myMSALObj = new Msal.UserAgentApplication(msalConfig);
// Register Callbacks for redirect flow
myMSALObj.handleRedirectCallback(authRedirectCallBack);
function signIn() {
myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
//Login Success
showWelcomeMessage();
acquireTokenPopupAndCallAPI();
}).catch(function (error) {
console.log(error);
});
}
function acquireTokenPopupAndCallAPI() {
//Always start with acquireTokenSilent to obtain a token in the signed in user from cache
myMSALObj.acquireTokenSilent(requestObj).then(function (tokenResponse) {
//Http Request
callAPI(sharePointConfig.sharePointFileEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
// Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
// Call acquireTokenPopup(popup window)
if (requiresInteraction(error.errorCode)) {
myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
//Http Request
callAPI(sharePointConfig.sharePointFileEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
});
}
});
}
function graphAPICallback(data) {
document.getElementById("json").innerHTML = JSON.stringify(data, null, 2);
}
function showWelcomeMessage() {
var divWelcome = document.getElementById('WelcomeMessage');
divWelcome.innerHTML = 'Welcome ' + myMSALObj.getAccount().userName + "to Microsoft Graph API";
var loginbutton = document.getElementById('SignIn');
loginbutton.innerHTML = 'Sign Out';
loginbutton.setAttribute('onclick', 'signOut();');
}
function authRedirectCallBack(error, response) {
if (error) {
console.log(error);
}
else {
if (response.tokenType === "access_token") {
callAPI(sharePointConfig.sharePointFileEndpoint, tokenResponse.accessToken, graphAPICallback);
} else {
console.log("token type is:" + response.tokenType);
}
}
}
function requiresInteraction(errorCode) {
if (!errorCode || !errorCode.length) {
return false;
}
return errorCode === "consent_required" ||
errorCode === "interaction_required" ||
errorCode === "login_required";
}
// Browser check variables
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
var msie11 = ua.indexOf('Trident/');
var msedge = ua.indexOf('Edge/');
var isIE = msie > 0 || msie11 > 0;
var isEdge = msedge > 0;
//If you support IE, our recommendation is that you sign-in using Redirect APIs
//If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
// can change this to default an experience outside browser use
var loginType = isIE ? "REDIRECT" : "POPUP";
if (loginType === 'POPUP') {
if (myMSALObj.getAccount()) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenPopupAndCallAPI();
}
}
else if (loginType === 'REDIRECT') {
document.getElementById("SignIn").onclick = function () {
myMSALObj.loginRedirect(requestObj);
};
if (myMSALObj.getAccount() && !myMSALObj.isCallback(window.location.hash)) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenPopupAndCallAPI();
}
} else {
console.error('Please set a valid login type');
}
function callAPI(theUrl, accessToken, callback) {
var xmlHttp = new XMLHttpRequest();
/*
xmlHttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200)
callback(JSON.parse(this.responseText));
}
*/
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
xmlHttp.send();
}
function signOut() {
myMSALObj.logout();
}
</script>
</body>
</html>
The above example is a modified version of the example from Microsoft. I was able to utilize this for successful testing.

The token is invalid, please check the way you got the access token. I granted AllSites.FullControl permission to the app. So the scope should be
https://{xxx}.sharepoint.com/AllSites.FullControl
The response:

Related

Need Assistance in integrating OAuth in VueJS App

I have a front end VueJS application running on port 8080 and a NodeJS server on port 3000. Now I need to integrate a secondary app whose API I need to access on a button click from my VueJS application. My current server architecture is as follows, I need to know whether I need to create a new server for the Authorization server or I can integrate with port 3000?
If I need to create a new server Authorization server, how do I add it? what are the settings. The problem what I faced is, I tried integrating the Authorization server with my backend server with port 3000, I was able to use the resource server, however I was able to call add API call only through redirect url as shown below (OAuth.js):
router.get('/oauth', async (req: Request, res: Response)=>{
oAuthSession = new OAuth(config.oauthRequestTokenUri, config.oauthAccessTokenUri, config.clientKey, config.clientSecret,
config.oAuthVersion, config.authorizeCallbackUri, config.oAuthSignatureMethod, config.oAuthNonceSize, config.oAuthCustomHeaders);
......
}
router.get('/callback', async (req: Request, res: Response)=>{
tokens.verifier = req.query.oauth_verifier;
l('----- Callback - Verifier Received -----');
l(JSON.stringify(tokens, null, 2));
l('----- Requesting Access Token and Secret -----');
oAuthSession.getOAuthAccessToken(tokens.requestToken, tokens.requestTokenSecret, tokens.verifier, function (error, token, secret, results) {
tokens.accessToken = token;
tokens.accessTokenSecret = secret;
l('----- Access Token and Secret Received -----');
l('StatusCode => ' + results.statusCode);
l(JSON.stringify(tokens, null, 2));
oAuthSession.get(config.platformBaseUri, tokens.accessToken, tokens.accessTokenSecret, function (error, responseData, result) {
l('StatusCode => ' + result.statusCode);
l('----- Ready to do OAuth authenticated calls now-----');
res.redirect(`http://localhost:3000/auth/add?accessToken=${tokens.accessToken}&accessTokenSecret=${tokens.accessTokenSecret}`)
res.end();
But when I tried calling the API call from the frontend VueJS, the API call doesn't get called. It triggers only thorugh the redirect URL shown in the above code. How should the API call be used from the frontend, should there be any configuration changes done for the new server (Authorization server) if added. I am newbie to this domain, if there is any lack of understanding in my problem, please let me know, I will try my best to clarify it.
Architecturally speaking, you should have:
1 OAuth server
1 front-end
2 APIs
1 OAuth client (If both of your APIs will accept a token from you OAuth server, likely)
The front-end is the OAuth client.
So, your VueJS client should:
Get a token from the OAuth server
Call API 1
In the button handler (or whatever), call the API 2
Both APIs should validate the token presented to them to ensure that they came from the OAuth server and are trustworthy.
Here's an example (sans-OAuth server and sans-API) that shows how the client/front-end will work. It's derived from this example that has more info.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Public Client Sample</title>
</head>
<body>
<h1>Public Client Sample</h1>
<button id="startButton">Start OAuth Flow</button>
<span id="result"></span>
<div id="app">
<button v-on:click="callApi1">Call API 1</button>
<button v-on:click="callApi2">Call API 2</button>
{{ info }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const authorizeEndpoint = "https://localhost:8443/dev/oauth/authorize";
const tokenEndpoint = "https://localhost:8443/dev/oauth/token";
const clientId = "public_client";
var access_token;
if (window.location.search && window.location.search.length > 0) {
var args = new URLSearchParams(window.location.search);
var code = args.get("code");
if (code) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var response = xhr.response;
var message;
if (xhr.status == 200) {
var access_token = response.access_token;
axios.defaults.headers.common["Authorization"] = "Bearer " + access_token;
message = "Access Token: " + access_token;
}
else {
message = "Error: " + response.error_description + " (" + response.error + ")";
}
document.getElementById("result").innerHTML = message;
};
xhr.responseType = 'json';
xhr.open("POST", tokenEndpoint, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(new URLSearchParams({
client_id: clientId,
code_verifier: window.sessionStorage.getItem("code_verifier"),
grant_type: "authorization_code",
redirect_uri: location.href.replace(location.search, ''),
code: code
}));
}
}
document.getElementById("startButton").onclick = function() {
var codeVerifier = generateRandomString(64);
generateCodeChallenge(codeVerifier).then(function(codeChallenge) {
window.sessionStorage.setItem("code_verifier", codeVerifier);
var redirectUri = window.location.href.split('?')[0];
var args = new URLSearchParams({
response_type: "code",
client_id: clientId,
code_challenge_method: "S256",
code_challenge: codeChallenge,
redirect_uri: redirectUri
});
window.location = authorizeEndpoint + "/?" + args;
});
}
async function generateCodeChallenge(codeVerifier) {
var digest = await crypto.subtle.digest("SHA-256",
new TextEncoder().encode(codeVerifier));
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
function generateRandomString(length) {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
var vueApp = new Vue({
el: '#app',
data () {
return {
info: null
}
},
methods: {
callApi1: function(event) {
console.log("Call API 1");
axios
.get('https://httpbin.org/bearer')
.then(response => (this.info = response));
},
callApi2: function(event) {
console.log("Call API 2");
axios
.get('https://httpbin.org/headers')
.then(response => (this.info = response));
}
}
});
</script>
</body>
</html>

Why does MSAL loginPopup respond with „the application client asked for scope that doesn't exist on the resource“?

(Continuation of question: Why is my SPA, which is calling my WebAPI, using Azure Active Directory, receiving "Authorization has been denied for this request."?)
My client SPA is trying to call a protected WebAPI (service). The client uses MSAL (Micosoft Authentication Libraries). The problem happens before calling the API, i.e. in the picture below somewhere between 1 and 2.
Here is the client
<!DOCTYPE html>
<html>
<head>
<title>Quickstart for MSAL JS</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.3.4/bluebird.min.js"></script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.2/js/msal.js"></script>
</head>
<body>
<div class="container">
<div>
<button id="GetTodos" onclick="getTodos()">Get Todos</button>
</div>
</div>
<script>
var msalConfig = {
auth: {
clientId: 'xxxxxxxxxxxxxxxxxxxxxxx2427',
authority: "https://login.microsoftonline.com/my.tenant"
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true
}
};
var requestObj = {
// scopes: ["user.read"]
scopes: ["access_as_user"]
};
var myMSALObj = new Msal.UserAgentApplication(msalConfig);
myMSALObj.handleRedirectCallback(authRedirectCallBack);
function getTodos() {
console.log("getTodos ...");
myMSALObj.loginPopup(requestObj)
.then(response => {
myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
console.log("success");
//this.responseText
} else {
console.log("failure");
}
}
const apiUrl = "https://localhost:44321/api/todolist";
xmlHttp.open("GET", apiUrl, true); // true for asynchronous
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + tokenResponse.accessToken);
xmlHttp.send();
}).catch(function (error) {
console.log(error);
});
})
.catch(err => {
// handle error
console.log("login popup failed.", err);
});
}
function authRedirectCallBack(error, response) {
console.log('authRedirectCallBack');
if (error) {
console.log(error);
} else {
if (response.tokenType === "access_token") {
console.log("response.tokenType === access_token");
} else {
console.log("token type is:" + response.tokenType);
}
}
}
</script>
</body>
</html>
Both client and service are registered apps on Azure Active Directory
The client has API permissions to access the service.
And the service does expose an API with the scope access_as_user
However the call
myMSALObj.loginPopup(requestObj)
causes
ServerError: "AADSTS650053: The application 'ClientSPA' asked for scope 'access_as_user' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'.
And when using Chrome I get this message
Further Investigation
Instead of asking for scope „access_as_user“, I ask for „user.read“
This gives me an access token. But this causes the WebAPI to respond with
Authorization has been denied for this request
And when I decode the access token I see that the audience is https://graph.microsoft.com. But I am expecting the audience to be „https://my.tenant/TodoListService-NativeDotNet“. See picture below (the obfuscated lines do contain information that is specific to my user and my registered app)
Questions
Where does the resource '00000003-0000-0000-c000-000000000000' ID come from (it is mentioned in the error message)? It is not the application ID of the client nor of the service.
What have I done incorrectly, either in the configuration on Azure Active Directory or in the client?
Could CORS be a problem? I have setup the service to allow CORS.
The scope should include the exposing resource's identifier (the Application ID URI). You can find the full value by going to "Expose an API" -> Edit a Scope -> Copy the label at the top...
var requestObj = {
// scopes: ["https://graph.microsoft.com/user.read"]
scopes: ["https://ServiceWebAPI/TodoListService-NativeDotNet-access_as_user"]
};
Further reading on individual scopes here: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#requesting-individual-user-consent

Get Token for Azure ADv2 Rest Api Protected

I create an WebApi using visual studio and the wizard, for a protected api.
The result was an new application in the Azure Portal, and a configuration file jsonconfig (I am using netcore 2.2)
The web api is very simple a part of the code is
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
Now I am trying to get a token to call the api, using the next code
var msalConfig = {
auth: {
clientId: '83a8a6ee-afd5-41d3-92bd-2a6352cff7da', //This is your client ID
authority: "https://login.microsoftonline.com/d7124a8f-3301-4c72-9231-4bb39d8b95a3" //This is your tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
and the calling
var requestObj2 = {
scopes:["https://xxxxxx.com/Test2019/user_impersonation"]
};
var myMSALObj = new Msal.UserAgentApplication(msalConfig);
function signIn() {
myMSALObj.loginPopup(requestObj).then(function (loginResponse) {
//Successful login
showWelcomeMessage();
//Call MS Graph using the token in the response
acquireTokenPopupAndCallMSGraph();
}).catch(function (error) {
//Please check the console for errors
console.log(error);
});
}
and at the end
function acquireTokenPopupAndCallMSGraph() {
//Always start with acquireTokenSilent to obtain a token in the signed in user from cache
myMSALObj.acquireTokenSilent(requestObj2).then(function (tokenResponse) {
console.log(tokenResponse.accessToken);
alert('autenticado');
callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
// Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY)
// Call acquireTokenPopup(popup window)
if (requiresInteraction(error.errorCode)) {
myMSALObj.acquireTokenPopup(requestObj).then(function (tokenResponse) {
callMSGraph(graphConfig.graphMeEndpoint, tokenResponse.accessToken, graphAPICallback);
}).catch(function (error) {
console.log(error);
});
}
});
}
Everything works but the token generated when used in postman in the Authorization Header ="Bearer Token"
Does not work.
Please any advice on how to get the token.. :(
Thanks!
The way you get access token is correct. Replace the value of scopes by scopes:["83a8a6ee-afd5-41d3-92bd-2a6352cff7da/.default"] and try again.
If this still doesn't work, paste the error message here.
Here is a complete example regarding calling the backend web api using access token.

Login with Amazon button to retrieve the customer profile?

<script type='text/javascript'>
window.onAmazonLoginReady = function() {
amazon.Login.setClientId('-your-client-id');
};
</script>
<script type='text/javascript' src='https://static-eu.payments-amazon.com/OffAmazonPayments/uk/sandbox/lpa/js/Widgets.js'></script>
<div id="AmazonPayButton"></div>
<script type="text/javascript">
var authRequest;
OffAmazonPayments.Button("AmazonPayButton", "-emailid-", {
type: "LwA",
authorization: function() {
loginOptions = {
scope: "profile payments:widget payments:shipping_address payments:billing_address", popup: "true"
};
authRequest = amazon.Login.authorize(loginOptions, "return url");
},
onError: function(error) {
// your error handling code
}
});
could you please check and let me know what else i need to amend for retriving the amazon customer profile.
source:
https://payments.amazon.co.uk/developer
Thanks
This code is supposed to pop up a window where the user can login and authorize the scopes you've requested. It will then redirect them to the "return url" you've specified.
The code running under your return URL must be able to take the info provided in the query string attached to the URL and then fetch the profile data from Amazon on the back-end.
I believe the JavaScript you're using defaults to an Implicit grant and will return an access token good for one hour plus a refresh token which can be used to retrieve a new access token when the current one expires.
You can use a current access token to call the profile API and get the profile information.
See their developer documentation and SDKs for different languages at: https://payments.amazon.co.uk/developer/documentation
<script type="text/javascript">
var authRequest;
OffAmazonPayments.Button("AmazonPayButton", "---Your Seller ID---", {
type: "LwA",
authorization: function() {
loginOptions = { scope: "profile payments:widget payments:shipping_address payments:billing_address" };
authRequest = amazon.Login.authorize(loginOptions, function(response) {
amazon.Login.retrieveProfile(response.access_token, function(response) {
alert('Hello, ' + response.profile.Name);
alert('Your e-mail address is ' + response.profile.PrimaryEmail);
alert('Your unique ID is ' + response.profile.CustomerId);
if (window.console && window.console.log)
window.console.log(response);
window.location.href = "--Return Url--";
});
});
}
});
</script>

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