Apache and form based login

Intro

Apache can protect files and folders by username and password. Basic authentication provided by mod_auth_basic is commonly used for this, although a user cannot log out from basic authentication. You have to close your webbrowser in order to get rid of cached credentials. mod_auth_form allows you to show a login page to the users, and to give them to ability to log out from the secured webpage.

As mod_auth_form (as well as mod_auth_basic) transfer usernames and passwords in cleartext, you should use them along with SSL protected websites, i.e. mod_ssl.

Setup

  1. On FreeBSD, add these modules to /usr/local/etc/apache24/httpd.conf:
    LoadModule auth_form_module libexec/apache24/mod_auth_form.so
    LoadModule request_module libexec/apache24/mod_request.so
    LoadModule session_module libexec/apache24/mod_session.so
    LoadModule session_cookie_module libexec/apache24/mod_session_cookie.so
    LoadModule session_crypto_module libexec/apache24/mod_session_crypto.so
    
    On Ubuntu:
    $ sudo a2enmod auth_form request session session_cookie session_crypto
    
  2. Add these lines to your virtual host in order to protect the URL path /secure by a form based login:
    <Location /secure>
    	AuthName "Secure area"
    	AuthType form
    	AuthFormProvider file
    	AuthUserFile "/var/www/etc/htpasswd"
    	AuthFormLoginRequiredLocation "/login.html"
    
    	Require valid-user
    
    	Session on
    	SessionCookieName the_session path=/
    	SessionMaxAge 3600
    	SessionExpiryUpdateInterval 10
    	SessionCryptoPassphrase topsecret
    </Location>
    <Location /login.aspx>
    	AuthName "Secure area"
    	AuthType form
    	AuthFormProvider file
    	AuthUserFile "/var/www/etc/htpasswd"
    	AuthFormLoginRequiredLocation "/login.html?error"
    	AuthFormLoginSuccessLocation "/secure"
    
    	SetHandler form-login-handler
    	AuthFormUsername my_username
    	AuthFormPassword my_password
    
    	Session on
    	SessionCookieName the_session path=/
    	SessionMaxAge 3600
    	SessionExpiryUpdateInterval 10
    	SessionCryptoPassphrase topsecret
    </Location>
    <Location /logout.aspx>
    	AuthName "Secure area"
    	AuthType form
    	AuthFormProvider file
    	AuthUserFile "/var/www/etc/htpasswd"
    	AuthFormLoginRequiredLocation "/login.html?error"
    	AuthFormLogoutLocation "/login.html?loggedout"
    
    	SetHandler form-logout-handler
    
    	Session on
    	SessionCookieName the_session path=/
    	SessionMaxAge 1
    	SessionExpiryUpdateInterval 0
    	SessionCryptoPassphrase topsecret
    </Location>
    
    Please note:
  3. Restart Apache. On FreeBSD:
    # service apache24 restart
    
    On Ubuntu:
    $ sudo systemctl restart apache2
    
  4. Create the username and password file if not already present:
    $ sudo htpasswd -c /var/www/etc/htpasswd myfirstuser
    
    Optionally, add additional users to it:
    $ sudo htpasswd /var/www/etc/htpasswd myseconduser
    
  5. Create the login mask. It uses JavaScript to handle different stages of the login process and to display related messages:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
    <html>
    <head>
    <title>Login</title>
    <style>
    body {
      font-family:sans-serif;
    }
    #msg {
      color:red;
    }
    </style>
    </head>
    <body>
    <form method="POST" action="/login.aspx">
    <div id="msg"></div>
    <p>Username: <input type="text" name="my_username"></p>
    <p>Password: <input type="password" name="my_password"></p>
    <input type="Submit">
    </form>
    <script>
    var msg = "";
    var search = window.location.search;
    if (search == "?error") {
      msg = "Invalid username and/or password";
    } else if (search == "?loggedout") {
      msg = "Successfully logged out";
    } else if (search == "?banned") {
      msg = "Temporarily banned due to too many login attempts";
    }
    document.getElementById("msg").innerText = msg;
    </script>
    </body>
    </html>
    
    Of course you can use distinct html pages instead of one big page.
  6. You can combine this with Fail2ban if you monitor the webserver's error logfile using this failregex:
    failregex = client .+ user .+ not found
    
    Then, add this to your webserver configuration:
    ...
    LoadModule rewrite_module libexec/apache24/mod_rewrite.so
    ...
    RewriteEngine on
    RewriteMap fail2ban-website "dbm=db:/var/run/fail2ban/website-apache.db"
    RewriteCond ${fail2ban-website:%{REMOTE_ADDR}|n} =y
    RewriteCond %{REQUEST_URI}?%{QUERY_STRING} !/login.html\?banned
    RewriteRule .* /login.html?banned [R]
    
    The R flag of the above RewriteRule sends your webbrowser an external redirect in order to add the query string banned.