Is it possible to send a 401 Unauthorized AND redirect (with a Location)?
Asked Answered
C

8

60

I'd like to send a 401 Unauthorized AND redirect the client somewhere. However:

if I do it like this:

header('HTTP/1.1 401 Unauthorized');
header('Location: /');

the server sends a 302 Found with Location, so not a 401 Unauthorized.

If I do it like this:

header('Location: /');
header('HTTP/1.1 401 Unauthorized');

the browser receives both a 401 Unauthorized and a Location, but does not redirect.

(IE 9 and Chrome 16 behave the same, so I'm guessing it's correct)

Maybe I'm misusing HTTP? I'd like my app interface to be exactly the same for all clients: text browser, modern browser, API calls etc. The 401 + response text would tell an API user what's what. The redirect is useful for a browser.

Is there a (good) way?

Chrisman answered 8/1, 2012 at 5:24 Comment(1)
So, if I understood this whole thread correctly... we should stop thinking that these codes have anything to do with our users. The codes have to do with the browser. So, if a user hasn't logged in, the browser just has to know to redirect them? Yeah?Warehouse
A
46

By definition (see RFC 2616), the HTTP 302 response code is the redirect code. Without it, the location header may be ignored.

However, you can send an HTTP 401 response and still display output. Instead of redirecting the user to an error page, you could simply write your content you want to send in the HTTP body in the same request.

Ammonite answered 8/1, 2012 at 5:29 Comment(5)
This makes sense, why redirect? Just load the login page or whatever, right?Bathsheb
The login form in the 401 page... That actually makes sense. Why did I not think of that!?Chrisman
This answer actually supports the idea of making modular templates of your web pages for inclusion in such situations as these.Aviv
I'm trying to use POST data to logout BASIC-AUTH. It works fine, but you end up with the form re-submission on refresh problem, which you can't solve with a redirect. You need to send 401 to logout, but you also need 302 to redirect. I'm not sure how to handle this one.Annabal
nevermind; solved it. sent a Javascript redirect in the body of the 401 page.Annabal
F
40

I'm coming in very late here but I thought I'd add my two cents. As I understand it, the desire is to indicate that the user doesn't have the correct authorization and to prompt them to log in. Rudie understandably would like to return 401 Unauthorized (because the user needs to authorize by some mechanism, eg. logging in), and also forward them to the login page - but this is not very easy to accomplish and isn't supported out-of-the-box by most libraries. One solution is to display the login page in the body of the 401 response, as was suggested in another answer. However, let me take a look at this from the perspective of established/best practice.

Test case 1: Facebook

Navigating to a protected Facebook page (my user profile) while logged out results in a 404 Not Found response. Facebook serves up a general purpose "this page is not available" page, which also includes a login form. Interesting. Even more interesting: when I navigate to the "events" page, I'm served a 302 response which forwards to a login page (which returns a 200 response). So I guess their idea is to return 302 for pages that we know exist, but serve 404 for pages which may or may not exist (eg. to protect a user's privacy).

Test case 2: Google Inbox

Navigating to my inbox when I am logged out returns 302 and forwards me to a login page, similar to Facebook. I wasn't able to figure out how to make my Google+ profile private so no test data there...

Test case 3: Amazon.com

Navigating to my order history when I am logged out returns 302 and forwards me to a login page as before. Amazon has no concept of a "profile" page so I can't test that here either.

To summarize the test cases here, it seems to be best practice to return a 302 Found and forward to a login page if the user needs to log in (although I would argue 303 See Other is actually more appropriate). This is of course just in the case where a real human user needs to input a username and password in an html form. For other types of authentication (eg. basic, api key, etc), 401 Unauthorized is obviously the appropriate response. In this case there is no need to forward to a login page.

Fyn answered 24/1, 2017 at 19:50 Comment(4)
I like this answer. Just out of curiosity, I tried requesting the URL for one of my private Amazon wishlists while unauthenticated, and received a 200 OK response! No forwarding, but the body of the page does show my name and public wishlists, as well as a message saying "We're sorry. The Web address you entered is not a functioning page on our site." Possibly the least defensible of the various approaches you identified!Little
I think the reason most sites do this is to count login pages. If you have a tracker, such as Google Analytics, on your login page, the simplest way to count it is by it's URL. There are other ways to track, specific identifiers that you can add to each page, but they mostly rely on Javascript. So if JS is not running on the UA the click tracking doesn't see it. So, for big sites like those, they want the simplest, easiest way to count important pages like login. So that's why they send redirect instead of a 401.Stope
I like your naturalistic approach. :-)Lenz
Awesome answer. Thank you for spending your time on this.Rake
E
7

