Outlook Add-in REST call error - outlook-addin

I'm trying to mark (flag) a message using the Outlook rest API, but I keep getting error messages. I've tried with different rest URLs but it doesn't help - the errors just varies.
Important values in the manifest for allowing this I believe are:
<Requirements>
<Sets>
<Set Name="Mailbox" MinVersion="1.1" />
</Sets>
</Requirements>
...
<Permissions>ReadWriteItem</Permissions>
<Rule xsi:type="RuleCollection" Mode="Or">
<Rule xsi:type="ItemIs" ItemType="Message" FormType="Read" />
</Rule>
...
<VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides" xsi:type="VersionOverridesV1_0">
<Requirements>
<bt:Sets DefaultMinVersion="1.3">
<bt:Set Name="Mailbox" />
</bt:Sets>
</Requirements>
Here is the part I'm trying to do that causes error:
Office.context.mailbox.getCallbackTokenAsync({ isRest: true }, function (result)
{
if (result.status === "succeeded")
{
var accessToken = result.value;
var itemId = getItemRestId();
var restUrl = Office.context.mailbox.restUrl + "/api/v2.0/messages/" + itemId;
var request = {
url: restUrl,
type: "PATCH",
dataType: 'json',
data: { "Flag": { "FlagStatus": "Flagged" } },
headers: {
"Authorization": "Bearer " + accessToken,
"Conntent-Type": "application/json"
}
};
$.ajax(request)
.done(function (item)
{
// dome something
})
.fail(function (error)
{
// handle error
});
}
else
{
// handle error
}
});
function getItemRestId()
{
if (Office.context.mailbox.diagnostics.hostName === 'OutlookIOS')
{
return Office.context.mailbox.item.itemId;
}
else
{
return Office.context.mailbox.convertToRestId(
Office.context.mailbox.item.itemId,
Office.MailboxEnums.RestVersion.Beta
);
}
}
This code above will result in the error:
{"readyState":4,"responseText":"","status":404,"statusText":"Not Found"}
If I try to JSON.stringify() the data attribute of the request I get:
{"readyState":4,"responseText":"","status":404,"statusText":"Not Found"}
If I change the rest URL to (seen in older samples):
'https://outlook.office.com/api/beta/me/messages/'+ itemId;
And the headers attribute of the request to (seen in older samples):
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
}
Then I get the following error instead:
{
"readyState": 4,
"responseText": "{\"error\":{\"code\":\"ErrorAccessDenied\",\"message\":\"The api you are trying to access does not support item scoped OAuth.\"}}",
"responseJSON": {
"error": {
"code": "ErrorAccessDenied",
"message": "The api you are trying to access does not support item scoped OAuth."
}
},
"status": 403,
"statusText": "Forbidden"
}
Can anyone see what I'm doing wrong or missing here?
I'm debugging in Outlook 2016 and the account is Office 365.
UPDATE: Fiddler outputs
Here is the request my own sample sends (results in 403 Forbidden)
Exact error: {"error":{"code":"ErrorAccessDenied","message":"The api you are trying to access does not support item scoped OAuth."}}
PATCH https://outlook.office.com/api/beta/me/messages/AAMkAGNmMDllMTVhLTI3ZDctNDYxZS05ZWM5LTA3ZWQzMzYyNDBiOABGAAAAAAD6OQOAoKyKT6R02yYFe0bIBwD5fUzv7OgQQYAILztCFSSWAALg591rAAC382lxTQ2HQpUKZsAGTeWVAARPu37CAAA= HTTP/1.1
Content-Type: application/json
Accept: application/json, text/javascript, */*; q=0.01
Authorization: Bearer <long token code removed...>
Referer: https://localhost:44394/MessageRead.html?_host_Info=Outlook$Win32$16.02$da-DK
Accept-Language: da-DK
Origin: https://localhost:44394
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: outlook.office.com
Content-Length: 33
Connection: Keep-Alive
Cache-Control: no-cache
{"Flag":{"FlagStatus":"Flagged"}}
Here is the request the demo project sends (results in 200 OK)
PATCH https://outlook.office.com/api/beta/me/messages/AAMkAGNmMDllMTVhLTI3ZDctNDYxZS05ZWM5LTA3ZWQzMzYyNDBiOABGAAAAAAD6OQOAoKyKT6R02yYFe0bIBwD5fUzv7OgQQYAILztCFSSWAALg591rAAC382lxTQ2HQpUKZsAGTeWVAARPu37CAAA= HTTP/1.1
Content-Type: application/json
Accept: application/json, text/javascript, */*; q=0.01
Authorization: Bearer <long token code removed...>
Referer: https://<company.domain.com>:1443/outlookaddindemo/RestCaller/RestCaller.html?_host_Info=Outlook$Win32$16.02$da-DK
Accept-Language: da-DK
Origin: https://<company.domain.com>:1443
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: outlook.office.com
Content-Length: 47
Connection: Keep-Alive
Cache-Control: no-cache
{
"Flag": {
"FlagStatus": "Flagged"
}
}
The only difference I can see is that the 2nd request payload seems formatted for reading while data wise being identical to the previous one.
I can't seem to find the problem here - I even made sure that both projects use the same version of JQuery.

