Joachim Breitner's Homepage
Protecting static content with mod_rewrite
Since fourteen years, I have been photographing digitally and putting the pictures on my webpage. Back then, online privacy was not a big deal, but things have changed, and I had to at least mildly protect the innocent. In particular, I wanted to prevent search engines from accessing some of my pictures.
As I did not want my friends and family having to create an account and remember a password, I set up an OpenID based scheme five years ago. This way, they could use any of their OpenID enabled account, e.g. their Google Mail account, to log in, without disclosing any data to me. As my photo album consists of just static files, I created two copies on the server: the real one with everything, and a bunch of symbolic links representing the publicly visible parts. I then used mod_auth_openid
to prevent access to the protected files, unless the users logged in. I never got around of actually limiting who could log in, so strangers were still able to see all photos, but at least search engine spiders were locked out.
But, very unfortunately, OpenID did never really catch on, Google even stopped being a provider, and other promising decentralized authentication schemes like Mozilla Persona are also phased out. So I needed an alternative.
A very simply scheme would be a single password that my friends and family can get from me and that unlocks the pictures. I could have done that using HTTP Auth, but that is not very user-friendly, and the login does not persist (at least not without the help of the browser). Instead, I wanted something that involves a simple HTTP form. But I also wanted to avoid server-side programming, for performance and security reasons. I love serving static files whenever it is feasible.
Then I found that mod_rewrite
, Apache’s all-around-tool for URL rewriting and request mangling, supports reading and writing cookies! So I came up with a scheme that implements the whole login logic in the Apache server configuration. I’d like to describe this setup here, in case someone finds it inspiring.
I created a login.html
with a simple HTML form:
<form method="GET" action="/bilder/login.html">
<div style="text-align:center">
<input name="password" placeholder="Password" />
<button type="submit">Sign-In</button>
</div>
</form>
It sends the user to the same page again, putting the password into the query string, hence the method="GET"
– mod_rewrite
unfortunately cannot read the parameters of a POST
request.
The Apache configuration is as follows:
public "dbm:/var/www/joachim-breitner.de/bilder/publicfiles.dbm"
RewriteMap<Directory /var/www/joachim-breitner.de/bilder>
RewriteEngine On
# This is a GET request, trying to set a password.
%{QUERY_STRING} password=correcthorsebatterystaple
RewriteCond ^login.html /bilder/loggedin.html [L,R,QSD,CO=bilderhp:correcthorsebatterystaple:www.joachim-breitner.de:2000000:/bilder]
RewriteRule
# This is a GET request, trying to set a wrong password.
%{QUERY_STRING} password=
RewriteCond ^login.html /bilder/notloggedin.html [L,R,QSD]
RewriteRule
# No point in loggin in if there is already the right password
%{HTTP:Cookie} bilderhp=correcthorsebatterystaple
RewriteCond ^login.html /bilder/loggedin.html [L,R]
RewriteRule
# If protected file is requested, check for cookie.
# If no cookie present, redirect pictures to replacement picture
%{HTTP:Cookie} !bilderhp=correcthorsebatterystaple
RewriteCond ${public:$0|private} private
RewriteCond ^.*\.(png|jpg)$ /bilder/pleaselogin.png [L]
RewriteRule
%{HTTP:Cookie} !bilderhp=correcthorsebatterystaple
RewriteCond ${public:$0|private} private
RewriteCond ^.+$ /bilder/login.html [L,R]
RewriteRule</Directory>
The publicfiles.dbm
file is generated from a text file with lines like
login.html.en 1
login.html.de 1
pleaselogin.png 1
thumbs/20030920165701_thumb.jpg 1
thumbs/20080813225123_thumb.jpg 1
...
using
/usr/sbin/httxt2dbm -i publicfiles.txt -o publicfiles.dbm
and whitelists all files that are visible without login. Make sure it contains the login page, otherwise you’ll get a redirect circle.
The other directives in the above configure fulfill these tasks:
- If the password (
correcthorsebatterystaple
) is in the query string, the server redirects the user to a logged-in-page that tells him that the login was successful and instructs him to reload the photo album. It also sets a cookie that will last very long – after all, I want this to be convenient for my visitors. The query string parsing is not very strict (e.g. a password ofcorrecthorsebatterystaplexkcdrules
would also work), but that’s ok. - The next request detects an attempt to set a password. It must be wrong (otherwise the first rule would have matched), so we redirect the user to a variant of the login page that tells him so.
- If the user tries to access the login page with a valid cookie, just log him in.
- The next two rules implement the actual protection. If there no valid cookie and the accessed file is not whitelisted, then access is forbidden. For requests to images, we do an internal redirect to a placeholder image, while for everything else we redirect the user to the login page.
And that’s it! No resource-hogging web frameworks, not security-dubious scripting languages, and a dead-simple way to authenticate.
Oh, and if you believe you know me well enough to be allowed to see all photos: The real password is not correcthorsebatterystaple
; just ask me what it is.
Have something to say? You can post a comment by sending an e-Mail to me at <mail@joachim-breitner.de>, and I will include it here.