Two-factor authentication also known as 2FA, adds an extra step to a basic authentication procedure. Without 2FA, a user only enters username and password. In this case, the password is the single factor of authentication. With 2FA an additional authentication mechanism is used, that is preferably performed out-of-band.
Google Authenticator is an application that implements two-factor authentication services using the Time-based One-time Password Algorithm (TOTP).
Apache provides basic authentication mechanism with mod_auth_basic or mod_auth_digest. For more secure applications, it is often required to have an additional layer of authentication. This repository provides necessary code and instructions to add two-factor authentication to basic Apache authentication. This method is transparent to underlying applications so it can be used for any Apache served web site whether it is static, dynamic (PHP, Django, Flask etc.) or pre-packaged (Wiki, CRM, CMS etc.).
Specific instructions are provided below for configuring two-factor authentication with mod_auth_digest, but the same code and approach can be used with different Apache authentication mechanisms with slight modifications. Similarly, it is also possible to use the same code with slight modifications and the same approach to provide 2FA based on HMAC-based one-time password (HOTP) algorithm.
Clone the repository and install dependencies:
$ git clone https://github.com/itemir/apache_2fa
$ cd apache_2fa
$ pip install -r requirements # Might require sudo
Create a directory for storing states:
$ mkdir state
Adjust permissions to allow access only to Apache (replace www-data with the user id of Apache process as needed):
$ sudo chown www-data:www-data state
$ sudo chown www-data:www-data tokens.json
$ sudo chmod 750 state
$ sudo chmod 640 tokens.json
Enable mod_rewrite, mod_auth_digest and mod_cgid if not already enabled (you will need to restart Apache):
$ sudo a2enmod rewrite
$ sudo a2enmod auth_digest
$ sudo a2enmod cgid
$ sudo service apache2 restart
Add the following configuration to Apache configuration under appropriate VirtualHost:
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/auth/
RewriteCond %{HTTP_COOKIE} !^.*2FA_Auth=([a-zA-Z0-9]+)
RewriteRule ^(.*)$ /auth/auth?$1?%{QUERY_STRING} [L,R=302]
RewriteCond %{REQUEST_URI} !^/auth/
RewriteCond %{HTTP_COOKIE} ^.*2FA_Auth=([a-zA-Z0-9]+)
RewriteCond <path to apache_2fa>/state/%1 !-f
RewriteRule ^(.*)$ /auth/auth?$1?%{QUERY_STRING} [L,R=302]
ScriptAlias /auth/ <path to_apache 2fa (note the trailing slash)>/
<Directory <path to apache_2fa>>
AuthType Digest
AuthName "yourdomain.com"
AuthDigestDomain /
AuthDigestProvider file
AuthUserFile <path to apache_2fa>/apache_credentials
Require valid-user
</Directory>
<Directory <path to protected directory>>
AuthType Digest
AuthName "yourdomain.com"
AuthDigestDomain /
AuthDigestProvider file
AuthUserFile <path to apache_2fa>/apache_credentials
Require valid-user
</Directory>
Replace path to apache_2fa with the full path of cloned repository, path to protected directory with the actual path of the site you are trying to protect. If you change yourdomain.com make sure to make corresponding changes in apache_credentials
file. Pay special attention to trailing slashes where present. You may be able to combine two Directory configurations into one depending on your directory structure, just make sure both paths are covered by the same auhentication mechanism.
NOTE: This configuration is for https. For a setup like this, using http is not recommended. However, if you want to test it with http you need to make changes to the auth script and comment out the following two lines:
cookie['2FA_Auth']['secure'] = True
cookie['2FA_Auth']['httponly'] = True
Test the configuration and reload Apache if no errors. If there are errors, verify steps above and make sure if you have all necessary modules enabled.
$ sudo apachectl configtest
$ sudo service apache2 reload
If all went well, you can now test the application. Go to a protected web page. You should be prompted to enter a username and password. Use test_user / test_password. You should now be prompted for an Authentication Token. If test_user authentication fails, change the password with the following command:
$ htdigest apache_credentials yourdomain.com test_user
In order to obtain Authentication Token, download Google Authenticator for iOS or Android and create a profile by scanning the following QR code:
Alternatively, you can use the R24UZEAOIUAZHY62IEB5XJOVKT6PYGOYNDKVVU3KS4DZCYOOSIF6M6TFYEWVZAOX secret key. There are many other applications that provide the same capability with additional features, you can basically use any application that supports TOTP. Once you define a profile, Google Authenticator will create a token that you can use in this form.
If the test is successful, edit apache_credentials
and tokens.json
files and remove test_user.
You can create new users with the following command:
$ htdigest apache_credentials yourdomain.com <username>
You can create corresponding OTP secrets with the following command:
$ ./create_token.py <username> # May require sudo
This will create a new token in tokens.json
file and create <username>.png
file with the QR code you can scan with your authenticator app.
For every successful authentication session, a new file will be created under /state directory. This file is relevant until the cookie expires (default value is 6 hours for expiration). You will eventually want to clean stale entries in this directory. state_clean utility that is included the repository can be used to delete state files that are older than 6 hours. You can call it from a cron job every hour which also prevents users from manually increasing the expiration timer of cookies to delay token re-authorization:
0 * * * * <path to apache_2fa>/state_clean