Can HTTP basic authentication be applied outside of assigned realm with PHP? - php

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.

Related

$_SERVER['REQUEST_URI'] reliability?

In one of my websites I use $_SERVER['REQUEST_URI'] to establish whether a non-registered user can see the content of a page or not.
In the manual there is written about $_SERVER['REQUEST_URI']:
The URI which was given in order to access this page; for instance,
'/index.html'.
My question is, is it in any client side way possible for a client to access eg. index.php even though $_SERVER['REQUEST_URI'] contains a different value?
I know that $_SERVER['REQUEST_URI'] contains the page that the client asked and that the server returns but if I don't ask myself these kind of questions once in a while I'm not happy
Also is it considered good practice to use $_SERVER['REQUEST_URI'] in this way?
EDIT: I included the script I use as it was too generic
list($c_page) = explode('.',substr($_SERVER['REQUEST_URI'],1));
define('C_PAGE',$c_page ?: 'index');
define('LOGGED',$_SESSION['user']['id'] ?: 0);
if(in_array(C_PAGE,array('page_1','page_2','page_3')) && !LOGGED){ header('Location: login.html'); exit; }
You should really track their entrance with a session variable (or a cookie). Either of these could be blocked...but they are closer to "foolproof". That said, anything can be forged...so use a combination and/or strong unique strings if security is of the utmost importance.
Depending on the serverside softwarestack you use this variable is set by the webserver or the fastcgi wrapper.
URL rewriting and non-transparent proxies in your hardware/software stack can influence the value you see within your script.
e.g. Nginx could rewrite the URL from /test.html to /index.php?action=test, then pass it to your webserver. User would have called /test.html while your application sees /index.php?action=test
Conclusion: REQUEST_URI is the URI passed to the webserver and can be used as a reference for URL-based access controls.
EDIT:
just to avoid confusion, because I've seen the other responses...
Your question as I understood:
You want to check wether your currently requesting and already password-authorized user has enough permissions to access specific URLs. Again, yes you can use the request uri as a reference value
Personally, I find it more reliable to declare one or more groups (classes) of users that should have access to a file, and then include a page that returns a 401 error if the logged in user if not in any of those groups. e.g.
session_start();
...
$access = 'admin';
include 'inc/guard.php';
Sounds like in your case you want 'public' and 'logged-in' which is slightly different, but is a case also covered by my guard script. There, I simply check that the $_SESSION variable is empty (I insert stuff into it upon log-in):
if($access != 'public' && empty($_SESSION)) {
header('HTTP/1.1 401 Unauthorized', true);
include 'inc/login.php';
exit;
}

PHP how to restrict folder and website content to different users?

I've been googling for days now and have come across different ways to secure folders (htaccess, using a PHP page with a password) but these don't tackle my issue.
The problem:
I need to have a site where different clients can access ONLY THEIR content. Client-A needs to be able to access all their Flash content and websites. Client-B and Client-C need to do the same but none of them can access each others content (even by directly linking to it). A username/password system won't work because each client has 400-1000 users and neither myself or the client has time to manage all these users.
I looked into htaccess and htpasswd but I prefer not to use any username/password combo's. Ideally, I'd like a "secret word" or "passphrase" I could pass from an iPad app or Air program to the server to get the content I need. Anyone have some ideas on the best way to handle this?
EDIT: To simplify things... I want to have HTML sites and Flash swf's above my web root and be able to display them to users. How can I make this happen? I have HTML sites that use relative links so using php's readfile() causes these sites to break since those links aren't correct.
What RDBMS are you using ?
With mod_authn_dbd and a basic authentification you would be able to do so.
Something like this,
AuthType Basic
AuthName "My Server"
AuthBasicProvider dbd
# core authorization configuration
Require valid-user
# mod_authn_dbd SQL query to authenticate a user
AuthDBDUserPWQuery "SELECT password FROM authn WHERE user = %s"
If you have control over the software which sends the requests, you could add an own X-header to every request which identifies the user.
With apache_request_headers() you can get your own request header from the request:
http://www.php.net/manual/en/function.apache-request-headers.php
==============
Edit after first comment:
Some code for example:
globals.php
$headers = apache_request_headers();
$key = $headers["X-Authorization-Key"];
$authorized = checkAuthorization($key);
if(!$authorized) {
header('HTTP/1.1 403 Forbidden');
echo "Access denied!";
exit;
}
//... db connection or something else to get user specific definitions, paths, ...
//e.g.:
$user = $users[$key];
define("CONTENT_PATH", "/var/www/mypage/data/".$user);
function checkAuthorization($key) {
//... db connection or something else where the authorization-information are stored in
//check whether the $key is in the auth-info and return true / false for the result
return true; //or false
}
in every script on top:
<?php
require_once("globals.php");
//... work with the user specific definitions, paths
include(CONTENT_PATH."/...");
//...
What you do is when the user creates their login account, they have the option to select what group they are using. Then when page info is displayed, it displays the normal page, but with the permissions name included in it. You would have to build 3 seperate content pages, but they would only see what the content of their chosen group.
homegroup1.php
homegroup2.php
homegroup3.php
if the user is in group one, the direct would be home"group".php for the display. It would call for the group on the site they go to.

PHP - Only allow access via AJAX

