Should the browser cache HTTP Auth credentials sent via Ajax? - javascript

I am building a static website with restricted access. I have configured the server to protect a subfolder (say www.example.com/restricted) with HTTP Basic Auth.
Now, I do not want to use the standard browser popup for the login, since it's ugly and does not remember the password across sessions. And it's ugly, did I mention that? =)
So, I am showing a html form, and try to do the authentication via JavaScript (here, with help of jQuery, but the "raw" version showed the same behaviour). The function I am calling looks like
try_login = function(username, password){
$.ajax({
url: '/restricted/login.token',
method: 'GET',
beforeSend: function(request) {
request.setRequestHeader(
'Authorization',
'Basic ' + Base64.encode(username + ':' + password));
},
success: function () { window.location='/restricted/'; },
error: function () { $('#error_message').text("Wrong password."); }});
}
Now, the authentication works for the ajax request. "login.token" is served correctly with status code 200 if I use the correct username and password, and I get a 401 if I don't.
But the browser (I tested Chrome 28 and IE 10) asks again for the credentials following the redirect in the success callback. It does not seem to save the credentials.
Am I doing something wrong, or is this indeed the expected behaviour? If it is, is there a way to cheat? I really think the browser popup is ugly.
Since this is a static site, any authentication scheme required active server-side code is unfortunately not usable here.

You need to send the Authorization header with each request.
Try this approach - How to use Basic Auth with jQuery and AJAX?

Related

Let's chat authentication via ajax request

I've deployed a Let's Chat application for my own server.
However, instead of using currently built, original Let's Chat web application I would like to develop my own, using its API.
And according to Let's Chat wiki:
Revoke an API Token
In the top-left dropdown menu:
Select "Auth tokens"
Click "Revoke token"
Choose "Yes". This will delete any previously generated token.
Basic Authentication
Use the API token as the username when authenticating. The password
can be set to anything, but it must not be blank (simply because most
clients require it).
So far I've generated own token and tried to send GET request to retrieve all rooms that I have in the app, but I've got an error: 401 - Unauthorized - I've tried to send this request with { data: my_token, password: my_random_password } credentials but without success. So my main question is: how exactly I can authenticate with Let's Chat API using ajax request?
I couldn't find any API url / endpoint dedicated for such task - please help.
EDIT:
I've tried also setting headers but it still doesn't work:
$.ajax({
url: CHAT_URL + 'rooms',
beforeSend: function(xhr){
xhr.setRequestHeader('username', 'NTczYzZ1111111111111111111JiMWE3MGUwYThiNzZhYjhmYjFjOWJkOTQ5ZDQ2YjhjNWUyMzkwNmMzYjhjMQ==');
xhr.setRequestHeader('password', '123qwe');
}
}).done(function(resp){
console.log('1');
console.log(resp);
}).done(function(resp){
console.log('2');
console.log(resp);
});
From that wiki page:
Use the API token as the Bearer token.
This is done by setting the header Authentication to the value bearer YOUR_TOKEN_HERE
So,
xhr.setRequestHeader('Authentication', 'bearer NTczYzZ1111111111111111111JiMWE3MGUwYThiNzZhYjhmYjFjOWJkOTQ5ZDQ2YjhjNWUyMzkwNmMzYjhjMQ==');
If you want to use basic authentication, this answers that question
How to use Basic Auth with jQuery and AJAX?

Django Rest framework replacing my currently authenticated user with AnonymousUser whenever I call it via ajax?

