February 24, 2008

.htaccess - Redirect to SSL (HTTPS) before Basic Authentication

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 HTTPS connection when confidential information are transfered over an IP network (in this case username and password). So the thing I wanted to do, was first to redirect a web browser to the HTTPS site and just then request the credentials. This was a point where I've got into a botleneck. If an authentication procedure is defined for a directory or a file, the authentication has higher priority then a redir command (mod_rewrite - redir). So the user is first authenticated, then moved to the HTTPS site and then authenticated once again. The problem is that the first athetincation is transferred in HTTP cleartext which is definitelly not secure.

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!

11 comments:

  1. jozjan, this is very useful. Thanks for putting it together!

    ReplyDelete
  2. Indeed - i too have searched for quite a lot of solutions to the "Auth before SSL"-problem - this one is the far most elegant.

    It even gives a possibility to customize what and how to redirect in the perl script.

    Thanks a lot :)

    ReplyDelete
  3. 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".

    Cheers from Poland.

    ReplyDelete
  4. 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.

    ReplyDelete
  5. Now this is something I could try to do. Thanks for sharing information.

    ReplyDelete
  6. I have searched for hours and hours for a solution to this problem!! THANKS!!!

    ReplyDelete
  7. And here is another spin on the solution:
    http://www.askapache.com/htaccess/apache-ssl-in-htaccess-examples.html

    ReplyDelete
  8. Thanks.
    I'm enyoing it already, great tip.

    ReplyDelete
  9. I found this solution: http://stackoverflow.com/questions/2220167/do-http-authentication-over-https-with-url-rewriting

    Seems 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.

    ReplyDelete
  10. I get around it this way. Just allow Non-SSL since it will be redirected then require auth once on SSL...

    SetEnvIf %{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

    ReplyDelete
  11. @Ben - it works, but your solution will redirect the whole traffic to HTTPS. Even the one that is not authenticated.

    ReplyDelete