123

I need to send authorization request using basic auth. I have successfully implemented this using jquery. However when I get 401 error basic auth browser popup is opened and jquery ajax error callback is not called.

Alexey Zakharov
  • 24,694
  • 42
  • 126
  • 197
  • 1
    Possible duplicate of [How can I supress the browser's authentication dialog?](http://stackoverflow.com/questions/86105/how-can-i-supress-the-browsers-authentication-dialog) – Korayem Dec 07 '15 at 05:53

12 Answers12

50

I was facing this issue recently, too. Since you can't change the browser's default behavior of showing the popup in case of a 401 (basic or digest authentication), there are two ways to fix this:

  • Change the server response to not return a 401. Return a 200 code instead and handle this in your jQuery client.
  • Change the method that you're using for authorization to a custom value in your header. Browsers will display the popup for Basic and Digest. You have to change this on both the client and the server.

    headers : {
      "Authorization" : "BasicCustom"
    }
    

Please also take a look at this for an example of using jQuery with Basic Auth.

Gergely Toth
  • 6,638
  • 2
  • 38
  • 40
nwinkler
  • 52,665
  • 21
  • 154
  • 168
  • 10
    WWW-Authenticate:xBasic realm=com.example can do it, together with classic 401 status code. this blog post showed me the hint ( I am not the owner of the blog )http://loudvchar.blogspot.ca/2010/11/avoiding-browser-popup-for-401.html – P.M Dec 13 '12 at 02:26
  • 2
    @P.M, the blog's answer is a perfect solution. Note that if using `` you do not need to define `basicAuthenticationFilter` but should define it as ``. – Brett Ryan Jul 17 '13 at 06:32
  • Can you plz tell me how to override the response before sending back to client,i am using jaxrs with basic auth . which class do i need to override for modifying the response? – moh Apr 18 '17 at 12:46
  • For some reason I am getting the popup when returning a `401` and `WWW-Authenticate:Bearer WWW-Authenticate:NTLM WWW-Authenticate:Negotiate` Do you know why that would be – DevEng Feb 16 '18 at 16:24
  • 2
    @P.M FYI this method no longer works in the current Chrome version – tnkh Jun 29 '21 at 00:37
38

Return a generic 400 status code, and then process that client-side.

Or you can keep the 401, and not return the WWW-Authenticate header, which is really what the browser is responding to with the authentication popup. If the WWW-Authenticate header is missing, then the browser won't prompt for credentials.

Ibraheem
  • 2,168
  • 20
  • 27
  • 7
    @MortenHaraldsen Well the 401 response is the proper response to give in this occasion, the problem is that the browser is automatically handling this natively, instead of allowing the javascript app to handle it. You can stick to the standard, by not returning the proper response that the standard recommends, or you can choose to not stick to the standard, by returning the standard-recommended response code. Take your pick :) – Ibraheem Feb 26 '15 at 19:30
  • In my express app, I fixed this with one line: `res.removeHeader('www-authenticate'); // prevents browser from popping up a basic auth window.` – gstroup Feb 13 '16 at 00:05
  • 2
    @Ibraheem, couldn't have stated it better myself. Standards are created by people who sit down and talk, not necessarily the ones who sit down and code. – user2867288 Aug 01 '16 at 17:33
  • @Ibraheem not working for the current Chrome browser. Anyway to circumvent this now? – tnkh Jun 29 '21 at 00:38
16

As others have pointed out, the only way to change the browser's behavior is to make sure the response either does not contain a 401 status code or if it does, not include the WWW-Authenticate: Basic header. Since changing the status code is not very semantic and undesirable, a good approach is to remove the WWW-Authenticate header. If you can't or don't want to modify your web server application, you can always serve or proxy it through Apache (if you are not using Apache already).

Here is a configuration for Apache to rewrite the response to remove the WWW-Authenticate header IFF the request contains contains the header X-Requested-With: XMLHttpRequest (which is set by default by major Javascript frameworks such as JQuery/AngularJS, etc...) AND the response contains the header WWW-Authenticate: Basic.

Tested on Apache 2.4 (not sure if it works with 2.2). This relies on the mod_headers module being installed. (On Debian/Ubuntu, sudo a2enmod headers and restart Apache)

    <Location />
            # Make sure that if it is an XHR request,
            # we don't send back basic authentication header.
            # This is to prevent the browser from displaying a basic auth login dialog.
            Header unset WWW-Authenticate "expr=req('X-Requested-With') == 'XMLHttpRequest' && resp('WWW-Authenticate') =~ /^Basic/"
    </Location>   
syastrov
  • 656
  • 6
  • 6
15

You can suppress basic auth popup with request url looking like this:

https://username:password@example.com/admin/...

If you get 401 error (wrong username or password) it will be correctly handled with jquery error callback. It can cause some security issues (in case of http protocol instead of https), but it's works.

UPD: This solution support will be removed in Chrome 59