In my CMS, I have a page which loads lots of mini-interfaces into a panel using AJAX. Those interfaces come from various PHP files in a /ajax directory.
Would it be possible somehow to only allow access to those files using ajax, and not just by browsing to them?
My concern is, of course, that someone finds out about the /ajax directory, and gets access to the core functionality of the CMS without even having to login. Of course I could import my user class and authenticate each of the AJAX files individually, but would it be possible to only allow access through AJAX?
Would it be possible somehow to only allow access to those files using ajax, and not just by browsing to them?
No.
You could add extra HTTP headers, or modify existing ones (such as Accept) when you make the request from JavaScript … but since you are trying to do this for security reasons, that would be insufficient.
Of course I could import my user class and authenticate each of the AJAX files individually
Do that. Ajax requests are not special. They are just HTTP requests. End points created for Ajax should be secured with authentication/authorization just like any other HTTP request end point.
No. A hacker could just fake Ajax requests, anyway. You need to authenticate everywhere or you'll get screwed.
From outside a browser, it's possible for anybody to initiate HTTP requests to any public URL on your site. There's nothing special about AJAX requests other than headers, and those can be spoofed easily.
Now what can't be easily spoofed is a secure session mechanism. If you're requiring people to log in, then you should be doing that anyway.
Simple answer, "no".
Your ajax files should also validate the user is logged in the same way as the front end of your system does.
AJAX is almost same request as Standart request you can check header but this is not secure way. So short you can't do this. Authetificate on server side what you have proposed.
Authenticate your AJAX files. If there are lots of them, create a class and extend it in each individual AJAX file.
/ajax/abstract.php:
<?php
abstract class AjaxHandler {
public function __construct() {
// import authentication handler
if ($authenticated) {
$this->display();
}
else {
header('HTTP/1.1 401 Unauthorized');
exit;
}
}
}
Then in each file, i.e. /ajax/get_user_profile.php:
<?php
class GetUserProfile extends AjaxHandler {
public function display() {
// do your routine
}
}
Well, an alternative option you could have is to use post method to get the content from the php, and in your php you should set a key for the post method. And then system wide remove cross origin access. :)

design of website with membership to restricted content

I have a web site which currently has over 900 html articles currently viewable to anyone. I want to change it to restrict viewing of certain articles by membership only. I have the articles in sql database with flag if restricted. There is an article index with links to each article. The process will be to check if article is restricted, check if user is member logged in, then display, otherwise send to login or subscribe pages. Since there is so many articles, I can't just add some php to not display if the article is accessed directly. My question is where in my web directory to I put these article pages, and how do you protect someone from directly accessing the page, but allow access once the user is authenticated? Any input is appreciated. Anyone know of good reference books on this either?
Move the files so that they're above your document root, and therefore inaccessible through the web server. Or, move the files to a directory which you protect with a password (.htaccess/.htpasswd pair). You never give out that password, it's only there to prevent direct access of the files.
Then, write a script which proxies the articles. That script checks if the user is logged in. If not, it redirects them to the login page. If it is, it reads the article HTML from its actual location, and sends it through.
Ex: http://www.example.com/article.php?view=cooking.html
session_start();
if (!isset($_SESSION['logged_in'])) {
header("Location: login.php");
} else {
readfile("/path/to/articles/" . $_GET['view']);
}
You'll want to do some kind of sanitation on $_GET['view'] to make sure it doesn't contain anything but the name of an article.
You can even keep your current URLs and links to the articles by rewriting them to the proxy script in your .httaccess/httpd.conf with mod_rewrite. Something like this:
RewriteEngine On
RewriteRule article/(.*)\.html articles.php?view=$1 [L]
If you don't already have any existing framework for PHP development that would help with security matters, you might consider something simpler than even using PHP to restrict access. Read up about .htaccess files, and how you can create a protected directory in which you could place all the restricted articles. Then you can setup user account and require people to authenticate themselves before they can read the restricted articles.
Here's a tutorial on how to setup .htaccess for user authorization/authentication:
http://www.javascriptkit.com/howto/htaccess3.shtml
You have a couple of basic options:
Add the code to each page. You can probably automate this, so its not as bad as it sounds. It really shouldn't be more than a single include.
Figure out how to get your web server software (e.g., apache) to do the authentication checks. Depending on how complicated your checks are, a mod_rewrite external mapping program may be able to do it. Other than that, there are existing authentication modules, or writing a fairly simple shim isn't that hard (if you know C)
Feed all page loads through PHP. This will probably break existing URLs, unfortunately. You pass the page you want to see as a parameter or part of the path (depending on server config), then do you checks inside your script, and finally send the page if the checks pass.
The simplest way would probably be to move all the aricle files outside the web root, and then use PHP to fetch them if the client is allowed to see it.
For example:
<?php
if (isset($_GET['id']))
{
$articleDir = "/var/articles/";
// Assuming a "?id=1" query string is used to pass a numberic ID of
// an article. (Like: example.com/showArticle.php?id=1)
$articleID = (int)$_GET['id'];
$articleFile = "article_{$articleID}.html";
// Look through your SQL database to see if the article is restricted.
// I'll leave the codeing to that up to you though.
if (!$isRestricted || $isLoggedIn)
{
$path = $articleDir . $articleFile;
if (file_exists($path))
{
include $path;
}
else
{
echo "The requested article does not exist.";
}
}
else
{
echo "You have to be logged in to see this article.";
}
}
else
{
echo "No article ID was passed. Did you perhaps follow a bad link?";
}
?>
Note that if you want to keep the old links alive, you can use Apache's mod_rewrite to rewrite incoming requests and route them to your PHP file.
Edit
This may help if you are new to mod_rewrite and/or regular expressions:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^article_(\d+)\.html$ fetchArticle.php?id=$1 [L]
</IfModule>
Routs any link such as example.com/article_123.html to example.com/fetchArticle.php?id=123 without the client noticing it.
Similar to what Dan Grossman did, but this one fetches only numbers.

HTTP authentication logout via PHP

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.

Categories