Need Assistance in integrating OAuth in VueJS App - node.js

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>

Related

Unauthorized request in Sharepoint API with MSAL token

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:

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

Is there a way to persist chat history in MS Bot Framework Web Chat after a page reload/navigation to another page?

I'm trying to persist the conversation a user has had with the bot during page reloads and navigation to other pages on the site the bot is linked to.
Currently these actions close the bot window and restart the conversation entirely, to the point that the bot's welcome message is triggered again.
The bot in question is embedded in the webpage following the instructions from the docs: https://learn.microsoft.com/bs-latn-ba/azure/bot-service/bot-service-channel-connect-webchat?view=azure-bot-service-4.0
I have read other articles that have used the conversationId to maintain the chat history between page loads, though this was for the DirectLine channel. As well as some other articles that suggested persisting the conversation in a database and passing the messages back into the chat window. Though this seems doesn't seem the best way to go about it.
I attempted to pass conversationId into the iframe but it did not work. Is there a way persist the conversation by passing the conversationId into the iframe?
This is the code for showing the chatbot in an iframe:
<iframe src='https://webchat.botframework.com/embed/THECHATBOT?s=YOUR_SECRET_HERE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>
This is my attempt at passing conversationId as a parameter:
<iframe src='https://webchat.botframework.com/embed/THECHATBOT?s=YOUR_SECRET_HERE&conversationId?=THE_CONVERSATIONID_VALUE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>
I expect the chat window to be populated with the conversation the user previously had, what I am getting is the conversation resets and no history is maintained.
If you are looking to do any sort of web chat customization, then I would highly recommend you steer away from using the Web Chat channel <iframe> option. It is useful if you need a simple plugin component, but it doesn't offer anywhere near the number of customization options that BotFramework-WebChat offers.
If you will consider using the v4 react-based Web Chat offering (referenced in the link above), then the following example will provide you with the functionality you are seeking.
Please note, for simplicity, I'm saving the conversationId in session storage.
Also, I'm generating a token by making an API call against a locally run direct line endpoint. I've included code at the end for doing the same. You could pass in your direct line secret against the directline/tokens/generate endpoint in the html file, however that is highly discouraged for security reasons.
Lastly, the watermark property used in the createDirectLine() method specifies the number of past activities to display (messages, cards, etc.).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>WebChat</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html,
body {
height: 100%;
width: 100%;
margin: 0;
}
#webchat {
height: 100%;
width: 40%;
}
#webchat>* {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="webchat" role="main"></div>
<script type="text/javascript"
src="https://unpkg.com/markdown-it/dist/markdown-it.min.js"></script>
<script
src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
<script>
( async function () {
let { token, conversationId } = sessionStorage;
if (!token) {
const res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
const { token: directLineToken } = await res.json();
sessionStorage['token'] = directLineToken;
token = directLineToken;
}
if (conversationId) {
const res = await fetch(`https://directline.botframework.com/v3/directline/conversations/${ conversationId }`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${ token }`,
},
});
let { conversationId } = await res.json();
sessionStorage['conversationId'] = conversationId;
}
const directLine = createDirectLine({
token,
webSockets: true,
watermark: 10
});
window.WebChat.renderWebChat( {
directLine: directLine,
}, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
} )().catch( err => console.error( err ) );
</script>
</body>
</html>
Here is the code for generating the token. I have this appended to the end of my index.js file in my bot. You can also run this as a separate project.
As I run my bot locally, the endpoint becomes available. You should be able to do something similar if you are running a C# bot. The port used here should be the same port referenced in the above directline/token call.
The directLineSecret is stored and accessed from a .env file.
/**
* Creates token server
*/
const bodyParser = require('body-parser');
const request = require('request');
const corsMiddleware = require('restify-cors-middleware');
const cors = corsMiddleware({
origins: ['*']
});
// Create server.
let tokenServer = restify.createServer();
tokenServer.pre(cors.preflight);
tokenServer.use(cors.actual);
tokenServer.use(bodyParser.json({
extended: false
}));
tokenServer.dl_name = 'DirectLine';
tokenServer.listen(process.env.port || process.env.PORT || 3500, function() {
console.log(`\n${ tokenServer.dl_name } listening to ${ tokenServer.url }.`);
});
// Listen for incoming requests.
tokenServer.post('/directline/token', (req, res) => {
// userId must start with `dl_`
const userId = (req.body && req.body.id) ? req.body.id : `dl_${ Date.now() + Math.random().toString(36) }`;
const options = {
method: 'POST',
uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
headers: {
'Authorization': `Bearer ${ process.env.directLineSecret }`
},
json: {
User: {
Id: userId
}
}
};
request.post(options, (error, response, body) => {
if (!error && response.statusCode < 300) {
res.send({
token: body.token
});
} else {
res.status(500);
res.send('Call to retrieve token from DirectLine failed');
}
});
});
Hope of help!
Re-updated - 8/6/2021
The script in the HTML above, technically, works though its implementation is not really clear. I've provided this snippet which simplifies the code.
Also, note the first line below: The CDN has since changed slightly. The current stable version pulls from latest, not master.
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
( async function () {
let { token, conversation_Id } = sessionStorage;
if ( !token ) {
const res = await fetch( 'http://localhost:3500/directline/conversations', { method: 'POST' } );
const { token: directLineToken, conversationId } = await res.json();
sessionStorage[ 'token' ] = directLineToken;
sessionStorage[ 'conversation_Id' ] = conversationId
token = directLineToken;
}
const directLine = createDirectLine( {
token
} );
window.WebChat.renderWebChat( {
directLine: directLine,
}, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
} )().catch( err => console.error( err ) );
</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 });
}
}
});
};

Login With Evernote

I'm trying to connect Evernote with Meteor.
But I'm having a really bad time, trying to get the Oauth token, I was trying to follow this example Evernote Sample Meteor, but is pretty old (2 years), but I tried to follow it and I got the idea.
I can get the connect to evernote, the login page and the email verification, my problem raised on the Meteor Method.
handleCallback, which need the "verify" param, which in this case is the
ouath_token
Second try.
On the evernote npm README they suggest to use oAuthjs, and I try with this code.
var hostName = "http://sandbox.evernote.com";
var options,oauth;
options = {
consumerKey: 'xxxxxxxxx',
consumerSecret: 'xxxxxxxxx',
callbackUrl : 'http://localhost:3000/oauth/auth',
signatureMethod : "HMAC-SHA1",
};
oauth.request({'method': 'GET', 'url': hostName + '/oauth',
'success': function(data){
console.log(data);
}, 'failure': function(data){
console.log(data);
}});
But it returns
(STDERR) No valid request transport found.
So I'm pretty stuck here.
The npm module provided by Evernote includes helper functions to get OAuth working.
Install the Evernote npm module via:
$npm install evernote
Below is the simplest single file example of implementing OAuth in a Evernote application I could put together. Just change the values of CONSUMER_KEY and CONSUMER_SECRET below and it should run just fine if you've installed Evernote:
var Evernote = require('evernote').Evernote;
var http = require("http");
var url = require("url");
CONSUMER_KEY="Put your consumer key here";
CONSUMER_SECRET="put your consumer secret here";
if (CONSUMER_KEY === "Put your consumer key here"){
console.error("\nPlease enter your Evernote consumer key and secret\n\nIf you don't have a key you can get one at:\nhttps://dev.evernote.com/#apikey\n")
process.exit(1)
}
var global = {};
global.oauthToken = '';
global.oauthSecret = '';
function getOauthVerifier(url) {
var regex = new RegExp("[\\?&]oauth_verifier=([^&#]*)"),
results = regex.exec(url);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
var handler = function(request, response) {
var params = url.parse(request.url)
var pathname = params.pathname;
console.log("Request for " + pathname + " received.");
var client = new Evernote.Client ({
consumerKey: "Put your consumer key here",
consumerSecret: "put your consumer secret here",
sandbox: true
});
if (pathname == "/"){
var callbackUrl = 'http://localhost:8888/oauth';
client.getRequestToken(callbackUrl, function(err, oauthToken, oauthSecret, results){
if(err) {
console.log(err);
}
else {
global.oauthToken = oauthToken;
global.oauthSecret = oauthSecret;
console.log("set oauth token and secret");
var authorizeUrl = client.getAuthorizeUrl(oauthToken);
console.log(authorizeUrl);
response.writeHead(200, {"Content-Type":"text/html"});
response.write("Please click here to authorize the application");
response.end();
}
});
}
else if (pathname == "/oauth"){
client.getAccessToken(
global.oauthToken,
global.oauthSecret,
getOauthVerifier(params.search),
function(error, oauthAccessToken, oauthAccessTokenSecret, results) {
if(error) {
console.log("error\n\n\n");
console.log(error);
}
else {
response.writeHead(200, {"Content-Type":"text/html"});
response.write(oauthAccessToken);
response.end();
}
}
);
}
else {
response.writeHead(200, {"Content-Type":"text/html"});
response.write("not a valid URL GO HOME ");
response.end();
}
};
http.createServer(handler).listen(8888);

Resources