4

For security purposes, I need to add a form_token to the login form. However, when (in hook_form_alter) I use the same code Drupal uses in form.inc for other forms, the validation fails because the token calculated during validation is different from the one calculated originally.

The code I'm using is:

if (!isset($form['#token'])) {
  $form['#token'] = $form_id;
  $form['form_token'] = array(
    '#id' => drupal_html_id('edit-' . $form_id . '-form-token'),
    '#type' => 'token',
    '#default_value' => drupal_get_token($form['#token']),
    // Form processing and validation requires this value, so ensure the
    // submitted form value appears literally, regardless of custom #tree
    // and #parents being set elsewhere.
    '#parents' => array('form_token'),
  );
}

I'm calling the form with drupal_render(drupal_get_form('user_login')). I'm aware that Drupal is not using the form_token on the login page because it is often cached, but I have disabled the cache for that page in hook_init with:

$GLOBALS['conf']['cache'] = FALSE 

How can I add and validate a form_token in the login form? I'm using Drupal 7.

Some extra info as to why I need this. The request to secure login forms against CSRF came up during a security scan and although I do not agree with the explanation for the request (easier to setup a brute force attack, despite auto blocking logins in such cases), there is a good explanation on stackoverflow as to why this still can be a good idea: Do login forms need tokens against CSRF attacks?

  • Usually the forms are built with token only for authenticated users. – awm Feb 26 '14 at 23:10
4

I ended up (as suggested) adding a token to all unsecured forms with hook_form_alter.

Seemed efficient to use drupal_get_token() for this, but that did not work because the session ID (used by drupal_get_token() to generate a token) alters when an (anonymous) user logs in.

I therefore stored the first session id (when displaying the login page) in $_SESSION so I could retrieve it when validating. But putting something in $_SESSION apparently prevents Drupal from regenerating the session_id. And now that the session id is preserved during login, drupal_valid_token() also works as expected so I did not have to write a special token validation function.

This is the code I ended up with:

function MY_MODULE_form_alter(&$form, $form_state, $form_id) {
    // add a custom csrf token for forms that do not have one
    if ($!isset($form['#token'])) {
        $form['anon_token'] = array(
            '#type' => 'token',
            '#default_value' => drupal_get_token()
        );

        $form['#validate'][] = 'MY_MODULE_validate_anon_token';

        // store current session id
        // touching $_SESSION alone seems to preserve the session id after login
        $sess_id = session_id();
        if (isset($_SESSION))
            $_SESSION['anon_session_id'] = $sess_id;
        else
            $_SESSION = array('anon_session_id' => $sess_id);
    }
}

function MY_MODULE_validate_anon_token($form, &$form_state) {
    $token = '';
    if (isset($form_state['values']['anon_token']))
        $token = $form_state['values']['anon_token'];

    if (!drupal_valid_token($token)) {
        // not a valid token!
        $path = current_path();
        $query = drupal_get_query_parameters();
        $url = url($path, array('query' => $query));

        // Setting this error will cause the form to fail validation.
        form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
    }
}
  • vote up,this is good solution based on drupal form token policy, you simulate drupal form token policy with yours. – Yusef Jul 26 '16 at 13:54
  • This works, managed to stop CSRF with this. – Johann Combrink Jun 15 '18 at 11:42
2
+50

Usually the forms are built with token only for authenticated users. Tokens are session-bound and forms displayed to anonymous users are cached in cache_form table, and a token cannot be assigned for them. If you need a different token for a different purpose, then you can add one by yourself without using the #token key. Try using $form['my_special_token'] = some_token. Then add a validation function to check if the special token is valid.

In any case, your question needs more information about the "security reason" for adding a token. Perhaps then we can have a better idea of what to be done.

  • This is close to what I ended up doing, although I'm using drupal_get_token(). I will add an answer with my code below. – Whiskey Mar 5 '14 at 8:19
  • I also added some extra info in the question with respect to the "security reason". – Whiskey Mar 5 '14 at 8:40
2

I also had the same use case come up in a previous project, where we needed form tokens attached to forms for anonymous users to ensure they were legitimate submissions from our site.

I used the following hook_form_alter code to add a token:

  if(!isset($form['#token'])) {
    $form['#token'] = $form_id;
    $form['form_token'] = array(
      '#id' => drupal_html_id('edit-' . $form_id . '-form-token'),
      '#type' => 'token',
      '#default_value' => mymodule_generate_token($form['#token']),
    );
  }

Then I used this custom function to generate the token for me:

function mymodule_generate_token($form_id){
  $secret = 'my-unique-string'; //could be todays date, whatever you want to make it.
  return drupal_hmac_base64($form_id, $secret. drupal_get_private_key() . drupal_get_hash_salt());
}

Then I also wrote a utility function which checks to see if the token coming from an anoymous form is valid:

function mymodule_check_valid_token($form_token, $form_id){
  return ($form_token == mymodule_generate_token($form_id));
}

Hope this helps

  • Thanks, this is very similar to what arsene lupin suggests and his answer was first, so I awarded him the bounty. But I upvoted yours for the code sample. – Whiskey Mar 5 '14 at 8:20
  • no problem, glad I could help out :) – Alex Kirsten Mar 5 '14 at 12:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.