I prefer running a site in HTTP only mode when there are no confidential information transferred (username, password, credit card number, etc.). It saves some of the CPU time because there is no need to do data encryption. But I strongly recommend to use HTTPS mode for confidential information exchange between a web browser and a web server.
I was facing a situation where I had to authenticate a user on a Apache web server, which provided HTTP as well as HTTPS connection. By default a web application running on that server was accessed only by HTTP. I had no access to the Apache's configuration (no root access), what would not be a problem, if I wanted to do just a .htaccess and a .htpasswd based basic HTTP authentication without anything else. It's pretty easy then. Just create a .htaccess file, with contents like this:
AuthName 'Enter your Username and Password:'
AuthType Basic
AuthUserFile /var/www/myweb/.htpasswd
Require valid-user
and a .htpasswd file (man htpasswd) which will contain usernames and particular passwords.
Then put the .htaccess file into a directory which you want to be protected by username/password.
You can even customize it by adding a FileMatch property to require credentials validation only when accessing some files:
AuthName 'Enter your Username and Password:'
AuthType Basic
AuthUserFile /var/www/myweb/.htpasswd
Require valid-user
<FilesMatch "(attach|edit|manage|rename|save|upload|mail|logon|.*auth).*">
Require valid-user
</FilesMatch>
As I said above, I preffer HTTP
AuthName 'Enter your Username and Password:'
AuthType Basic
AuthUserFile /var/www/myweb/.htpasswd
Require valid-user
<FilesMatch "(attach|edit|manage|rename|save|upload|mail|logon|.*auth).*">
RewriteEngine on
RewriteCond %{HTTPS} !=on # If the connection is not HTTPS then apply the next Rewrite rule
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
Require valid-user
</FilesMatch>
After some hours spent with configuration and RTFM, I found a hack which is maybe not the ideal solution, but it's pretty good and it's working.
There is an Apache configuration command which can be used in .htaccess as well. If the "SSLRequireSSL" command is specified for a directory or a file and it's accessed with a connection which is not SSL secured (HTTPS), it will generate a 403 error code and an error message will be sent to a web broswer. The "SSLRequireSSL" command has higher priority then the authentication itself, so it will generate this error code always when the connection is not SSL secured.
So far it looks good, the problem is that a user will just see an error page and he is still not automatically redirected to the HTTPS connection. A workaround is a bit tricky. You can define your own custom error documents, which are displayed when an error code is thrown. You have definitely seen these fancy custom error documents for the 404 - Page Not Found error code.
So the workaround was to use the ErrorDocument property in the .htaccess file. A custom page defined in ErrorDocument is called when the error code is thrown. The page itself gets information about the original request, so you can write a custom error page in some server side scripting language and generate some "special" events. I created a perl cgi script (there was no PHP support for that site) which redirects a browser to the HTTPS site which is exactly what I wanted in the beginning. So here is the .htaccess file:
AuthName 'Enter your Username and Password:'
AuthType Basic
AuthUserFile /var/www/myweb/.htpasswd
Require valid-user
<FilesMatch "(attach|edit|manage|rename|save|upload|mail|logon|.*auth).*">
SSLRequireSSL
ErrorDocument 403 /bin/move.pl
Require valid-user
</FilesMatch>
and the move.pl file goes here:
#!/usr/bin/perl -T
use CGI qw(:standard);
$path = "https://$ENV{'SERVER_NAME'}$ENV{'REQUEST_URI'}";
if ( $ENV{'SERVER_PORT'} == 80) {
print "Status: 302 Moved\n";
print "Location: $path\n\n";
}
else {
print "Content-type: text/html\n\n";
print "How did you get here???";
}
With this combination, if the files defined in the FilesMatch directive are accessed with the HTTP only connection, an 403 error code is thrown by the SSLRequireSSL, which is handled by the ErrorDocument property. The /bin/move.pl cgi perl script is called which will then redirect a web browser to the HTTPS site. Furthermore, if the files defined in the FilesMatch directive are accessed with the HTTPS connection, a user is requested to authenticate himself with his username/password.
It's maybe not the best solution, but it's working, and it's enough to have .htaccess definitions enabled.
Enjoy!
jozjan, this is very useful. Thanks for putting it together!
ReplyDeleteIndeed - i too have searched for quite a lot of solutions to the "Auth before SSL"-problem - this one is the far most elegant.
ReplyDeleteIt even gives a possibility to customize what and how to redirect in the perl script.
Thanks a lot :)
Thank you very much for this hint, jozjan. Fortunately your blog is no. 1 when asking Google for "apache 2.0 first redirect then auth".
ReplyDeleteCheers from Poland.
If you have access to your apache configuration files then try using RewriteCond/RewriteRules in server context instead of directory context. This should also solve the problem.
ReplyDeleteNow this is something I could try to do. Thanks for sharing information.
ReplyDeleteI have searched for hours and hours for a solution to this problem!! THANKS!!!
ReplyDeleteAnd here is another spin on the solution:
ReplyDeletehttp://www.askapache.com/htaccess/apache-ssl-in-htaccess-examples.html
Thanks.
ReplyDeleteI'm enyoing it already, great tip.
I found this solution: http://stackoverflow.com/questions/2220167/do-http-authentication-over-https-with-url-rewriting
ReplyDeleteSeems to work for me. "SetEnvIf HTTPS on prompt_auth" is handled before auth, meaning the rewrite is applied on HTTP access (redirecting to HTTP without auth). Auth is then applied normally on HTTPS access.
I get around it this way. Just allow Non-SSL since it will be redirected then require auth once on SSL...
ReplyDeleteSetEnvIf %{SERVER_PORT} ^80$ IS_NON_SSL
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
AuthUserFile /var/www/myweb/.htpasswd
AuthName "Enter your Username and Password:"
AuthType Basic
require valid-user
Allow from env=IS_NON_SSL
@Ben - it works, but your solution will redirect the whole traffic to HTTPS. Even the one that is not authenticated.
ReplyDelete