XMLHttpRequest not caching basic HTTP auth in firefox - javascript

I am trying to authorize a user with basic HTTP auth with an XMLHttpRequest object in Firefox 25 in a custom extension.
The flow is like so:
hit URL that is HTTP basic auth protected, authorize using either xml.open('GET', url, true, username, password) or xml.setRequestHeaders('Authorization', 'Basic someBASE64encodedSTRING==').
be redirected to another, arbitrary, non-HTTP-basic-protected URL.
be redirected back to my protected URL from step 1. Between step 1 and 3, the basic auth is lost, and on step (3) the user is again presented with the basic HTTP auth modal.
Is there a way to force the browser (specifically firefox) to cache the authorization?
In Chrome I was able to solve this issue using chrome.webRequest.onAuthRequired listener: https://gist.github.com/Lordnibbler/2b616adfa4662ece5095

As HTTP is stateless protocol...
in Step 1) once you authenticate - web server should return you "session id" - unique string by which web server knows, that you were authenticated. You should save that "session id" to a cookie.
2) maybe step 2 returns session id - also possibility. Otherwise its irrelevant.
3) make sure that you send "session id" with your requests - should work
p.s. I just popped here randomly... I have never developed any firefox extension.

Related

Is it possible to ignore `WWW-Authenticate` header with JavaScript's `fetch` to avoid Browsers showing the basic auth dialog? [duplicate]

