What is the correct way to log out of HTTP authentication protected folder?
There are workarounds that can achieve this, but they are potentially dangerous because they can be buggy or don't work in certain situations / browsers. That is why I am looking for correct and clean solution.
Mu. No correct way exists, not even one that's consistent across browsers.
This is a problem that comes from the HTTP specification (section 15.6):
Existing HTTP clients and user agents typically retain authentication
information indefinitely. HTTP/1.1. does not provide a method for a
server to direct clients to discard these cached credentials.
On the other hand, section 10.4.2 says:
If the request already included Authorization credentials, then the 401
response indicates that authorization has been refused for those
credentials. If the 401 response contains the same challenge as the
prior response, and the user agent has already attempted
authentication at least once, then the user SHOULD be presented the
entity that was given in the response, since that entity might
include relevant diagnostic information.
In other words, you may be able to show the login box again (as #Karsten says), but the browser doesn't have to honor your request - so don't depend on this (mis)feature too much.
Method that works nicely in Safari. Also works in Firefox and Opera, but with a warning.
Location: http://logout#yourserver.example.com/
This tells browser to open URL with new username, overriding previous one.
The simple answer is that you can't reliably log out of http-authentication.
The long answer:
Http-auth (like the rest of the HTTP spec) is meant to be stateless. So being "logged in" or "logged out" isn't really a concept that makes sense. The better way to see it is to ask, for each HTTP request (and remember a page load is usually multiple requests), "are you allowed to do what you're requesting?". The server sees each request as new and unrelated to any previous requests.
Browsers have chosen to remember the credentials you tell them on the first 401, and re-send them without the user's explicit permission on subsequent requests. This is an attempt at giving the user the "logged in/logged out" model they expect, but it's purely a kludge. It's the browser that's simulating this persistence of state. The web server is completely unaware of it.
So "logging out", in the context of http-auth is purely a simulation provided by the browser, and so outside the authority of the server.
Yes, there are kludges. But they break RESTful-ness (if that's of value to you) and they are unreliable.
If you absolutely require a logged-in/logged-out model for your site authentication, the best bet is a tracking cookie, with the persistence of state stored on the server in some manner (mysql, sqlite, flatfile, etc). This will require all requests to be evaluated, for instance, with PHP.
Workaround
You can do this using Javascript:
<html><head>
<script type="text/javascript">
function logout() {
var xmlhttp;
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}
// code for IE
else if (window.ActiveXObject) {
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
if (window.ActiveXObject) {
// IE clear HTTP Authentication
document.execCommand("ClearAuthenticationCache");
window.location.href='/where/to/redirect';
} else {
xmlhttp.open("GET", '/path/that/will/return/200/OK', true, "logout", "logout");
xmlhttp.send("");
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4) {window.location.href='/where/to/redirect';}
}
}
return false;
}
</script>
</head>
<body>
Log out
</body>
</html>
What is done above is:
for IE - just clear auth cache and redirect somewhere
for other browsers - send an XMLHttpRequest behind the scenes with 'logout' login name and password. We need to send it to some path that will return 200 OK to that request (i.e. it shouldn't require HTTP authentication).
Replace '/where/to/redirect' with some path to redirect to after logging out and replace '/path/that/will/return/200/OK' with some path on your site that will return 200 OK.
Workaround (not a clean, nice (or even working! see comments) solution):
Disable his credentials one time.
You can move your HTTP authentication logic to PHP by sending the appropriate headers (if not logged in):
Header('WWW-Authenticate: Basic realm="protected area"');
Header('HTTP/1.0 401 Unauthorized');
And parsing the input with:
$_SERVER['PHP_AUTH_USER'] // httpauth-user
$_SERVER['PHP_AUTH_PW'] // httpauth-password
So disabling his credentials one time should be trivial.
Logout from HTTP Basic Auth in two steps
Let’s say I have a HTTP Basic Auth realm named “Password protected”, and Bob is logged in. To log out I make 2 AJAX requests:
Access script /logout_step1. It adds a random temporary user to .htusers and responds with its login and password.
Access script /logout_step2 authenticated with the temporary user’s login and password. The script deletes the temporary user and adds this header on the response: WWW-Authenticate: Basic realm="Password protected"
At this point browser forgot Bob’s credentials.
My solution to the problem is the following. You can find the function http_digest_parse , $realm and $users in the second example of this page: http://php.net/manual/en/features.http-auth.php.
session_start();
function LogOut() {
session_destroy();
session_unset($_SESSION['session_id']);
session_unset($_SESSION['logged']);
header("Location: /", TRUE, 301);
}
function Login(){
global $realm;
if (empty($_SESSION['session_id'])) {
session_regenerate_id();
$_SESSION['session_id'] = session_id();
}
if (!IsAuthenticated()) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.$_SESSION['session_id'].'",opaque="'.md5($realm).'"');
$_SESSION['logged'] = False;
die('Access denied.');
}
$_SESSION['logged'] = True;
}
function IsAuthenticated(){
global $realm;
global $users;
if (empty($_SERVER['PHP_AUTH_DIGEST']))
return False;
// check PHP_AUTH_DIGEST
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset($users[$data['username']]))
return False;// invalid username
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
// Give session id instead of data['nonce']
$valid_response = md5($A1.':'.$_SESSION['session_id'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response)
return False;
return True;
}
Typically, once a browser has asked the user for credentials and supplied them to a particular web site, it will continue to do so without further prompting. Unlike the various ways you can clear cookies on the client side, I don't know of a similar way to ask the browser to forget its supplied authentication credentials.
The only effective way I've found to wipe out the PHP_AUTH_DIGEST or PHP_AUTH_USER AND PHP_AUTH_PW credentials is to call the header HTTP/1.1 401 Unauthorized.
function clear_admin_access(){
header('HTTP/1.1 401 Unauthorized');
die('Admin access turned off');
}
Trac - by default - uses HTTP Authentication as well. Logout does not work and can not be fixed:
This is an issue with the HTTP authentication scheme itself, and there's nothing we can do in Trac to fix it properly.
There is currently no workaround (JavaScript or other) that works with all major browsers.
From: http://trac.edgewall.org/ticket/791#comment:103
Looks like that there is no working answer to the question, that issue has been reported seven years ago and it makes perfect sense: HTTP is stateless. Either a request is done with authentication credentials or not. But that's a matter of the client sending the request, not the server receiving it. The server can only say if a request URI needs authorization or not.
I needed to reset .htaccess authorization so I used this:
<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'Text to send if user hits Cancel button';
exit;
}
?>
Found it here :
http://php.net/manual/en/features.http-auth.php
Go figure.
A number of solutions reside on that page and it even notes at the bottom: Lynx, doesn't clear the auth like other browsers ;)
I tested it out on my installed browsers and once closed, each browser seems like it consistently requires reauth on reentry.
This might be not the solution that was looked for but i solved it like this.
i have 2 scripts for the logout process.
logout.php
<?php
header("Location: http://.#domain.com/log.php");
?>
log.php
<?php
header("location: https://google.com");
?>
This way i dont get a warning and my session is terminated
AFAIK, there's no clean way to implement a "logout" function when using htaccess (i.e. HTTP-based) authentication.
This is because such authentication uses the HTTP error code '401' to tell the browser that credentials are required, at which point the browser prompts the user for the details. From then on, until the browser is closed, it will always send the credentials without further prompting.
The best solution I found so far is (it is sort of pseudo-code, the $isLoggedIn is pseudo variable for http auth):
At the time of "logout" just store some info to the session saying that user is actually logged out.
function logout()
{
//$isLoggedIn = false; //This does not work (point of this question)
$_SESSION['logout'] = true;
}
In the place where I check for authentication I expand the condition:
function isLoggedIn()
{
return $isLoggedIn && !$_SESSION['logout'];
}
Session is somewhat linked to the state of http authentication so user stays logged out as long as he keeps the browser open and as long as http authentication persists in the browser.
Maybe I'm missing the point.
The most reliable way I've found to end HTTP Authentication is to close the browser and all browser windows. You can close a browser window using Javascript but I don't think you can close all browser windows.
While the others are correct in saying that its impossible to logout from basic http authentication there are ways to implement authentication which behave similarly. One obvious appeoach is to use auth_memcookie. If you really want to implement Basic HTTP authentication (i.e. use the browser dialogs for logging in trather than an HTTP form) using this - just set the authentication to a seperate .htaccess protected directory containing a PHP script which redirects back where te user came after createing the memcache session.
There's a lot of great - complex - answers here. In my particular case i found a clean and simple fix for the logout. I have yet to test in Edge.
On my page that I have logged in to, I have placed a logout link similar to this:
logout
And in the head of that logout.html page (which is also protected by the .htaccess) I have a page refresh similar to this:
<meta http-equiv="Refresh" content="0; url=https://logout:logout#MyDomainHere.net/" />
Where you would leave the words "logout" in place to clear the username and password cached for the site.
I will admit that if multiple pages needed to be able to be directly logged in to from the beginning, each of those points of entry would need their own corresponding logout.html page. Otherwise you could centralize the logout by introducing an additional gatekeeper step into the process before the actual login prompt, requiring entry of a phrase to reach a destination of login.
I have summarised my solution in an article (https://www.hattonwebsolutions.co.uk/articles/how_to_logout_of_http_sessions) however I have used an ajax call and 2x htaccess files (as suggested in this question: How to logout of an HTTP authentication (htaccess) that works in Google Chrome?).
In short - you:
Create a sub folder with an htaccess file on the same AuthName but require a different user
Send an ajax request to the page (with the wrong username) (which fails) and then trigger a timeout redirect to the logged out page.
This avoids having a secondary popup in the logout folder requesting another username (which would confuse users). My article uses Jquery but it should be possible to avoid this.
Related
$email = trim($_POST['email']);
$pass = md5(trim($_POST['password']));
$user = R::findOne("user"," email = ? AND password = ? ", array($email,$pass));
if($user != NULL) {
// good login
header('Location: http://www.google.com/');
} else {
// bad login
}
Now the framework we are provided already in redbean to create the login script is in this file which runs when we submit any form on website -
require_once('xyz.class.php');
if (isset($_REQUEST['apiName']) && $_REQUEST['apiName'] != null) {
$apiName = $_REQUEST['apiName'];
switch ($apiName) {
case 'requestSignUp':
echo UberHealth::requestSignUp($_REQUEST['email']);
break;
case 'contact':
echo UberHealth::contact($_REQUEST['email'], $_REQUEST['msg'], $_REQUEST['name'], $_REQUEST['subject']);
break;
...
}
}
and xyz.class.php has redbean functions for logging in / sign up etc.
Now when i login through this script this gives me an error on console like -
XMLHttpRequest cannot load http://www.google.com/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.
I have tried
return header('Location: http://www.google.com/');
return json_encode(header('Location: http://www.google.com/'));
instead of just header('Location: ...'));
But gave the same error.
I think you're missing the Access-Control-Allow-Origin header, as the message said :)
Here is the same question on SO.
Edit: To address your header alternative question
As far as I understood you're searching for an alternative approach for header redirects. Well typically PHP Frameworks I know use some Bootstrapping mechanisms to build the user output. Take a look at my rude graphic below:
Essentially the request from the client will always be handled via a main entry point (index.php) where you depending on the url style either use hashes, or parts of the querystring to determine where to navigate to. Thats the job of the bootstrapper to find out: "Ok the user want's to visit Page XYZ".
The next layer could be the Authorization amongst others. Here you would check whether the user has the permission or not if so continue to your BL and render the result which then is returned to the client.
So this whole sequence takes place during the initial request from the client thus no redirecting would be needed. For detailed examples take a look how well-known Frameworks do the Job.
I am using PHP to implement HTTP Basic Authentication on the Apache HTTP Server (version 2.2). The only directory I want to password-protect is a sub-directory of the main public web root of my website (for example purposes, let's call the protected directory '/private', and its realm "Private”).
If possible, I want users that have already been authenticated in the “Private” realm – and those users only – to see customised content on the web site’s home page (and any other web page, for that matter).
What I want to know is, is it possible to do this without forcing a login prompt at the top-level of my domain's top-level (e.g. www.jdclark.org or, for that matter, any other URIs outside if the “Private” realm)?
EDIT:
One technique that I have thought about applying to achieve the above is as follows: When a user has been authenticated into the “Private” realm via the access control in the ’/private’ directory, a session cookie could be set in PHP.
I could then check for the presence of this cookie (which could possibility contain a session ID or some kind of unique random string), but although I’m don’t proclaim to be an IT security expert, this method feels a bit “hackish,” and something tells me that this is insecure (e.g. would it be trivial for a malicious user to spoof that cookie with an HTTP header?).
Any advice would be very much appreciated.
Check for the presence of the HTTP authentication headers and use this to determine what to display.
if (isset($_SERVER['PHP_AUTH_USER'])) {
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
// validate login credentials
$private = true; // or false if validation fails
}
if ($private) {
// load special view
}
else {
// load normal view
}
This will not prompt the user to log in if they have not already done so elsewhere.
If you are going to be doing this in multiple places it would make sense to have a single re-usable class or file that provided a function to check for the headers and validate the credentials (returning true on success, false on fail) rather than duplicating code.
So I've got a Backbone application + web homepage. Right now, if you login to my website, I create a global object with your user details from the database. However, you can still just hit one of the routes in the application directly.
How should I handle users who are not "logged in" and redirect them to a "you must login page"?
Is this a standard operation? Basically, I have a REST url setup that returns just
{ sessionId: [php-session-id-here] }
If they are logged in, it would return something more like this:
{
sessionId: [php-sess-id],
userId: [user-id-from-db],
firstName: [f-name],
lastName: [l-name]
}
Ideas? Thanks!
What I've done in the past is to include on every page along with jQuery (actually, added to the jQuery file) an extension on the AJAX method to check for a custom code that I send when a user isn't logged in. When that value was seen it redirected the user to the login page regardless of what was going down.
This was because that site had a time out on login, so a user could get logged out while sitting on a page and then the AJAX request would just fail. If you don't have a timeout on the login the odds of ever seeing this issue are slim. Just ignore requests that come from users that aren't logged in.
If you need help coding this, start here: Extending Ajax: Prefilters, Converters, and Transports.
Really shouldn't require anything as complex as pseudo-code:
JS needs to do some AJAX, so JS talks to server
PHP checks for login if needed
If not logged in, send back the abort message (I used a converter to catch a "notLoggedIn" dataType. However this could also be done with a transport, they are just more complex.)
JS sees the abort message and does a window.location redirect rather than return AJAX message.
If you want, you could load a lightbox with a login form and send that via AJAX to PHP where a re-login can take place, if you remember the AJAX attempt that failed you can send it again after login. Then the user doesn't even need to leave the page to log back in.
If you're using jQuery, you can set a global ajaxSetting that allows you to do certain things upon certain http codes. Some pages I read recommend adding to your JSON a url field to point to where to login, but I figure that's just up to you. So the only modifications you'd need to implement what I've mentioned is 1. change the http code to something reasonable like 401 (unauthorized) and implement the http code handler. But I wouldn't call this standard, I'd just say that's what several people have done (including myself).
<?php
function IsLoggedIn()
{
if(isset($_SESSION['id'])) // Change that to what you want
{
return 1;
}
else
{
return 0;
}
}
?>
Then in your code, you could use something like:
if(isLogged()){ header('Location: http://google.com'); }
I'm using some PHP pages do some AJAX stuff but I don't want them to be directly accessible. Facebook does a similar thing so for example: domain.com/ajax/my_ajax_form.php
If I was to load that page using AJAX it would work fine, but if a user were to try and loading the file directly by typing in that url it would do through an error so e.g.
if( IS FILE LOADED DIRECT? )
{
header ( HTTP/1.0 404 );
}
This isn't possible. You cannot rely on $_SERVER['HTTP_X_REQUESTED_WITH'], and even if you could, it doesn't matter. Anyone can send the same request to your server that your browser does, via POST or GET.
What you should do is validate the request, and return the proper result if it is valid. If it is invalid, do not return a 404. (Browsers can cache errors like 404. If your client-side code had a trouble, subsequent requests may fail!) If the request is invalid, return an error of some sort.
Again, it is impossible to secure stuff like this. You should be validating the session and request data. That's all.
You can look for the HTTP_X_REQUESTED_WITH header.
$is_ajax = array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER)
&& $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
if (! $is_ajax) {
die('go away.');
}
Note, though, that it's not standard, but needs to be set explicitly on the requesting side. AFAIK, at least jQuery and Mootools set it though, probably most others as well, but don't take my word for it.
Simplest way is to only access that page via POST, and not via GET. Though keep in mind - if a browser can do it - then a hacker can too.
You have to use session variables, or more generally, cookies.
With cookies: (set in JavaScript)
JavaScript: Set token in cookie
JavaScript: Make XMLHttpRequest
Server side: Check token from cookie
Server side: Return JSON output or error message
Please note that this is no way secure! This just prevents easy linking.
With session variables: (cookies set in server side)
Server side: Authenticate user, set privileges
JavaScript: Make XMLHttpRequest
Server side: Check privileges
Server side: Return JSON output or error message
This method is as secure as the user authentication is.
How can I use HTTP basic Authentication and have the user submit their Username and Password in a HTML form and have it Authenticate using HTTP Basic Authentication.
I heard that Internet Explorer no longer supports the use of http://user:password#website.com no more so I don't know the best way to approach this.
Use of PHP and javascript and HTML is OK. I don't want to use PERL and I perfer no big javascript libs.
If you don't think HTTP Basic Auth. is the best way, please recommend something easy and simple to do. It will only be a login site for 5-6 people. No need to complicate it.
jQuery library has ajax function which has "password" and "user" parameter for Authentication. When user click login you can get value of login and password and passed to $.ajax function.
$('#submit').click(function() {
$.ajax({
url: 'authenticated.php',
username: $('#login').val(),
password: $('#passwd').val(),
success: function(data) {
//do something with data from the server
}
});
return false;
});
IMHO, the whole point of using HTTP authentication is being able to delegate authentication tasks:
The web server takes care of denying unauthorized access to protected resources
The browser takes care of asking for username and password when required
So you have a working system with minimum effort.
Now, if you use an HTML form to ask for credentials, the server will know who you are but the browser won't: it'll ask for credentials as soon as it finds the WWW-Authenticate response header and the 401 status code. For this to work, the browser has to send an Authorization request header on every HTTP request; however, your form cannot instruct the browser to send the appropriate HTTP header.
Of course, you can write your own server-side authentication code in PHP, configure the server to parse static files through it and omit 401 and WWW-Authenticate as soon as you get valid credentials (which then need to be stored somewhere else, e.g., a PHP session). But then you've lost all the advantages of HTTP authentication: at this point, a custom login handler with PHP sessions will be a much easier solution.
To sum up:
If you need simplicity, forget about HTML forms
If you need HTML forms, write your own code
If i understand you correctly you need to use .htpasswd with .htaccess: http://tools.dynamicdrive.com/password/
How can I use HTTP basic Authentication and have the user submit their Username and Password in a HTML form
No way.
please recommend something easy and simple to do.
sessions, cookies.
google for PHP auth tutorial and get over 9000 articles
oh well, one of them
<?
if (isset($_POST['auth_name'])) {
$name=mysql_real_escape_string($_POST['auth_name']);
$pass=mysql_real_escape_string($_POST['auth_pass']);
$query = "SELECT * FROM users WHERE name='$name' AND pass='$pass'";
$res = mysql_query($query) or trigger_error(mysql_error().$query);
if ($row = mysql_fetch_assoc($res)) {
session_start();
$_SESSION['user_id'] = $row['id'];
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
}
header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
exit;
}
if (isset($_GET['action']) AND $_GET['action']=="logout") {
session_start();
session_destroy();
header("Location: http://".$_SERVER['HTTP_HOST']."/");
exit;
}
if (isset($_REQUEST[session_name()])) session_start();
if (isset($_SESSION['user_id']) AND $_SESSION['ip'] == $_SERVER['REMOTE_ADDR']) return;
else {
include 'your design here.php';
?>
<form method="POST">
<input type="text" name="auth_name"><br>
<input type="password" name="auth_pass"><br>
<input type="submit"><br>
</form>
<?
}
exit;
?>
intended to be put into file and called as require '/path/auth.php'; at the top of your scripts