Unauthorized when fetching Tasks with Microsoft graph - javascript

I want to fetch my tasks within Javascript and possibly add new ones, but let's focus on fetching a task.
I made an app and use the msal.js file to get a token. I get prompted to allow the app to read/write from my account, the popup closes and I've obtained a token!
So far so good, but when I try to fetch my tasks the API responds with "unauthorized". When I check the headers I can see I sent along "bearer [token]" however.
I'm completely clueless on how to get my tasks by now since I did get a proper token and I've followed the guided setup to make sure I send along the token.
In my app (which I created on https://apps.dev.microsoft.com) I've set all Task related permissions and User.read for good measure. As for the platform I've set "Web".
Is there something I'm missing or mis-configuring?
My init method:
const self = this
this.userAgentApplication = new Msal.UserAgentApplication(this.clientID, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
this.userAgentApplication.loginPopup(['Tasks.readwrite']).then(function (token) {
let user = self.userAgentApplication.getUser()
if (user) {
self.token = token
localStorage.setItem('token', token)
self.getTasks()
}
}, function (error) {
console.log(error)
})
My getTasks method:
const bearer = 'Bearer ' + this.token
let headers = new Headers()
headers.append('Authorization', bearer)
let options = {
method: 'GET',
headers: headers
}
// Note that fetch API is not available in all browsers
fetch('https://outlook.office.com/api/v2.0/me/tasks', options).then(function (response) {
let contentType = response.headers.get('content-type')
if (response.status === 200 && contentType && contentType.indexOf('application/json') !== -1) {
response.json().then(function (data) {
console.log(data)
})
.catch(function (error) {
console.log(error)
})
} else {
response.json().then(function (data) {
console.log(data)
})
.catch(function (error) {
console.log(error)
})
}
})
.catch(function (error) {
console.log(error)
})

Your token is scoped for Graph, not Outlook. Tasks.readwrite will default to the Microsoft Graph and won't work against the Outlook endpoint.
Change this bit:
this.userAgentApplication.loginPopup(['Tasks.readwrite'])
To:
this.userAgentApplication.loginPopup(['https://outlook.office.com/Tasks.readwrite'])

You are trying to use Microsoft Graph, so the request should look like
GET https://graph.microsoft.com/beta/users/{id|userPrincipalName}/outlook/tasks
It's documented here:https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/api/outlookuser_list_tasks
I believe you got a Microsoft Graph token but you're trying to use it on the Outlook REST endpoint, which would not work.

Related

401 Error - Call REST API secured with AZURE Active Directory (JavaScript Client)

Hi All,
I got a scenario in which i am supposed to call a REST api that is secured by a AZURE ACTIVE DIRECTORY. Most of the code runs fine and i am able to get the token using myMSALObj.acquireTokenSilent function too.
401 ERROR COMES WHEN I SEND AJAX CALL USING THAT TOKEN, YOU CAN SEE FOLLOWING CODE
User is there in Active Directory for which i get proper token, i dont know why my ajax call fails when i send that token to rest-api, please help
<script type="text/javascript">
const msalConfig = {
auth: {
clientId: "94edf294-08ae-489f-8621-c6d98009afa8",
authority: "https://login.microsoftonline.com/c483b3c4-21a6-4c93-95ea-7436cf318a68",
redirectUri: "https://localhost:44334/",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
const myMSALObj = new Msal.UserAgentApplication(msalConfig);
function CallApi() {
// Add scopes for the id token to be used at Microsoft identity platform endpoints.
const loginRequest = {
scopes: ["User.Read"],
};
myMSALObj.loginPopup(loginRequest)
.then((loginResponse) => {
const accessTokenRequest = {
scopes: ["api://8c2b0253-f9e8-442c-bccf-b4a8bbe73b59/access_as_user"]
};
myMSALObj.acquireTokenSilent(accessTokenRequest).then((accessTokenResponse) => {
var accessToken = accessTokenResponse.accessToken;
var apiEndpoint = "https://localhost:44387/api/hello";
var bearer = "Bearer " + accessToken;
console.log("accessToken = ", accessToken);
$.ajax({
url: apiEndpoint,
type: "GET",
beforeSend: function (xhr) { xhr.setRequestHeader("Authorization", bearer) }
}).done(function (response) {
alert("SUCCESS");
console.log("response = ", JSON.stringify(response));
}).fail(function (err) {
console.error("Error Occurred");
console.log("error = ", JSON.stringify(err));
});
})
}).catch(function (error) {
console.log(error);
});
}
</script>
Screenshot of a NEW API Code
Screenshot of JWT.ms (Access Token)
New Screenshot of JWT Token
You should not set the client ID in the appsettings.json file to the client id of your api app. It should be the client id of your client app. According to the jwt analysis diagram you provided, I think it should be: 94edf294- 08ae-489f-8621-c6xxxxxxx.
My bad, Following call was missing in my startup.cs file
app.UseAuthentication();
thanks for your help guys
Specially - #Carl Zhao, #Purushothaman #Rass Masood

http 401 error when providing an access token the outlook api

I am trying to create a folder for a user, and I have been unsuccessful with api call attempts. My code is able to receive the correct access token, so I believe the be bug would be in createFolderTestFunction below.
async function redirectToDashboard() {
console.log("redirect to dashboard");
// var response = await requestTokenSilent();
var response;
if (!response || !response.status == 200) {
response = await requestTokenPopup();
}
if (response.accessToken) {
console.log(response);
createFolderTest(response.accessToken);
// location.href = hostname;
} else {
console.log("Unable to acquire token");
}
}
function createFolderTest(accessToken) {
var options = {
method: "POST",
headers: {
Authorization: accessToken,
"Content-Type": "application/json"
},
mode: "cors",
body: JSON.stringify({
displayName: "#COOLMONDAY"
})
};
var graphEndpoint = "https://outlook.office.com/api/v2.0/me/Inbox/";
fetch(graphEndpoint, options)
.then(resp => {
console.log(resp);
})
.catch(err => {
console.log(err);
});
}
A recommendation would be to get this working in Graph Explorer first. As this eliminates any issues with language being used and access token permissions.
https://developer.microsoft.com/en-us/graph/graph-explorer/preview
The Microsoft Graph endpoint is actually https://graph.microsoft.com/ , you can use the outlook url but moving forward Graph is where we invest in documentation, sdks and tooling.
As per the documentation https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=http
You should be using, you're missing 'mailfolders'
POST /me/mailFolders
You could also use our JavaScript SDK which makes mistakes like these a little easier with intellisense and strongly typed objects.
const options = {
authProvider,
};
const client = Client.init(options);
const mailFolder = {
displayName: "displayName-value"
};
let res = await client.api('/me/mailFolders')
.post(mailFolder);
https://learn.microsoft.com/en-us/graph/api/user-post-mailfolders?view=graph-rest-1.0&tabs=javascript

Get Current Users: Google Real Time API with NodeJS & gtoken: "Invalid Credentials"

I would like to use a NodeJS Server to obtain the current users on my Website from Google Analytic using the Real Time Reporting API:
So far I try to do this via an HTTP request with request and gtoken. The getToken-Part works. I get a token. But the HTTP-Request doesnt work. I get an "Invalid Credentials" Error with Code 401.
Does anybody have an idea what to do? Maybe this is the completely wrong approach to get these data.
var received_token;
var url = "https://www.googleapis.com/analytics/v3/data/realtime";
var paramsObject = { ids:"ga:123456789"};
const gtoken = new GoogleToken({
keyFile: 'pathToServiceAccountJSONKeyFile:',
scope: ['https://www.googleapis.com/auth/analytics.readonly']
});
gtoken.getToken(function(err, token) {
if (err) {
console.log(err);
return;
}
received_token = token;
console.log(token);
request({
url: url,
qs: paramsObject,
headers: {
'Authorization': received_token
}
}, function(err, response, body) {
if(err) { console.log(err); return; }
// console.log(response);
console.log(body);
});
I found the error :-)
In the Authorization Header "Bearer" was missing, now it works like charm and I receive data from the Google Real Time API.
headers: {
'Authorization': "Bearer " +received_token
}

Axios POST request fails with error status code 500: Internal Server error

I'm trying to send a POST request locally with a username and password in the body through Axios.
I'm deploying a Flask app on http://127.0.0.1:5000/login, which handles the /login route. The POST request fails with the following error
POST http://127.0.0.1:5000/login 500 (INTERNAL SERVER ERROR)
Error: Request failed with status code 500
at createError (createError.js:16)
at settle (settle.js:18)
at XMLHttpRequest.handleLoad (xhr.js:77)
I researched a bit and thought it might be a problem with CORS, but this doesn't seem to be the case because I tried an Axios GET request and it worked fine (response logged properly). Here's part of my code
axios.get("http://127.0.0.1:5000").then(function(response) {
console.log(response);
}).catch(function(error) {
console.log(error);
})
axios.post("http://127.0.0.1:5000/login", {
username: this.state.username,
password: this.state.password
}).then(function(response) {
console.log(response);
}).catch(function(error) {
console.log(error);
})
Looking at Chrome DevTools, I can see that the POST request payload is properly populated. I then tried printing out the keys server-side in the Flask app using the following code, but I got nothing, empty. (which was expected since the POST request failed)
dict = request.form
for key in dict:
print('form key '+dict[key])
HOWEVER using Postman with the corresponding keys and values works properly and returns a response and prints out the keys (see above). Where is the failure coming from? Why would the POST request fail when a GET seems to work just fine?
Feb 2021. Wasted 2 hours on this. Not much help on this famous library on internet.
Solution:
In the catch block, the error which will always be 500 internal server error
so, use error.response.data instead of error.
Code:
try {
let result = await axios.post( // any call like get
"http://localhost:3001/user", // your URL
{ // data if post, put
some: "data",
}
);
console.log(result.response.data);
} catch (error) {
console.error(error.response.data); // NOTE - use "error.response.data` (not "error")
}
Update:
I ended up writing a common function for handing error:
File: common.app.js
export const errorUtils = {
getError: (error) => {
let e = error;
if (error.response) {
e = error.response.data; // data, status, headers
if (error.response.data && error.response.data.error) {
e = error.response.data.error; // my app specific keys override
}
} else if (error.message) {
e = error.message;
} else {
e = "Unknown error occured";
}
return e;
},
};
More info: https://github.com/axios/axios#handling-errors
So I also got stuck in the same problem and the solution that I found was something like this :
let data = JSON.stringify({
username: this.state.username,
password: password
});
const response = axios.post(url,data,{headers:{"Content-Type" : "application/json"}});
This solution worked for me.
Apparently Axios didn't take kindly to the raw JSON object
{username: this.state.username, password: password}
but passing the data into a FormData object seemed to work just fine!
After working 2 hours, I realized I made a mistake about the body and data. So, in the axios make sure you pass the data like this.
async function loadToken(){
try{
response = await axios({
url: ``,
headers: {
'Authorization': '',
'Content-Type': '',
},
data: '',
method: 'POST'
});
let data = response.data;
return {
tokenInfo:data,
timestamp:new Date().getTime()
}
} catch(err) {
console.log("err->", err.response.data)
return res.status(500).send({ret_code: ReturnCodes.SOMETHING_WENT_WRONG});
}
}
My previous code pass the data like this, which is wrong
async function refreshToken(){
try{
let headers = {
authorization: '',
'Content-Type': ''
}
let url = ``
let body = {
grant_type: '',
refresh_token: global.tokenInfo.refresh_token
}
data = await axios.post(url, body, {headers});
let data = response.data
console.log(data)
return {
tokenInfo:data,
timestamp:new Date().getTime()
}
} catch(err) {
console.log("err->", err.response)
return res.status(500).send({ret_code: ReturnCodes.SOMETHING_WENT_WRONG});
}
}
Simply try my first code, hope that solves your issue.
Most of the time it happens because of using wrong content type header.
Open postman and see "Body" tab. There you can find the content type of your post data. It's also accessible from "Headers" tab. There should be a Content-Type header. The correct format of data you send through a POST request depends on Content-Type header. for example, json content type requires a json (javascript object) data or form-data content type requires a FormData.
To set a header in axios, change the code like this:
axios.post("http://127.0.0.1:5000/login", {
username: this.state.username,
password: this.state.password
}, {
headers: {'Content-Type': 'application/json'}
}).then(function(response) {
console.log(response);
}).catch(function(error) {
console.log(error);
})
I had similar error i had the JSON capital and it should have been lowercase

Angular 2 - Sequential HTTP requests

I am trying to implement two sequential HTTP requests in my Angular 2 App.
When the user click the login button I want to save in LocalStorage the refresh and access tokens and then execute one more HTTP request in order to get User info. I saw this post but it doesn't work in my case.
Angular 2 - Chaining http requests
This was my initial implementation for login and getMe service:
How can I merge the requests bellow?
login.component.ts
this.authenticationService.login(this.user.email, this.user.password)
.subscribe(
data => {
// this.alertService.success('Registration successful', false);
this.router.navigate([this.returnUrl]);
},
error => {
console.log(error);
this.alertService.error(error);
this.loading = false;
});
authentication.service.ts
login(email: string, password: string) {
let headers = new Headers({
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({ headers: headers });
return this.http.post(this.config.apiUrl + this.authenticationUrl + '/login',
JSON.stringify({ email: email, password: password }), options)
.map((response: Response) => {
let tokens = response.json();
if (tokens && tokens.token && tokens.refreshToken) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('tokens', JSON.stringify(tokens));
}
});
}
user.service.ts
getMe() {
console.log('getMe');
return this.http.get(this.config.apiUrl + this.usersUrl + '/me', this.jwt()).map((response: Response) => {
let user = response.json();
if (user)
localStorage.setItem('currentUser', JSON.stringify(user));
});
}
You are returning an Observable on your login method, so in your data subscription just call getMe() from your user service.
this.authenticationService.login(this.user.email, this.user.password)
.subscribe(
data => {
// Check to make sure data is what you expected, then call getMe()
this.userService.getMe();
this.router.navigate([this.returnUrl]);
},
error => {
console.log(error);
this.alertService.error(error);
this.loading = false;
});
This is a fairly blunt and un-exciting way to approach that problem. As mentioned, mergeMap could serve you well here.
Check out this quick example. I recommend playing around with the Observable data to get a feel for what is happening. The Observable.of are basically acting like fake network request in this case.
http://jsbin.com/pohisuliqo/edit?js,console

Categories