My web application has a login page that submits authentication credentials via an AJAX call. If the user enters the correct username and password, everything is fine, but if not, the following happens:
The web server determines that although the request included a well-formed Authorization header, the credentials in the header do not successfully authenticate.
The web server returns a 401 status code and includes one or more WWW-Authenticate headers listing the supported authentication types.
The browser detects that the response to my call on the XMLHttpRequest object is a 401 and the response includes WWW-Authenticate headers. It then pops up an authentication dialog asking, again, for the username and password.
This is all fine up until step 3. I don't want the dialog to pop up, I want want to handle the 401 response in my AJAX callback function. (For example, by displaying an error message on the login page.) I want the user to re-enter their username and password, of course, but I want them to see my friendly, reassuring login form, not the browser's ugly, default authentication dialog.
Incidentally, I have no control over the server, so having it return a custom status code (i.e., something other than a 401) is not an option.
Is there any way I can suppress the authentication dialog? In particular, can I suppress the Authentication Required dialog in Firefox 2 or later? Is there any way to suppress the Connect to [host] dialog in IE 6 and later?
Edit
Additional information from the author (Sept. 18):
I should add that the real problem with the browser's authentication dialog popping up is that it give insufficient information to the user.
The user has just entered a username and password via the form on the login page, he believes he has typed them both correctly, and he has clicked the submit button or hit enter. His expectation is that he will be taken to the next page or perhaps told that he has entered his information incorrectly and should try again. However, he is instead presented with an unexpected dialog box.
The dialog makes no acknowledgment of the fact he just did enter a username and password. It does not clearly state that there was a problem and that he should try again. Instead, the dialog box presents the user with cryptic information like "The site says: '[realm]'." Where [realm] is a short realm name that only a programmer could love.
Web broswer designers take note: no one would ask how to suppress the authentication dialog if the dialog itself were simply more user-friendly. The entire reason that I am doing a login form is that our product management team rightly considers the browsers' authentication dialogs to be awful.
I encountered the same issue here, and the backend engineer at my company implemented a behavior that is apparently considered a good practice : when a call to a URL returns a 401, if the client has set the header X-Requested-With: XMLHttpRequest, the server drops the www-authenticate header in its response.
The side effect is that the default authentication popup does not appear.
Make sure that your API call has the X-Requested-With header set to XMLHttpRequest. If so there is nothing to do except changing the server behavior according to this good practice...
The browser pops up a login prompt when both of the following conditions are met:
HTTP status is 401
WWW-Authenticate header is present in the response
If you can control the HTTP response, then you can remove the WWW-Authenticate header from the response, and the browser won't popup the login dialog.
If you can't control the response, you can setup a proxy to filter out the WWW-Authenticate header from the response.
As far as I know (feel free to correct me if I'm wrong), there is no way to prevent the login prompt once the browser receives the WWW-Authenticate header.
I don't think this is possible -- if you use the browser's HTTP client implementation, it will always pop up that dialog. Two hacks come to mind:
Maybe Flash handles this differently (I haven't tried yet), so having a flash movie make the request might help.
You can set up a 'proxie' for the service that you're accessing on your own server, and have it modify the authentication headers a bit, so that the browser doesn't recognise them.
I realize that this question and its answers are very old. But, I ended up here. Perhaps others will as well.
If you have access to the code for the web service that is returning the 401. Simply change the service to return a 403 (Forbidden) in this situation instead 401. The browser will not prompt for credentials in response to a 403. 403 is the correct code for an authenticated user that is not authorized for a specific resource. Which seems to be the situation of the OP.
From the IETF document on 403:
A server that receives valid credentials that are not adequate to
gain access ought to respond with the 403 (Forbidden) status code
In Mozilla you can achieve it with the following script when you create the XMLHttpRequest object:
xmlHttp=new XMLHttpRequest();
xmlHttp.mozBackgroundRequest = true;
xmlHttp.open("GET",URL,true,USERNAME,PASSWORD);
xmlHttp.send(null);
The 2nd line prevents the dialog box....
What server technology do you use and is there a particular product you use for authentication?
Since the browser is only doing its job, I believe you have to change things on the server side to not return a 401 status code. This could be done using custom authentication forms that simply return the form again when the authentication fails.
In Mozilla land, setting the mozBackgroundRequest parameter of XMLHttpRequest (docs) to true suppresses those dialogs and causes the requests to simply fail. However, I don't know how good cross-browser support is (including whether the the quality of the error info on those failed requests is very good across browsers.)
jan.vdbergh has the truth, if you can change the 401 on server side for another status code, the browser won't catch and paint the pop-up.
Another solution could be change the WWW-Authenticate header for another custom header. I dont't believe why the different browser can't support it, in a few versions of Firefox we can do the xhr request with mozBackgroundRequest, but in the other browsers?? here, there is an interesting link with this issue in Chromium.
I have this same issue with MVC 5 and VPN where whenever we are outside the DMZ using the VPN, we find ourselves having to answer this browser message. Using .net I simply handle the routing of the error using
<customErrors defaultRedirect="~/Error" >
<error statusCode="401" redirect="~/Index"/>
</customErrors>
thus far it has worked because the Index action under the home controller validates the user. The view in this action, if logon is unsuccessful, has login controls that I use to log the user in using using LDAP query passed into Directory Services:
DirectoryEntry entry = new DirectoryEntry("LDAP://OurDomain");
DirectorySearcher Dsearch = new DirectorySearcher(entry);
Dsearch.Filter = "(SAMAccountName=" + UserID + ")";
Dsearch.PropertiesToLoad.Add("cn");
While this has worked fine thus far, and I must let you know that I am still testing it and the above code has had no reason to run so it's subject to removal... testing currently includes trying to discover a case where the second set of code is of any more use. Again, this is a work in progress, but since it could be of some assistance or jog your brain for some ideas, I decided to add it now... I will update it with the final results once all testing is done.
I'm using Node, Express & Passport and was struggling with the same issue. I got it to work by explicitly setting the www-authenticate header to an empty string. In my case, it looked like this:
(err, req, res, next) => {
if (err) {
res._headers['www-authenticate'] = ''
return res.json(err)
}
}
I hope that helps someone!
I recently encountered the similar situation while developing a web app for Samsung Tizen Smart TV. It was required to scan the complete local network but few IP addresses were returning "401 Unauthorized" response with "www-authenticate" header attached. It was popping up a browser authentication pop requiring user to enter "Username" & "Password" because of "Basic" authentication type (https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
To get rid from this, the simple thing which worked for me is setting credentials: 'omit' for Fetc Api Call (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). Official documentation says that:
To instead ensure browsers don’t include credentials in the request, use credentials: 'omit'
fetch('https://example.com', {
credentials: 'omit'
})
For those unsing C# here's ActionAttribute that returns 400 instead of 401, and 'swallows' Basic auth dialog.
public class NoBasicAuthDialogAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new HttpStatusCodeResult(400);
}
}
use like following:
[NoBasicAuthDialogAuthorize(Roles = "A-Team")]
public ActionResult CarType()
{
// your code goes here
}
Hope this saves you some time.

How to store basic auth credentials in browser cache after XHR

Context
I have a friend, his name is Bob.
Bob have a server with an application running & accessible only in local. To access this application from the outside world, Bob installed & configured a reverse proxy with nginx and the module auth_basic.
Each request go through the authentication process by the reverse proxy. Two cases :
If a HTTP GET request contain valid HTTP header parameter Authorization: Basic base64credentials, then the reverse proxy access the local application and response accordingly. Each sub-request will not require a new authentication because the browser cache the credentials and send them in every request automatically until we close the browser.
If a HTTP GET request doesn't contain valid HTTP header parameter, the reverse proxy respond directly with the HTTP header WWW-Authenticate: Basic realm="User Visible Realm". Then the browser automatically show a dialog box to enter credentials.
Everything works fine until here. It's like expected from basic auth specification.
Problem
Bob doesn't like the default dialog box from the browser and want a nice html page with a form. He configurate the nginx server to have his own html form.
The problem is that the HTML submit process for a form, by default, doesn't send the Authorization header parameter. Bob need to use a XMLHttpRequest. He implement it and receive a good 200 HTTP response from the server when the credentials are good.
Unlike the default form behavior, with the XMLHttpRequest, the browser doesn't cache the credentials automatically in case of success. So each sub-request display again the login form :'(
Bob can't change the front code of the local application to send by himself the credentials in each request (as with a SPA). Indeed, he doesn't have access to this app. He just have access to nginx conf and his own html login form. So storage is useless here.
Questions
Is it possible for bob to make the browser cache the credentials after receive the XHR response ?
(The goal is to behave like the default behavior even when he use a XMLHttpRequest)
EDIT :
Further explanation
The local app is running on localhost. Bob didn't develop this app and he can't edit it. This app doesn't provide authentication and Bob used the basic_auth module of nginx as a reverse proxy to authenticate people.
It works good but use the default behavior of browsers which implement Basic Auth specification. This behavior display an ugly form and cache the credentials when success. If Bob provide his own form the behavior go, which is normal because the Basic Auth specification require specific header parameter (Authorization: Basic ...) that HTML form can't provide. Bob need to use XHR to provide this parameter.
The question is, how get back the good behavior of the browser with XHR ?
We can only use JS on login.html and not on the local app. Here is the workflow :
HTTP GET request to the server
Server doesn't find Authorization parameter OR credentials are wrong
Server respond login.html
User provide credentials by form. XHR is emitted with Authorization parameter.
Server find Authorization parameter AND credentials are valid
Server give back the local app entry file (for example index.html)
Browser read index.html and want request other files (img, css, js...)
These sub requests will fail because no credentials provide in these requests.
If ugly default form use, the credentials are cached automatically and it works.
I precise also that a solution would be to replace nginx basic auth reverse proxy by a real backend app and another authentication system (with cookie for example which are send automatically) which would work as a reverse proxy but it is not the question asked.
EDIT 2 :
Why Bob can't use storage solution ?
In the ulgy form scenario, he doesn't have HTML login file. When the browser client ask a request to the server, the server only response a HTTP response with the WWW-Authenticate header but without HTML content. The simple fact to have this header parameter display a form. Just putting the good credentials will send back a 200 HTTP Response and the browser will cache the credentials and send it in every request with the HTTP header Authorization: Basic.
In the login.html scenario, after a success login, we need to send back in every request the HTTP header Authorization: Basic (not a cookie, because it's how work Basic Auth spec and Bob doesn't have any backend, just the nginx module). It's possible to send this header from the login.html because we can attach JS on it. But then, the next pages respond by the server will be HTML files from the local app, where Bob doesn't have access to their HTML and can't attach JS on them to provide header Authorization: Basic for the next requests. A cookie could be stored from the login.html file, but this cookie need to be retrieved from the other pages and used to send header Authorization: Basic, which is impossible because Bob doesn't have access to the JS of these pages.
Thank you in advance.
Since you're already using ajax, just have javascript set and read a cookie:
(I use jQuery here, for simplicity, replace the ajax call with the appropriate syntax if you're not using jQuery):
function getCookie(cookiename) {
/* a function to find a cookie based on its name */
var r = document.cookie.match('\\b' + cookiename + "=([^;]*)\\b");
// document.cookie returns all cookies for this url
return r ? r[1] : undefined;
// return the regex capture if it has content, otherwise return undefined
}
function getData(auth_basic) {
$.ajax({
url: 'url_of_nginx...',
headers: {
'Authorization': 'Basic ' + auth_basic
} // send auth header on xmlhttprequest GET
}).next(function ajaxSuccess(data) {
// data from the nginx
document.cookie = '_auth_cookie=' + auth_basic;
// store my auth in a cookie
}, function ajaxFailed(jqXHR) {
// do something on failure, like
showLoginForm()
});
}
function showLoginForm() {
/* function to render your form */
// attach an event handler to form submission
$('#submit_button_id').click(function form_submitted(login_evt) {
// I clicked login
login_evt.preventDefault(); // don't really submit the form
// get my field form values
username = $('#username_input_field').val();
password = $('#password_input_field').val();
// I base64 the auth string
var auth_basic = btoa(username + ':' + password);
// try to auth
getData(auth_basic);
});
}
var auth_cookie = getCookie('_auth_cookie');
if (auth_cookie === undefined) {
// I have no cookie
showLoginForm()
} else {
getData(auth_cookie)
}

JWT: handling token auth on a multi-domain-level

Currently I'm facing the following problem:
I'm building an application with a standalone login view.
Let's assume they both run on different domains. My login view communicates with a REST service on the server that issues a JWT token.
With this issued token the user should now be able to view (be forwarded to) the application. But this application - as mentioned - runs on another domain (or subdomain, maybe).
In my mind I tried the following:
Token is issued by the server. A hash is stored in a database and the hash is issued to the user. The hash is transferred to the application via URL and the applications checks the hash.
Token is issued by the server. The token is transferred to the user. When the user now opens the application (or is forwarded to...) the token should be transferred there, too. I have no clue how to do...
Both are not ideal ways, i know. But I really don't know how I can achieve that...
I hope anyone is able to help me with my thinkings?
If anything is unclear, just comment.
Thanks in advance!
Facebook, Twitter, Google, etc. offer OAuth by redirecting (302 Redirect Request). Basically your app starts if user has a valid token everything is fine, else it will open the Login-Page from the Identity Provider (e.g. Google) and return the token, if the login was successful.
This graphic shows the general steps:
https://www.soapui.org/soapui/media/images/stories/oauth2/oauth2flow.png
Hope this helps.
Lets assume you have two services in 2 different domains. One is your identity provider which generates tokens and holds the token <--> user assignment (we call it: idp.my.company). The other one is any application that does stuff but needs to login via idp.my.company (we call it app.my.company).
You have two scenarios:
1. Login without having a token aquired before.
2. Login with token.
Request GET: app.my.company
Response 302: Moved to: idp.my.company
Take a look which redirection type follows your needs (307 maybe)
https://en.wikipedia.org/wiki/URL_redirection
You need to add an information where to redirect back. In this case app.my.company.
[This happens automatically because of the Redirect] GET: idp.my.company/login.html.
Response 200 OK: idp.my.company/login.html
The User will now see the Login-Page of your IDP-Service and perform a Login.
Request POST: idp.my.company/login.html (or whatever)
The User posts his credentials to aquire a token
Response 302 Redirect: If login is successful, return token and now redirect to the origin site (app.my.company) which you provided earlier.
Request GET: app.my.company/afterlogin.html
The header contains a valid token
Response 200 Ok: Now the app.my.company service needs to check if the token is valid and if true return 200 Ok otherwise redirect again to IDP (start over at step 2).
This should be it. There could be errors but you should have a consistent picture of the process and get a grasp on how to implement it. Those steps cover scenario 1 and 2.

Gracefully Handling 401 Not Authorized with Angularjs $http

I am currently working on an problem with a login page for an AngularJS app. The login page uses the $http service to submit the username and password using Basic authentication (Authorization: Basic (username and password in base 64)) to a Web service that may or may not be running on the same server that the webapp is hosted on. If authentication succeeds, the server returns a response with status 200 OK; otherwise, it returns a 401 Not Authorized. We have an error function to gracefully handle this error condition, but before it is even called, the browser by default shows a modal dialog for the user to enter his or her username and password. How can I prevent this?
I've looked at several sources, including the W3 standards for XHR.send(), for help, and my findings show that in Firefox and Chrome, the browser dialog pops up after a 401 response to an XHR call when all of the following are true:
1) The request is "same-origin" (which it is in the cases where the web service is running on the same server that serves the webapp)
2) The response includes the header WWW-Authenticate: Basic [some realm] or WWW-Authenticate: Digest [some realm] (our web service returns the former)
3) The request was not made by specifically setting a username and password in the XHR object whose send() method was called.
I found a test site (which SO won't let me link to) that exhibits the desired behavior for 401 responses (no modal login box). It meets the first two conditions but avoids meeting the third, apparently because it sets the XHR object's username and password by sending them as arguments #4 and 5 to its open() function. I'm not sure if there's any other way to set these properties of the XHR object, since I can't even find them by inspecting the XHR object in a browser's JavaScript debugger.
The problem is that our site's login service doesn't itself call XmlHttpRequest.open() to pass the username and password; instead, it creates the Authorization header itself and passes it in the headers collection of a params object to $http. By inspecting the source of angular.js, I found that $http then proceeds to call open() with only three parameters.
Does Angular provide a way to have $http call the 5-parameter overload of open(), or otherwise prevent this login dialog from showing up? If not, I can only think of a few workarounds:
1) Somehow decorate $http to enforce calling the 5-parameter overload of open() if a username and password are provided
2) Somehow use $httpBackend to achieve the same as #1 (though the documentation discourages developers from using $httpBackend so I'm not sure if this is a good idea or even possible)
3) Ignore $http and have my login service create and send the XHR itself
4) Alter the server so it doesn't return the WWW-Authenticate: Basic header (least desirable)
Is there a well-known or "right" way to work around this problem?
The issues you are facing is due to the first reason you have specified
1) The request is "same-origin" (which it is in the cases where the web service is running on the same server that serves the webapp).
solution
Either add access-control-allow-origin header to your backend service in order to acccept calls from different origin.
or
if you are in development phase and later your frontend and service are anyway going to be in same origin,then use https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi?hl=en extension ,
this will allow you to override chrome cross origin policy (this is just for development,its a workaround).
or
Open chrome with web security disabled by opening chrome from command prompt
chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security