If you need write access to the item via REST, you need to specify ReadWriteMailbox in the Permissions element in your manifest. Despite it's name, ReadWriteItem doesn't give you a token with the proper scope. Any permission level other than ReadWriteMailbox gives an item-scoped token, and as the error says, the operation you're trying to do doesn't support item-scoped OAuth.
See https://learn.microsoft.com/en-us/outlook/add-ins/use-rest-api for details, but here's the relevant bit:
Add-in permissions and token scope
It is important to consider what level of access your add-in will need via the REST APIs. In most cases, the token returned by getCallbackTokenAsync will provide read-only access to the current item only. This is true even if your add-in specifies the ReadWriteItem permission level in its manifest.
If your add-in will require write access to the current item or other items in the user's mailbox, your add-in must specify the ReadWriteMailbox permission level in its manifest. In this case, the token returned will contain read/write access to the user's messages, events, and contacts.

Related

Axios Post return 405 Method Not Allowed on Vue.js

I'm trying to made a POST request on a NET CORE 5 service (hosted on IIS 10) from a Vue.js app with axios.
When I test the service with POSTMAN it's working perfectly but with Axios I'm always getting a 405 from the server.
Analyzing the requests with fiddler are looking very different. IN the axios request the content-type header is missing and the method is OPTIONS instead of POST.
This is the POSTMAN request:
POST https://localhost/apiluxor/api/SignIn HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.28.4
Accept: */*
Postman-Token: acfed43c-731b-437b-a88a-e640e8216032
Host: localhost
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 55
{
"username":"user",
"password":"testpw"
}
And this is the axios request:
OPTIONS https://localhost/apiluxor/api/SignIn HTTP/1.1
Host: localhost
Connection: keep-alive
Accept: */*
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://172.16.1.110:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Sec-Fetch-Dest: empty
Referer: http://172.16.1.110:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7
In my Vue.js module I've tried to force the settings of the 'content-type' in the main config and in the post request with no results.
import { App } from "vue";
import axios from "axios";
import VueAxios from "vue-axios";
import JwtService from "#/core/services/JwtService";
import { AxiosResponse, AxiosRequestConfig } from "axios";
class ApiService {
public static vueInstance: App;
public static init(app: App<Element>) {
ApiService.vueInstance = app;
ApiService.vueInstance.use(VueAxios, axios);
ApiService.vueInstance.axios.defaults.baseURL =
"https://localhost/apiluxor/api";
ApiService.vueInstance.axios.defaults.headers.post["Content-Type"] =
"application/json";
}
public static setHeader(): void {
ApiService.vueInstance.axios.defaults.headers.common[
"Authorization"
] = `Bearer ${JwtService.getToken()}`;
}
public static post(
resource: string,
params: AxiosRequestConfig
): Promise<AxiosResponse> {
return ApiService.vueInstance.axios.post(resource, params, {
headers: { "content-type": "application/json" },
});
}
export default ApiService;
I'm very new to Vue.js so maybe I'm missing something.
The problem is that axios made a CORS request before the POST, and the NET Core API should be configured to accept CORS request.
I've found an article (here) that saying the problem for this cases is that IIS does not accept CORS request and the CORS module should be installed .
I've tried this change but the result was the I was receving an HTTP/1.1 204 No Content instead of a 403.
In my case the problem was the API service itself.
The CORS should be enabled in the API Service.
In NET CORE 5 for a basic configuration it's enough to add the CORS services in Startup
services.AddCors();
and configure it
app.UseCors(builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
With these changes the service works perfectly for any vue.js requests coming from axios.
To send the data as JSON in the body of the post request, just add a parameter "data" to the axios options. Axios will automatically set the content-tpe to the proper value for you.
// Simple example
axios.post(myUrl, {
data: {
"username":"user",
"password":"testpw"
}
});
This should have the expected outcome :)

Axios post request failing due to CORS but the same request using ajax is getting no issues

I am getting following error wile doing axios post request.
But when I use ajax request there is no issue:
request has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Ajax Request:
Axios request:
let payload = {
type: ["a", "b"],
category: ["a", "b"],
category: ["a", "b"],
accountNumber: "123"
};
var apiURL = this.$apiBaseURL + "/Product/ProductDetails";
$.ajax({
url: apiURL,
type: "POST",
data: { payload },
xhrFields: {
withCredentials: true
},
success: function (result) {
console.log(JSON.stringify(result));
}
});
this.$http.post(apiURL,payload,{withCredentials: true})
**UPDATE 1 **
I am still facing the same issue. Here I will share the request header in both ajax and axios request
AJAX Working code and request header :
{
var apiURL = this.$apiBaseURL + "/Request/MediaUpload";
$.ajax({
method: 'post',
processData: false,
contentType: false,
cache: false,
data: fileformData,
enctype: 'multipart/form-data',
url: apiURL,
xhrFields: {
withCredentials: true
}
});
}
Request header:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 7610
Content-Type: multipart/form-data; boundary=----
WebKitFormBoundaryAjc8HwVPaRtQ5Iby
Host: localhost:62148
Origin: http://localhost:8989
Referer: http://localhost:8989/
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
AXIOS failing code and header
var apiURL = this.$apiBaseURL + "/Request/MediaUpload";
var self=this;
let config={
headers:{
'Content-Type': 'multipart/form-data'
}
}
this.$http.post(apiURL, { withCredentials: true },fileformData,
config)
Request Headers:
Provisional headers are shown
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Referer: http://localhost:8989/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Here is my web api config where I have enabled cors
string origin = "http://localhost:8989";
EnableCorsAttribute cors = new EnableCorsAttribute(origin, "*", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
**UPDATE 2 **
The CORS configuration at server side is done correctly thats why I am able to make the call successfully via AJAX.
Is this a known AXIOS issue which occurs only when we enable the windows authentication?
This issue arises because jQuery sends the request as application/x-www-form-urlencoded by default and does not send preflight request whereas axios sends as application/json and sends a preflight request i.e. OPTIONS before sending actual request.
One way it could work is by setting Content-type header to 'Content-Type': 'application/x-www-form-urlencoded' in axios.
You will have to change the settings on server side to get around this issue.
Add this in your web.config file:
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
Using default ajax won't send any OPTIONS request, but using axios does. This will enable accepting OPTIONS requests.

Get shopify analytics token programmatically

How do you get the token needed to make a request to the Shopify analytics API?
For example:
POST https://analytics.shopify.com/queries?beta=true
REQUEST BODY:
token: HOW_DO_I_GET_THIS?
q[]: SHOW+total_visitors+AS+%22total_visitors...
source: shopify-reports
I've tried using OAuth, but it seems to be a different token entirely.
You have to request Shopify Core GraphQL to get analyticsToken.
POST /admin/internal/web/graphql/core HTTP/1.1
Host: [[STORE_NAME]].myshopify.com
Accept: application/json
Accept-Language: en-CA,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
content-type: application/json
X-CSRF-Token: [[CSRF_TOKEN]]
Origin: https://[[STORE_NAME]].myshopify.com
Connection: close
Cookie: [[COOKIE]]
{ "query": "{ shop { analyticsToken } }" }
Since the request requires CSRF Token & Cookie, it's not programmatic at all.
For additional information please check the Examples for Testing in the Shopify Admin page.

Office add-in: XMLHttpRequest cannot load XXX due to access control checks

I'm building an Outlook add-in with jQuery and the Office JS API. I have a local server going while developing, and I'm trying to submit a POST request to an endpoint on my site's main server. Every time I try to submit the request, I get the following three errors:
Origin https://localhost:3000 is not allowed by Access-Control-Allow-Origin
XMLHttpRequest cannot load https://myurl.com/my_endpoint due to access control checks
Failed to load resource: Origin https://localhost:3000 is not allowed by Access-Control-Allow-Origin
What I've done so far:
Found this related thread: HTTP fetch from within Outlook add-ins
The only answer says to do three things:
Make the request with XMLHttpRequest. Yup, did that:
function submitForm(var1, var2) {
var http = new XMLHttpRequest();
var params = 'var1=' + encodeURIComponent(var1) + '&var2=' + encodeURIComponent(var2);
http.open("POST", 'https://myurl.com/my_endpoint', true);
http.setRequestHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
http.setRequestHeader('Access-Control-Allow-Credentials', true);
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.onreadystatechange = function() {
console.log("response:", http.responseText);
console.log("status:", http.status);
};
http.send(params);
}
Add the service URL into the manifest's AppDomains list. Yup, did that, too. This is from my manifest.xml:
<AppDomains>
<AppDomain>https://myurl.com</AppDomain>
<AppDomain>https://myurl.com/my_endpoint</AppDomain>
<AppDomain>https://localhost:3000</AppDomain>
</AppDomains>
Use only services which are under SSL connection. Yup, the myurl.com server is only accessible via SSL.
I also found this documentation (https://learn.microsoft.com/en-us/office/dev/add-ins/develop/addressing-same-origin-policy-limitations) that recommends to solve this with cross-origin-resource-sharing (CORS), and points to this link: https://www.html5rocks.com/en/tutorials/file/xhr2/#toc-cors
So, I checked the server set-up for https://myurl.com and I am in fact allowing requests from any origin. UPDATE 1: as an example, here's what the output of a successful network request to https://myurl.com/my_endpoint looks like (notice the Accept: */* header):
Request URL: https://myurl.com/my_endpoint
Request Method: POST
Status Code: 200 OK
Referrer Policy: no-referrer-when-downgrade
Cache-Control: no-cache, no-store, must-revalidate, public, max-age=0
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Expires: 0
Pragma: no-cache
Server: nginx/1.10.3 (Ubuntu)
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 52
Content-type: application/x-www-form-urlencoded
Host: myurl.com
Origin: chrome-extension://focmnenmjhckllnenffcchbjdfpkbpie
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
var1: var1
var2: var2
Plus, another thing leading me to believe the problem isn't with https://myurl.com is: when I open my network tab in my debugger, I can see that my request never reaches https://myurl.com. I'm also not seeing the request pings in my https://myurl.com server logs. This is the output of my network request when I try to ping https://myurl.com from the Outlook add-in:
Summary
URL: https://myurl.com/my_endpoint
Status: —
Source: —
Request
Access-Control-Allow-Origin: https://localhost:3000
Access-Control-Allow-Credentials: true
Content-Type: application/x-www-form-urlencoded
Origin: https://localhost:3000
Accept: */*
Referer: https://localhost:3000/index.html?_host_Info=Outlook$Mac$16.02$en-US
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14) AppleWebKit/605.1.15 (KHTML, like Gecko)
Response
No response headers
Request Data
MIME Type: application/x-www-form-urlencoded
var1: var1
var2: var2
Any recommendations for what else I need to change to enable making a POST request to myurl.com? Thanks in advance to the kind soul that helps me figure this out.
UPDATE 2: For what it's worth, I haven't done any configs to my node server beyond what came out-of-the box when I ran npm install -g generator-office. E.g. I haven't touched these two files:
.babelrc
{
"presets": [
"env"
]
}
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
polyfill: 'babel-polyfill',
app: './src/index.js',
'function-file': './function-file/function-file.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader'
},
{
test: /\.html$/,
exclude: /node_modules/,
use: 'html-loader'
},
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'file-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
chunks: ['polyfill', 'app']
}),
new HtmlWebpackPlugin({
template: './function-file/function-file.html',
filename: 'function-file/function-file.html',
chunks: ['function-file']
})
]
};
Failed to load resource: Origin https://localhost:3000 is not allowed by Access-Control-Allow-Origin
The server responds to your pre-flight request (usually OPTIONS) and does not allow to get a response, that's because your origin localhost:3000 is not allowed on server side.
You need to respond to OPTIONS on server with 204 status code and a header like:
Access-Control-Allow-Origin 'localhost';

ember-simple-auth oauth2 authorizer issue

I am trying to set up authorization on an Ember App running on a Node.js server.
I am using the oauth2 Authenticator, which is requesting a token from the server. This is working fine. I am able to provide the app with a token, which it saves in the local-storage.
However, when I make subsequent requests, the authorizer is not adding the token to the header, I have initialized the authorizer using the method described in the documentation (http://ember-simple-auth.simplabs.com/ember-simple-auth-oauth2-api-docs.html):
Ember.Application.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.setup(container, application, {
authorizerFactory: 'authorizer:oauth2-bearer'
});
}
});
var App = Ember.Application.create();
And I have added an init method to the Authorizer, to log a message to the server when it is initialized, so I know that it is being loaded. The only thing is, the authorize method of the authorizer is never called.
It feels like I am missing a fundamental concept of the library.
I have a users route which I have protected using the AuthenticatedRouteMixin like so:
App.UsersRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, {
model: function() {
return this.get('store').find('user');
}
});
Which is fetching the data, fine, and redirects to /login if no token is in the session, but the request headers do not include the token:
GET /users HTTP/1.1
Host: *****
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Accept: application/json, text/javascript, */*; q=0.01
Origin: *****
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36
Referer: *****
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Any help you could give me would be greatly appreciated.
Is your REST API served on a different origin than the app is loaded from maybe? Ember.SimpleAuth does not authorizer cross origin requests by default (see here: https://github.com/simplabs/ember-simple-auth#cross-origin-authorization)