Re-Authentication for expired Tokens
Contents
Background
The introduction of CSRF Protection migitates the risk of Cross Site Request Forgery (CSRF) attacks on Geeklog sites. At the same time, however, it inconveniences Admin users, since an expired token will invalidate a request and may lead to data loss.
Re-authentication (as of Geeklog 1.7.0) attempts to address this problem.
How it works
Following the recommendations for the use of security tokens, a call to SEC_checkToken() will check the token for validity. Previously, that function simply returned false when the token was not valid.
From now on, when SEC_checkToken() detects an invalid token, it will instead display a re-authentication form. This form explains what just happened and asks the user to enter their username and password. If the user provides the correct information, the previous request is recreated and sent again automatically. As a result, the last changes are not lost and the request is processed as if a valid token had been found.
Recreating Requests
The re-authentication form contains the information from the last request (GET or POST) in hidden fields, i.e. the entire $_GET and $_POST arrays, as well as the information from $_FILES, if files were uploaded with the failed request (the uploaded files themselves are temporarily stored in the site's 'data' directory).
On proper authentication, the GET or POST request is recreated from this information. A new security token is added and the request is sent to the original URL again. So from the target script's point of view, it looks like a regular request with a valid token came through.
In other words: The re-authentication and recreation of the request is entirely transparent. For scripts that already use CSRF protection, no code changes are required.
Uploaded Files
Files uploaded through the original request (the one with the expired token) are rescued and moved to the site's 'data' directory. This is necessary, as the files would automatically be deleted by PHP otherwise. As a result, the rescued files can no longer be moved with move_uploaded_file() since that function only works on files that were uploaded with the last POST request.
If you are using Geeklog's upload class (upload.class.php), this is handled automatically for you.
If you are using your own upload handling, then check the $_FILES array for an '_gl_data_dir' entry, e.g. $_FILES['file1']['_gl_data_dir']. If such an entry is present and not false, then use rename() instead of move_uploaded_file() to move the uploaded file to its destination. The 'tmp_name' entry will already contain the correct path (for the 'data' directory), so you don't need to (and shouldn't) bother with its actual location.
Using SEC_checkToken
It is important that you only call SEC_checkToken() when you expect a token. In other words, do NOT do this:
$token_valid = SEC_checkToken(); // don't do this! switch ($_POST['mode']) { case 'delete': if ($token_valid) ... break; case 'save': if ($token_valid) ... break; case 'edit': // token not checked here display_editor(); break; }
In the above example, a token check is required for the 'delete' and 'save' cases, but not for 'edit'. However, SEC_checkToken() is also being called in the 'edit' case. In Geeklog versions 1.5.0 - 1.6.1, this would only cause SEC_checkToken() to return false for this case - but since the return value isn't checked, this would do no harm. From now on, however, this case would trigger the re-authentication form to be displayed!
So the proper use of SEC_checkToken() would be:
switch ($_POST['mode']) { case 'delete': if (SEC_checkToken()) ... break; case 'save': if (SEC_checkToken()) ... break; case 'edit': // token not checked here display_editor(); break; }
Known Issues
- Re-authentication does not work for users that authenticate against OpenID (it does, however, work with other remote authentication modules like LDAP and LiveJournal).
- When using a server running as localhost on Mac OS X, the recreated request may fail due to the current IP address being ::1 in the session but 127.0.0.1 in the recreated request.
Solution: Use the machine's Bonjour name (xxx.local) instead of localhost.
Also see: Troubleshooting Authentication Problems