I am trying to access Adyen test API that requires basic authentication credentials. https://docs.adyen.com/developers/ecommerce-integration
My credentials work when accessing the API page through browser.
But I get an 401 Unauthorized response when trying to access the API with XMLHttpRequest POST request.
Javascript Code
var url = "https://pal-test.adyen.com/pal/servlet/Payment/v25/authorise";
var username = "ws#Company.CompanyName";
var password = "J}5fJ6+?e6&lh/Zb0>r5y2W5t";
var base64Credentials = btoa(username+":"+password);
var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, true);
xhttp.setRequestHeader("content-type", "application/json");
xhttp.setRequestHeader("Authorization", "Basic " + base64Credentials);
var requestParams = XXXXXXXX;
xhttp.send(requestParams);
Result
That screenshot shows “Request Method: OPTIONS”, which indicates the details displayed are for a CORS preflight OPTIONS request automatically made by your browser—not for your POST.
Your browser doesn’t (and can’t) send the Authorization header when it makes that OPTIONS request, and that causes the preflight to fail, so the browser never moves on to trying your POST.
As long as https://pal-test.adyen.com/pal/servlet/Payment/v25/authorise requires authentication for OPTIONS requests, there’s no way you can make a successful POST to it.
The reason is because what’s happening here is this:
Your code tells your browser it wants to send a request with the Authorization header.
Your browser says, OK, requests with the Authorization header require me to do a CORS preflight OPTIONS to make sure the server allows requests with that header.
Your browser sends the OPTIONS request to the server without the Authorization header—because the whole purpose of the OPTIONS check is to see if it’s OK to send that.
That server sees the OPTIONS request but instead of responding to it in a way that indicates it allows Authorization in requests, it rejects it with a 401 since it lacks that header.
Your browser expects a 200 or 204 response for the CORS preflight but instead gets that 401 response. So your browser stops right there and never tries the POST request from your code.
The PAL is a Payment Authorisation API. You never want to call it from a browser. You only want to expose your username and password to send in payments in your backend code.
In Client-side encryption, the encryption is done in the browser. You then send the encrypted data to your own server. On your server you then create a payment authorization request (of which the encrypted data is one of the elements, along side payment amount, etc).
If you would be able to manage to make this run from your browser, your end solution will allow your shoppers to change amounts, currency's, payment meta data etc from the JavaScript layer. This should never be the case.
The authorization is for that reason part of the "Server side" integration part of documentation: https://docs.adyen.com/developers/ecommerce-integration?ecommerce=ecommerce-integration#serverside
Depending on your server side landscape the CURL implementation in your favorite language differs, but most of the time are easy to find.
Kind regards,
Arnoud
Related
I am trying to write an RSS feed consumer in JavaScript, but unfortunately most feeds do not seem to explicitly set an access-control-allow-origin header on their responses (Even though it is my understanding that the data is for public consumption / scraping).
My question is: Is there a way to load data like this in javascript (Aside from using a server side proxy or turning the project into a browser plugin) given that:
The requests are simple get requests. (So no OPTIONS request would normally be sent even if the access-control-allow-origin header was present)
Cookies / Authentication is not important as the feeds are public. (So withCredentials would be false if it were an XMLHttpRequest)
e.g. Something like:
fetch('http://rss.slashdot.org/Slashdot/slashdotMain', {
crossOrigin: false,
xhrFields: {
withCredentials: false
}
})).then(function(response){
console.log('Got a response!', response);
});
Update
The second part of my question is: Why is this not allowed?
For example: Suppose I for whatever reason navigate to a domain malicious-website.com. It sends a simple Ajax GET request including withCredentials: false to my-bank.com. my-bank.com processes this request, but then the browser blocks the response.
How does blocking the response to this get request improve security?
It does not matter if I am logged in to my-bank.com in a different tab, as no cookies or authorization header is send as per the withCredentials: false directive. The classic XSRF scenario has already been prevented - this request is exactly as if any other user on the internet [including a malicious one] had loaded this resource.
If there was an authentication token in the URL (Such as JWT) then malicious-website.com already has this and can potentially store it for their own use later - blocking this particular response does not change this.
It does not protect the data on my-bank.com since it does not block the request, just the response - if they have a REST style resource that performs an update in response to that GET then I am going to have a bad time. i.e.: The classic CSRF is not prevented unless my-bank.com requires a non simple request on updates (A POST or request with headers so that an OPTIONS request is sent first)
So again, what good does blocking just the response actually do here?
I guess the answer I was looking for was along the lines of: "If simple withCredentials: false requests were also allowed to subvert the same origin policy then a bad actor could do X".
Any ideas on what that X is?
In our application we validate user name/password. Once validation is done, credentials are encoded using base64 and then needs to be set at request header for subsequent rest calls.
Need to set below in request header.
Authorization:Basic AQNLzR69OFTNJE8X
In the response setting as below from the java code,
javax.ws.rs.core.Response.status(200).entity("").header("Authorization:","Basic AQNLzR69OFTNJE8X").build();
And in the javascript tried setting as below,
sessionStorage.setItem('Authorization:', 'Basic AQNLzR69OFTNJE8X');
But in the subsequent rest service calls in the same session can see the header request is not set with authorization. Request to provide some pointers on setting the Authorization in javascript, so that it is retained for the entire session.
I think you misunderstand how authentication works (or should work).
You are supposed to send the Authorization header only once during the authentication. If the authentication is successful, the server sends you back a session cookie and your session is marked as authenticated (server-side).
You never send back the content of the header, and you don't have to send it each request.
1) The Authorization header is not automatically added. But the cookie will be automatically sent.
2) You should not send the credential and return them: for security purposes, you want to transport them the less you can.
3) You don't want to store the credential in the sessionStorage, I don't know if this is a secure place for a password (i doubt it), but here, the password is only encoded in B64, and it's reversable. So it's as well as cleartext (which is bad for a password).
Hopes this helps!
I'm testing a HTTP Server that I have developed myself with C++ and Boost libraries. More specifically, I'm testing an endpoint where a JSON is received by PUT.
To test the RESTFul webservice I use Curl with the following command:
curl -H "Content-Type: application/json" -H "Content-Length: 34" -H "Connection: close" -X PUT --data "#response_json" http://localhost:8080/answer
where response_json is a file with the json to be sent. This works fine, the server receives the request as a PUT and do what is supposed to do.
However, when I test the webservice from AJAX with this:
function sendPut2() {
var http = new XMLHttpRequest();
var url = 'http://localhost:8080/answer';
var data = JSON.stringify({"question": "a", "answer": "b"});
http.open("PUT", url, true);
http.setRequestHeader("Content-type", "application/json");
http.setRequestHeader("Content-Length", data.length);
http.setRequestHeader("Connection", "close");
http.onreadystatechange = function() {
if(http.readyState == 4 && http.status == 200) {
alert(http.responseText);
}
}
http.send(data);
}
the server receives it as OPTIONS and does not work. Moreover, in Firebug console I can see: "NetworkError: 404 Not Found - http://localhost:8080/answer".
I have tried with Firefox and Chrome. What is wrong in my javascript code?
This is the Firebug with the request from Javascript:
The browser has a same origin policy for security reasons. When you request a Ajax PUT in the browser from a different origin than the current web page was loaded from, then the request is subject to that same origin policy. The destination site can choose to support CORS (cross origin resource sharing) which is a specific scheme that the browser implements that lets it ask the target site if a specific cross origin request is OK or not.
Using the OPTIONS request before the PUT request is one such part of the CORS scheme. If the browser detects certain conditions on the original cross origin request, then it will first issue an OPTIONS request and, if it gets the right response from that, then it will issue the target request (a PUT in your case). Things that can trigger the browser to use the OPTIONS request are things like custom headers, certain types of authorization required, certain content types, certain types of requests, etc...
CURL, on the other hand, enforces no such same origin security (that is someting a browser invented for its own web page security model) so it just sends the PUT request right through without requiring the correct answer from the OPTIONS request first.
FYI, if the Javascript in the browser that is making the Ajax request is requesting from the same origin as the loaded web page that contains the Javascript, then it should not trigger the OPTIONS request because it would be a same origin request rather than a cross origin request. If you have a local server, make sure that the web page is being loaded from the local server (same hostname and port number) too, not from the file system and not one using an IP address and the other using localhost or something like that. As far as the browser is concerned, the hostname has to physically be the same, not just the same IP address.
Here's info from MDN on what requests are "preflighted" with the OPTIONS request:
Preflighted requests
Unlike simple requests (discussed above), "preflighted" requests first
send an HTTP request by the OPTIONS method to the resource on the
other domain, in order to determine whether the actual request is safe
to send. Cross-site requests are preflighted like this since they may
have implications to user data. In particular, a request is
preflighted if:
It uses methods other than GET, HEAD or POST. Also, if POST is used
to send request data with a Content-Type other than
application/x-www-form-urlencoded, multipart/form-data, or text/plain,
e.g. if the POST request sends an XML payload to the server using
application/xml or text/xml, then the request is preflighted. It sets
custom headers in the request (e.g. the request uses a header such as
X-PINGOTHER)
FYI, here's a pretty good explanation of the various aspects of CORS. Because your request is a PUT, it will be in the "not-so-simple request" part of that article.
I am attempting to use XMLHTTPRequest to get an update on twitter.
var XMLReq = new XMLHttpRequest();
XMLReq.open("GET", "http://twitter.com/account/verify_credentials.json", false, "TestAct", "password");
XMLReq.send(null);
However, using my sniffer I cannot see any authorization headers being passed through. Hence, I get a 401 error response from Twitter.
The account and password are correctly entered.
Anyone attempt this? Can anyone give me some pointers? Thank you.
You just need to add a Authorization header, a user name and password in a base64 encoded string as follows.
XMLReq.setRequestHeader("Authorization", "Basic " + btoa("username:password"));
In cross-origin requests, you have to explicitly set the withCredentials flag if you want user credentials to be sent.
See http://www.w3.org/TR/XMLHttpRequest/#the-withcredentials-attribute (where user credentials includes HTTP authentication)
Due to the Origin policy, you cannot make a XMLHttpRequest from your domain to another domain. E.g. you cannot use http://twitter.com/... URLs unless your script was loaded from twitter.com. If your script is loaded from http://localhost/, the AJAX request also need to go to localhost.
I am currently in the process of implementing a server-side OAuth2 flow in order to authorize my application.
The JS application will be displaying YouTube Analytics data on behalf of a registered CMS account to an end user (who own's a channel partnered with the CMS account). As a result of this, the authorization stage needs to be completely hidden from the user. I am attempting to authorize once, then use the 'permanent' authorization code to retrieve access tokens as and when they're needed.
I am able to successfully authorize, and retrieve an access code. The problem begins when i attempt to exchange the access code for a token.
The HTTP POST Request to achieve this needs to look like this...
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=8819981768.apps.googleusercontent.com&
client_secret={client_secret}&
redirect_uri=https://oauth2-login-demo.appspot.com/code&
grant_type=authorization_code
I am using this code to achieve this:
var myPOSTRequest = new XMLHttpRequest();
myPOSTRequest.open('POST', 'https://accounts.google.com/o/oauth2/token', true);
myPOSTRequest.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
myPOSTRequest.send('code=' + myAuthCode + '&redirect_uri=http%3A%2F%2Flocalhost%2FCMSAuth3.html&client_id=626544306690-kn5m3vu0dcgb17au6m6pmr4giluf1cle.apps.googleusercontent.com&scope=&client_secret={my_client_secret}&grant_type=authorization_code');
I can successfully get a 200 OK response to this Request however no access token is returned, and myPOSTRequest.responseText returns an empty string.
I have played with Google's OAuth Playground - and can successfully get a token using my own credentials.
Am i missing something here?
You cannot do this, because there is the same origin policy. This is a security concept of modern browsers, which prevents javascript to get responses from another origin, than your site. This is an important concept, because it gives you the ability, to protect you against CSRF. So don't use the code authorization flow, use instead the token authorization flow.
Try and build up the full URL. Then dump it in a webbrowser. If its corect you will get the json back. You have the corect format.
https://accounts.google.com/o/oauth2/token?code=<myAuthCode>&redirect_uri=<FromGoogleAPIS>&client_id=<clientID>&client_secret={my_client_secret}&grant_type=authorization_code
Other things to check:
Make sure that you are using the same redirect_uri that is set up in google apis.
How are you getting the Authcode back? If you are riping it from the title of the page i have had issues with it not returning the full authcode in the title try checking the body of the page. This doesnt happen all the time. I just ocationally.