I'm trying to add a few interactive things the to Django admin page via a simple RESTful api and Javascript. Should be simple, but I'm facing a weird issue where every single one of my requests from javascript is returning a 403 authorization error. Note that this only applies to js. I can hit the url from a browser just fine and do all the basic CRUD stuff.
The code is very basic.
Javascript
$.ajax({
xhrFields: {withCredentials: true},
type: 'PATCH',
url: 'path/to/my/endpoint,
data: {
aParam: someValue,
'csrfmiddlewaretoken': getCookie('csrftoken')
},
success: doSomething,
error: doSomething
});
Python
class MyObjectDetail(RetrieveUpdateDestroyAPIView):
queryset = MyObject.objects.all()
serializer_class = MyObjectSerializer
authentication_classes = (SessionAuthentication,)
permission_classes = (IsAuthenticated,)
I initially suspected that the session ID wasn't being sent, and thus that was why everything was failing due to permissions. However, the session cookie is indeed sent in the ajax POST and picked up by the Django middleware. Django pulls my Admin session with no problems. However, (after a lot of debugging) I've traced the User rewrite to a dispatch method in Django Rest Framework -- specifically, a call to self.initialize_request. After that call returns, my Admin user gets swapped out for one of rest Framework's AnonymouseUsers.
I'm totally lost. I spent about 2 hours stepping through with a debugger, but still don't understand why my user is being swapped out. Has anyone faced this before? Am I just doing something wrong?
The body of the 403 error should include a friendly message explaining what the issue is, I think in this case you will find that the message is complaining about a lack of CSRF token.
Note that this only applies to js. I can hit the url from a browser just fine and do all the basic CRUD stuff.
There are only a few things different between JS and the browser, and none of them are directly caused by Django REST framework. The one that you are probably hitting is how CSRF is dealt with in AJAX requests: the CSRF token should be passed in through a header. Right now that means changing your JavaScript to
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
function addCsrfToken (xhr, settings) {
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
$.ajax({
xhrFields: {withCredentials: true},
type: 'PATCH',
url: 'path/to/my/endpoint',
data: {
aParam: someValue
},
success: doSomething,
error: doSomething,
beforeSend: addCsrfToken
});
Note that while the Django documentation recommends setting this header globally, it explicitly disallows the token from being sent in cross-origin requests. It looks like (from the withCredentials) in your code that you are accessing an API on a different domain.
Django pulls my Admin session with no problems.
This is most likely because hitting the page in your browser doesn't trigger a CSRF check, and the browsable API handles CSRF for you.
After that call returns, my Admin user gets swapped out for one of rest Framework's AnonymouseUser.
Django REST framework only deals with the anonymous user in one place, and that's when authentication fails. So while your request may be authenticated through Django, it likely isn't being authenticated through Django REST framework.

jQuery.ajax doesn't send Authorization header with OPTIONS request

It appears that jQuery doesn't send along the Authorization header when sending an OPTIONS request before a POST request (or possibly other types). The server I'm trying to reach is returning a 401 status for the OPTIONS request - how can I force jQuery to include the Authorization header, even in this initial request?
$.ajax({
type: "POST",
url: url,
data: postData,
beforeSend: function ajaxBeforeSend(jqXHR) {
jqXHR.withCredentials = true;
jqXHR.setRequestHeader("Authorization", "Basic " + btoa(encodeURIComponent(escape($username.val())) + ":" + encodeURIComponent(escape($password.val()))));
},
success: runReportUrlCallback,
error: runReportErrorCallback
});
I also tried adding username and password to the ajax options, to no avail.
It seems that the 3rd party server has been configured incorrectly without the OPTIONS request in mind.
W3 states that preflight OPTIONS request must:
Exclude user credentials.
User credentials are defined:
The term user credentials for the purposes of this specification means cookies, HTTP authentication, and client-side SSL certificates
See https://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
If the server is in your control then you simply put the OPTIONS request handler in front of your auth check.
If the server is NOT in your control, which seems to be the case here, then you moan at the server administrator explaining they've done it wrong and hope they change it.

How do I trigger the browser's Basic Authentication dialog from an AJAX call?

I'm using basic authentication to secure a set of WCF web services exposed only inside our corporate network, and I was wondering if there was a way to trigger the browser's credentials dialog to appear from an AJAX call when the web service returns with a 401 error?
Currently my AJAX call receives the 401 as a regular failed request and doesn't prompt the browser to do anything. However, if I take the same URI and copy-paste it into into the browser's URL bar, the returned 401 correctly triggers the Basic Authentication dialog.
Is there any way to get the AJAX callback to tell the browser to pop up that dialog?
Dynamically create an iframe with your url and append to document. It'll trigger authentication form. jQuery snipet to add iframe
$('<iframe src="your_url"></iframe>').appendTo('body')
A very simplified example is here:
var url = 'your_url_here';
$.ajax({
url: url,
error: function(response){
if(response.status==401){
$('<iframe src="'+url+'"></iframe>').appendTo('body');
}
},
success:function(){
//your success code here
}
});
I have faced almost the same 401 problem, except for my request was cross domain. But I hope the reason is the same. Following the instructions on developer.mozilla - Access control CORS I have finally succeeded with simple:
var xhttp=new XMLHttpRequest();
xhttp.withCredentials = true;
xhttp.open("GET", "https://my.foo.server/app/resource", true);
xhttp.send();
I think the xhttp.withCredentials is the solution. It is not header! You let browser to communicate with server through cookies. The following answer explains a lot XHR2 withCredentials - which cookies are sent?
Without xhttp.withCredentials there was always 401 (Unauthorized). But using it, the browser added the required header Authorization:Basic dGVFooFooFooFoosaWVudA== or triggered the login dialog, when credentials were not available yet.
You can't, you'll need to provide the request with the credentials.
See How to use Basic Auth with jQuery and AJAX?
You would suggest to open/display/insert a form to allow inserting username and password and than resend the AJAX Request with the given credentials. I wouldn't really on browsers credential popup.
How you set authentication header you can read here: How to use Basic Auth with jQuery and AJAX?
Yes, you can invoke it from AJAX. Just pass the request with the following header:
withCredentials: true
As found somewhere in the stack :
Receiving a 401 response is the server telling you, “you aren’t
authenticated–either not authenticated at all or authenticated
incorrectly–but please reauthenticate and try again.” To help you out,
it will always include a WWW-Authenticate header that describes how to
authenticate
Use jQuery's beforeSend callback to add an HTTP header with the authentication information
beforeSend: function (xhr) {
xhr.setRequestHeader ("Authorization", "Basic " + btoa(username + ":" + password));
},
Do you meet the conditions highlighted in this SO answer?
Also based on in this other answer you may want to check that the headers returned from your backend are the same whether you request it from the browser or from an AJAX call.
you could just trigger a redirect when you check for the 401 condition:
window.location = "https://example.com"

Ajax jquery unauthorized request

I am making an ajax request using Jquery and everything works fine except the page is protected so only logged in users can make request. I am already logged in but I think Jquery ajax doesn't send my cookies to the url. How can I solve this error so cookies, headers are also sent so the page doesn't treat it as 401 request?
Thanks in advance :)
It sounds like you're trying to request a page on a different domain from the one your site is running on. If this is the case then it's the browser that is telling you the request is unauthorized rather than the other site.
Edit:
If this is the case and you control the other website then I believe you can allow requests using a specific header (http://www.w3.org/TR/2008/WD-access-control-20080912/#access-control-allow-origin). If you don't control the other server then there is no (pure javascript) way round this problem.
You could run a proxy on a server you control to request the page and pass it on to you. However this will still have the problem that it will be requesting the page without your user's cookies.
my jquery solution:
in your ajax call(for basic authorization):
headers: {
"Authorization": "Basic " + btoa('username:password')
},
or
beforeSend: function (xhr){
xhr.setRequestHeader('Authorization', "Basic " + btoa('username:password'));
},
You still can check session as normal

Categories