Ivan Samovar
  • 323
  • 2
  • 7
  • AMAZING!!!! Solved my problem as the issue was trying 192.168.1.1 and the router kept asking for Auth. – Nadav Lebovitch Feb 05 '15 at 10:00
  • 2
    If you go to the "Network" tab under the Developer tools, in any browser, you are able to read the username and password in plain text. It works, though. – Bruno Finger Sep 30 '15 at 14:00
  • 23
    Do not ever do this please, the requests logs on your webserver are much more valuable to me now.. Free usernames and password combo's plus the response code! Thanks – Remco Mar 09 '16 at 09:26
  • but how can someone prevent the username and password from being viewed – sdx11 Apr 10 '17 at 10:38
  • @sdx11 Don't use basic authorization and send your username/passwords in Authorization header which are not logged anywhere (according to standard). – Michael Fry Apr 23 '17 at 10:06
  • @MichaelFry this was cctv authentication and the only supported way to access it , but i think i should find a custom firmware for this issue . – sdx11 Apr 23 '17 at 10:12
  • 4
    This authentication method is becoming depreciated and Chrome will be dropping support for embedded credentials i.e. `https://user:pass@host/` in M59 around June 2017. See [this chromestatus blog post](https://www.chromestatus.com/feature/5669008342777856) for more info. – Garywoo Apr 26 '17 at 10:34
9

Use X-Requested-With: XMLHttpRequest with your request header. So the response header will not contain WWW-Authenticate:Basic.

beforeSend: function (xhr) {
                    xhr.setRequestHeader('Authorization', ("Basic "
                        .concat(btoa(key))));
                    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
                },
Sedhu
  • 723
  • 1
  • 11
  • 36
5

If you're using an IIS Server, you could setup IIS URL Rewriting (v2) to rewrite the WWW-Authentication header to None on the requested URL.

Guide here.

The value you want to change is response_www_authenticate.

If you need more info, add a comment and I'll post the web.config file.

stites
  • 4,903
  • 5
  • 32
  • 43
Zymotik
  • 6,412
  • 3
  • 39
  • 48
  • 1
    This worked out great. I would like to note that the "response" part must be written as "RESPONSE_www_authenticate" in URL Rewrite v2 on IIS 7.5. – Michael Freeman Jun 21 '16 at 13:42
4

If WWW-Authenticate header is removed, then you wont get the caching of credentials and wont get back the Authorization header in request. That means now you will have to enter the credentials for every new request you generate.

  • This is very important, absolutely spot on. – Tez Wingfield Feb 20 '17 at 11:08
  • This assumes you have control over the server, which unfortunately is not always the case. – Michael Jan 27 '21 at 23:20
  • So, there's no way to cache the header when not using WWW-Authenticate? This is fine if all that has to be done is return a token from the server and use that for future Authorization header AJAX requests and save it in local or session storage, but if there is a way to cache it without using the standard popup, that'd be nice, especially since visits to sites via browser address bar do not allow injected headers like Authorization. – Jonathan Ma Jul 28 '21 at 14:58
3

Haven't explored the why or scope of fix, but I found if I'm doing a fetch request, and add the header x-requested-with: 'XMLHttpRequest', I no longer get the popup auth box in Chrome and don't need a server change. It's talking to the node http library. Looks like WWW-Authenticate header comes back from the server, but Chrome handles it differently. Probably spec'd.

Example:

fetch(url, {
  headers: {
     Authorization: `Basic ${auth}`,
     'x-requested-with': 'XMLHttpRequest'
  },
  credentials: 'include'
})
fionbio
  • 3,368
  • 2
  • 23
  • 38
2

Alternatively, if you can customize your server response, you could return a 403 Forbidden.

The browser will not open the authentication popup and the jquery callback will be called.

Javier Ferrero
  • 8,741
  • 8
  • 45
  • 50
  • 5
    That goes against the HTTP 1.1 specification, where it is stated that "... Authorization will not help and request SHOULD NOT be repeated". – Jukka Dahlbom Feb 27 '13 at 11:04
  • 1
    It is valid to receive 403 while being authenticated for resources you are not allowed access to, 401 should be sent where you have not yet been authenticated. – Brett Ryan Jul 17 '13 at 06:21
1

In Safari, you can use synchronous requests to avoid the browser to display the popup. Of course, synchronous requests should only be used in this case to check user credentials... You can use a such request before sending the actual request which may cause a bad user experience if the content (sent or received) is quite heavy.

    var xmlhttp=new XMLHttpRequest;
    xmlhttp.withCredentials=true;
    xmlhttp.open("POST",<YOUR UR>,false,username,password);
    xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    xmlhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
Emmanuel Sellier
  • 526
  • 1
  • 5
  • 13
0

Make an /login url, than accept "user" and "password" parameters via GET and don't require basic auth. Here, use php, node, java, whatever and parse your passwd file and match parameters (user/pass) against it. If there is a match then redirect to http://user:pass@domain.com/ (this will set credential on your browser) if not, send 401 response (without WWW-Authenticate header).

neiker
  • 8,887
  • 4
  • 29
  • 32
  • 1
    That would be super-great for anybody trying to sniff username/passwords out of plain-text man in the middle attacks! – Ajax Jan 09 '18 at 19:58
0

From back side with Spring Boot I've used custom BasicAuthenticationEntryPoint:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().authorizeRequests()
            ...
            .antMatchers(PUBLIC_AUTH).permitAll()
            .and().httpBasic()
//    https://www.baeldung.com/spring-security-basic-authentication
            .authenticationEntryPoint(authBasicAuthenticationEntryPoint())
            ...

@Bean
public BasicAuthenticationEntryPoint authBasicAuthenticationEntryPoint() {
    return new BasicAuthenticationEntryPoint() {
        {
            setRealmName("pirsApp");
        }

        @Override
        public void commence
                (HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
                throws IOException, ServletException {
            if (request.getRequestURI().equals(PUBLIC_AUTH)) {
                response.sendError(HttpStatus.PRECONDITION_FAILED.value(), "Wrong credentials");
            } else {
                super.commence(request, response, authEx);
            }
        }
    };
}
Grigory Kislin
  • 16,647
  • 10
  • 125
  • 197