Facebook Same Window Authentication

I am trying to do Facebook authentication in same window of my web application's login page.
I am using following code when user clicked login button to go to authentication page.
function loginUsingOAUTH()
{
top.location = 'https://graph.facebook.com/oauth/authorize?client_id=839846246064537&scope=email&redirect_uri=http://www.olcayertas.com/testqa/result.html';
}
1) After authentication Facebook redirects me to my redirect url and returns a parameter "code".
At this point I want to access Facebook user information but I don't know how to do that.
What is this "code" parameter for?
2) Is there any other way to access user information?
3) Do you have any other advice facebook authentication with same window login?
Thank you in advance for your help
When you get the code you should make a server side request to get an access token and than pass the access token to user. It is explained in Facebook Developer page:
Exchanging code for an access token
To get an access token, make an HTTP GET request to the following
OAuth endpoint:
GET https://graph.facebook.com/v2.3/oauth/access_token?
client_id={app-id}
&redirect_uri={redirect-uri}
&client_secret={app-secret}
&code={code-parameter}
This endpoint has some required parameters:
client_id. Your app's IDs
redirect_uri. This argument is required and must be the same as the original request_uri that you used when starting the OAuth login
process.
client_secret. Your unique app secret, shown on the App Dashboard. This app secret should never be included in client-side code or in
binaries that could be decompiled. It is extremely important that it
remains completely secret as it is the core of the security of your
app and all the people using it.
code. The parameter received from the Login Dialog redirect above.
Response
The response you will receive from this endpoint will be returned in
JSON format and, if successful, is
{“access_token”: <access-token>, “token_type”:<type>, “expires_in”:<seconds-til-expiration>}
If it is not successful, you will receive an explanatory error
message.

Categories

Resources