3xx means Redirect
4xx means the browser did something wrong.

There's a reason why the codes are split up the way they are - they don't mix ;)

Expecting answered 8/1, 2012 at 5:30 Comment(1)
To expand on this - the way the concerns are separated in HTTP encourages you not to create unnecessary redirects - you should be able to send all necessary information (in this case that you are unauthorised (header) to access the resource and the information about how to authenticate yourself (body)) in a single response instead of requiring two separate transactions.Grigson
L
4

In addition to the fine answers from Kolink and David (+1's), I would point out that you are attempting to change the semantics of the HTTP protocol by both returning a 401 AND telling the browser to redirect. This is not how the HTTP protocol is intended to work, and if you do find a way to get that result, HTTP clients will find the behavior of your service to be non-standard.

Either you send a 401 and allow the browser to deal with it, or you handle the situation differently (e.g. as one commenter suggested, redirect to a login page or perhaps to a page explaining why the user had no access).

Lim answered 8/1, 2012 at 5:46 Comment(7)
I'm pretty sure we all don't use HTTP the way it was originally intended, like 70 years ago. And that doesn't matter, at all.Chrisman
@Chrisman When people have to communicate with eachother over HTTP, it does matter what practice each side is following and this is why standards matter.Rosana
@Rosana It does indeed matter what practice each side is following, but that practice usually isn't HTTP as it was originally intended. And that's fine. Only the last few years REST is suddenly hot, and "changing the semantics of the HTTP protocol" is 'wrong'.Chrisman
@Chrisman Yes, I agree with you. My opinion was completely confined to REST. Obviously, when any party want to customize HTTP semantic that's fine as long as any other party agree on thatRosana
If either of you downvoted this, would you please explain why? The downvote happened the same day this conversation started, but I don't see any comment on why this answer might be wrong or misleading.Lim
Wasn't me, but I don't think it's a good answer, because <my previous comment>.Chrisman
@Rudie: Browsers implement the current HTTP standard. The browser makers did not all get together and decide to ignore the standard, in a consistent manner. If a server application behaves in a manner contrary to the standard, there can be no expectation that browsers will react as you might hope, or that they will react in a consistent manner. If "the other party" is an internal HTTP client that you control, sure you can do whatever you want.Lim
M
3

You can send 401 and then in response body you can send window.location='domain.com'. However, user will be immediately redirected without knowing that 401 occurred.

Myrlmyrle answered 8/1, 2012 at 6:0 Comment(1)
The user doesn't have to know that a 401 occured. The user has no idea what a 401 is. The redirect takes too long though: downloading response body, parsing HTML, parsing JS etc.Chrisman
E
3

Here is a clean way:

On the 401 page, you can choose the "view" to send based on the "accept" header in the request.

If the accept is application/json, then you can include the body:

{"status":401;"message":"Authentication required"}

If the "accept" is text/html, then you can include the body:

<form action="/signin" method="post">
    <!-- bla bla -->
    <input type="hidden" name="redirect" value="[URL ENCODE REQUEST URI]">
</form>

Then you run into the same question... do you issue a 200 OK or a 302 Found on a successful login? (see what I did there? )

If you can handle authentication on any page, you can just have the form action be the same page URL, but watch for XSS if you are putting the user supplied request_uri in the form action attribute.

Electrolyze answered 10/3, 2016 at 20:24 Comment(0)
O
1

Web browsers are not REST clients. Stick to sending status 200 with a Location header and no body content. The 30x redirects are for pages that have moved. No other status code/Location header should be expected to redirect in a web browser.

Alternatively, your web server may have configurable error pages. You can add javascript to the error page to redirect.

Onlybegotten answered 23/12, 2017 at 0:6 Comment(0)
B
0

Why nobody mentioned sending HTML with auto-redirection via 'meta' (with 401 status)? As for me, it is not bad approach.

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="refresh" content="0; url=/login?redirect=bla-bla">
        <title>User is not authenticated</title>
    </head>
    <body>
        <a href="/login?redirect={redirect}">Login</a>
    </body>
</html>

Balliett answered 14/6, 2024 at 15:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.