diff --git a/plugins/kolab_2fa/kolab_2fa.php b/plugins/kolab_2fa/kolab_2fa.php
index 8bef323e..6bc206ac 100644
--- a/plugins/kolab_2fa/kolab_2fa.php
+++ b/plugins/kolab_2fa/kolab_2fa.php
@@ -1,842 +1,839 @@
 <?php
 
 /**
  * Kolab 2-Factor-Authentication plugin
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 class kolab_2fa extends rcube_plugin
 {
     public $task = '(login|settings)';
 
     protected $login_verified = null;
     protected $login_factors = [];
     protected $drivers = [];
     protected $storage;
 
     /**
      * Plugin init
      */
     public function init()
     {
         $this->load_config();
         $this->add_hook('startup', [$this, 'startup']);
     }
 
     /**
      * Startup hook
      */
     public function startup($args)
     {
         $rcmail = rcmail::get_instance();
 
         // register library namespace to autoloader
         $loader = include(INSTALL_PATH . 'vendor/autoload.php');
         $loader->set('Kolab2FA', [$this->home . '/lib']);
 
         if ($args['task'] === 'login' && $this->api->output) {
             $this->add_texts('localization/', false);
             $this->add_hook('authenticate', [$this, 'authenticate']);
 
             // process 2nd factor auth step after regular login
             if ($args['action'] === 'plugin.kolab-2fa-login' /* || !empty($_SESSION['kolab_2fa_factors']) */) {
                 return $this->login_verify($args);
             }
         } elseif ($args['task'] === 'settings') {
             $this->add_texts('localization/', !$this->api->output->ajax_call);
             $this->add_hook('settings_actions', [$this, 'settings_actions']);
             $this->register_action('plugin.kolab-2fa', [$this, 'settings_view']);
             $this->register_action('plugin.kolab-2fa-data', [$this, 'settings_data']);
             $this->register_action('plugin.kolab-2fa-save', [$this, 'settings_save']);
             $this->register_action('plugin.kolab-2fa-verify', [$this, 'settings_verify']);
         }
 
         return $args;
     }
 
     /**
      * Handler for 'authenticate' plugin hook.
      *
      * ATTENTION: needs to be called *after* kolab_auth::authenticate()
      */
     public function authenticate($args)
     {
         // nothing to be done for me
         if ($args['abort'] || $this->login_verified !== null) {
             return $args;
         }
 
         $rcmail = rcmail::get_instance();
 
         // parse $host URL
         $a_host = parse_url($args['host']);
         $hostname = $_SESSION['hostname'] = $a_host['host'] ?: $args['host'];
         $username = !empty($_SESSION['kolab_auth_admin']) ? $_SESSION['kolab_auth_admin'] : $args['user'];
 
         // Check if we need to add/force domain to username
         $username_domain = $rcmail->config->get('username_domain');
         if (!empty($username_domain)) {
             $domain = '';
             if (is_array($username_domain)) {
                 if (!empty($username_domain[$hostname])) {
                     $domain = $username_domain[$hostname];
                 }
             } else {
                 $domain = $username_domain;
             }
 
             if ($domain = rcube_utils::parse_host((string) $domain, $hostname)) {
                 $pos = strpos($username, '@');
 
                 // force configured domains
                 if ($pos !== false && $rcmail->config->get('username_domain_forced')) {
                     $username = substr($username, 0, $pos) . '@' . $domain;
                 }
                 // just add domain if not specified
                 elseif ($pos === false) {
                     $username .= '@' . $domain;
                 }
             }
         }
 
         // Convert username to lowercase. Copied from rcmail::login()
         $login_lc = $rcmail->config->get('login_lc', 2);
         if ($login_lc) {
             if ($login_lc == 2 || $login_lc === true) {
                 $username = mb_strtolower($username);
             } elseif (strpos($username, '@')) {
                 // lowercase domain name
                 [$local, $domain] = explode('@', $username);
                 $username = $local . '@' . mb_strtolower($domain);
             }
         }
 
         // 2a. let plugins provide the list of active authentication factors
         $lookup = $rcmail->plugins->exec_hook('kolab_2fa_lookup', [
             'user'    => $username,
             'host'    => $hostname,
             'factors' => null,
             'check'   => $rcmail->config->get('kolab_2fa_check', true),
         ]);
 
         $factors = [];
         if (isset($lookup['factors'])) {
             $factors = (array)$lookup['factors'];
         }
         // 2b. check storage if this user has 2FA enabled
         elseif ($lookup['check'] !== false && ($storage = $this->get_storage($username))) {
             $factors = (array)$storage->enumerate();
         }
 
         if (count($factors) > 0) {
             $args['abort'] = true;
             $factors = array_unique($factors);
 
             // 3. flag session for 2nd factor verification
             $_SESSION['kolab_2fa_time']    = time();
             $_SESSION['kolab_2fa_nonce']   = bin2hex(openssl_random_pseudo_bytes(32));
             $_SESSION['kolab_2fa_factors'] = $factors;
 
             $_SESSION['username'] = $username;
             $_SESSION['host']     = $args['host'];
             $_SESSION['password'] = $rcmail->encrypt($args['pass']);
 
             // 4. render to 2nd auth step
             $this->login_step($factors);
         }
 
         return $args;
     }
 
     /**
      * Handler for the additional login step requesting the 2FA verification code
      */
     public function login_step($factors)
     {
         // replace handler for login form
         $this->login_factors = array_values($factors);
         $this->api->output->add_handler('loginform', [$this, 'auth_form']);
 
         // focus the code input field on load
         $this->api->output->add_script('$("input.kolab2facode").first().select();', 'docready');
 
         $this->api->output->send('login');
     }
 
     /**
      * Process the 2nd factor code verification form submission
      */
     public function login_verify($args)
     {
         $this->login_verified = false;
 
         $rcmail = rcmail::get_instance();
 
         $time     = $_SESSION['kolab_2fa_time'];
         $nonce    = $_SESSION['kolab_2fa_nonce'];
         $factors  = (array) $_SESSION['kolab_2fa_factors'];
         $expired  = $time < time() - $rcmail->config->get('kolab_2fa_timeout', 120);
         $username = !empty($_SESSION['kolab_auth_admin']) ? $_SESSION['kolab_auth_admin'] : $_SESSION['username'];
 
         if (!empty($factors) && !empty($nonce) && !$expired) {
             // TODO: check signature
 
             // try to verify each configured factor
             foreach ($factors as $factor) {
                 [$method] = explode(':', $factor, 2);
 
                 // verify the submitted code
                 $code = rcube_utils::get_input_value("_${nonce}_${method}", rcube_utils::INPUT_POST);
                 $this->login_verified = $this->verify_factor_auth($factor, $code, $username);
 
                 // accept first successful method
                 if ($this->login_verified) {
                     break;
                 }
             }
         }
 
         if ($this->login_verified) {
             // restore POST data from session
             $_POST['_user'] = $_SESSION['username'];
             $_POST['_host'] = $_SESSION['host'];
             $_POST['_pass'] = $rcmail->decrypt($_SESSION['password']);
 
             if (!empty($_SESSION['kolab_auth_admin'])) {
                 $_POST['_user']    = $_SESSION['kolab_auth_admin'];
                 $_POST['_loginas'] = $_SESSION['username'];
             }
         }
 
         // proceed with regular login ...
         $args['action'] = 'login';
 
         // session data will be reset in index.php thus additional
         // auth attempts with intercepted data will be rejected
         // $rcmail->kill_session();
 
         // we can't display any custom error messages on failed login
         // but that's actually desired to expose as little information as possible
 
         return $args;
     }
 
     /**
      * Helper method to verify the given method/code tuple
      */
     protected function verify_factor_auth($method, $code, $username)
     {
-        if (strlen($code) && ($driver = $this->get_driver($method))) {
-            // set properties from login
-            $driver->username = $username;
-
+        if (strlen($code) && ($driver = $this->get_driver($method, $username))) {
             try {
                 // verify the submitted code
                 return $driver->verify($code, $_SESSION['kolab_2fa_time']);
             } catch (Exception $e) {
                 rcube::raise_error($e, true, false);
             }
         }
 
         return false;
     }
 
     /**
      * Render 2nd factor authentication form in place of the regular login form
      */
     public function auth_form($attrib = [])
     {
         $form_name  = !empty($attrib['form']) ? $attrib['form'] : 'form';
         $nonce = $_SESSION['kolab_2fa_nonce'];
 
         $methods = array_unique(array_map(
             function ($factor) {
                 [$method, $id] = explode(':', $factor);
                 return $method;
             },
             $this->login_factors
         ));
 
         // forward these values as the regular login screen would submit them
         $input_task   = new html_hiddenfield(['name' => '_task', 'value' => 'login']);
         $input_action = new html_hiddenfield(['name' => '_action', 'value' => 'plugin.kolab-2fa-login']);
         $input_tzone  = new html_hiddenfield(['name' => '_timezone', 'id' => 'rcmlogintz', 'value' => rcube_utils::get_input_value('_timezone', rcube_utils::INPUT_POST)]);
         $input_url    = new html_hiddenfield(['name' => '_url', 'id' => 'rcmloginurl', 'value' => rcube_utils::get_input_value('_url', rcube_utils::INPUT_POST)]);
 
         // create HTML table with two cols
         $table = new html_table(['cols' => 2]);
         $required = count($methods) > 1 ? null : 'required';
         $row = 0;
 
         // render input for each configured auth method
         foreach ($methods as $i => $method) {
             if ($row++ > 0) {
                 $table->add(
                     ['colspan' => 2, 'class' => 'title hint', 'style' => 'text-align:center'],
                     $this->gettext('or')
                 );
             }
 
             $field_id = "rcmlogin2fa$method";
             $input_code = new html_inputfield([
                     'name'         => "_${nonce}_${method}",
                     'class'        => 'kolab2facode',
                     'id'           => $field_id,
                     'required'     => $required,
                     'autocomplete' => 'off',
                     'data-icon'    => 'key', // for Elastic
                 ] + $attrib);
             $table->add('title', html::label($field_id, html::quote($this->gettext($method))));
             $table->add('input', $input_code->show(''));
         }
 
         $out  = $input_task->show();
         $out .= $input_action->show();
         $out .= $input_tzone->show();
         $out .= $input_url->show();
         $out .= $table->show();
 
         // add submit button
         if (rcube_utils::get_boolean($attrib['submit'])) {
             $out .= html::p(
                 'formbuttons',
                 html::tag('button', [
                     'type'  => 'submit',
                     'id'    => 'rcmloginsubmit',
                     'class' => 'button mainaction save',
                 ], $this->gettext('continue'))
             );
         }
 
         // surround html output with a form tag
         if (empty($attrib['form'])) {
             $out = $this->api->output->form_tag(['name' => $form_name, 'method' => 'post'], $out);
         }
 
         return $out;
     }
 
     /**
      * Load driver class for the given authentication factor
      *
-     * @param string $factor Factor identifier (<method>:<id>)
+     * @param string $factor   Factor identifier (<method>:<id>)
+     * @param string $username Username (email)
+     *
      * @return Kolab2FA\Driver\Base|false
      */
-    public function get_driver($factor)
+    public function get_driver($factor, $username = null)
     {
         [$method] = explode(':', $factor, 2);
 
         $rcmail = rcmail::get_instance();
 
         if (!empty($this->drivers[$factor])) {
             return $this->drivers[$factor];
         }
 
         $config = $rcmail->config->get('kolab_2fa_' . $method, []);
 
-        // use product name as "issuer""
+        // use product name as "issuer"
         if (empty($config['issuer'])) {
             $config['issuer'] = $rcmail->config->get('product_name');
         }
 
-        try {
-            // TODO: use external auth service if configured
+        if (empty($username) && $rcmail->user->ID) {
+            $username = $rcmail->get_user_name();
+        }
 
-            $driver = \Kolab2FA\Driver\Base::factory($factor, $config);
+        try {
 
-            // attach storage
-            $driver->storage = $this->get_storage();
+            $storage = $this->get_storage($username);
 
-            if ($rcmail->user->ID) {
-                $driver->username  = $rcmail->get_user_name();
-            }
+            $driver = \Kolab2FA\Driver\Base::factory($storage, $factor, $config);
 
             $this->drivers[$factor] = $driver;
             return $driver;
         } catch (Exception $e) {
             $error = strval($e);
         }
 
         rcube::raise_error(
             [
                 'code' => 600,
                 'type' => 'php',
                 'file' => __FILE__,
                 'line' => __LINE__,
                 'message' => $error],
             true,
             false
         );
 
         return false;
     }
 
     /**
      * Getter for a storage instance singleton
      */
     public function get_storage($for = null)
     {
         if (!isset($this->storage) || (!empty($for) && $this->storage->username !== $for)) {
             $rcmail = rcmail::get_instance();
             try {
                 $this->storage = \Kolab2FA\Storage\Base::factory(
                     $rcmail->config->get('kolab_2fa_storage', 'roundcube'),
                     $rcmail->config->get('kolab_2fa_storage_config', [])
                 );
 
                 $this->storage->set_username($for);
                 $this->storage->set_logger(new \Kolab2FA\Log\RcubeLogger());
 
                 // set user properties from active session
                 if (!empty($_SESSION['kolab_dn'])) {
                     $this->storage->userdn = $_SESSION['kolab_dn'];
                 }
             } catch (Exception $e) {
                 $this->storage = false;
 
                 rcube::raise_error(
                     [
                         'code' => 600,
                         'type' => 'php',
                         'file' => __FILE__,
                         'line' => __LINE__,
                         'message' => $e->getMessage()],
                     true,
                     false
                 );
             }
         }
 
         return $this->storage;
     }
 
     /**
      * Handler for 'settings_actions' hook
      */
     public function settings_actions($args)
     {
         // register as settings action
         $args['actions'][] = [
             'action' => 'plugin.kolab-2fa',
             'class'  => 'twofactorauth',
             'label'  => 'settingslist',
             'title'  => 'settingstitle',
             'domain' => 'kolab_2fa',
         ];
 
         return $args;
     }
 
     /**
      * Handler for settings/plugin.kolab-2fa requests
      */
     public function settings_view()
     {
         $this->register_handler('plugin.settingsform', [$this, 'settings_form']);
         $this->register_handler('plugin.settingslist', [$this, 'settings_list']);
         $this->register_handler('plugin.factoradder', [$this, 'settings_factoradder']);
         $this->register_handler('plugin.highsecuritydialog', [$this, 'settings_highsecuritydialog']);
 
         $this->include_script('kolab2fa.js');
         $this->include_stylesheet($this->local_skin_path() . '/kolab2fa.css');
 
         $this->api->output->set_env('session_secured', $this->check_secure_mode());
         $this->api->output->add_label('save', 'cancel');
         $this->api->output->set_pagetitle($this->gettext('settingstitle'));
         $this->api->output->send('kolab_2fa.config');
     }
 
     /**
      * Render the menu to add another authentication factor
      */
     public function settings_factoradder($attrib)
     {
         $rcmail = rcmail::get_instance();
 
         $attrib['id'] = 'kolab2fa-add';
 
         $select = new html_select($attrib);
         $select->add($this->gettext('addfactor') . '...', '');
         foreach ((array)$rcmail->config->get('kolab_2fa_drivers', []) as $method) {
             $select->add($this->gettext($method), $method);
         }
 
         return $select->show();
     }
 
     /**
      * Render a list of active factor this user has configured
      */
     public function settings_list($attrib = [])
     {
         $attrib['id'] = 'kolab2fa-factors';
         $table = new html_table(['cols' => 3]);
 
         $table->add_header('name', $this->gettext('factor'));
         $table->add_header('created', $this->gettext('created'));
         $table->add_header('actions', '');
 
         return $table->show($attrib);
     }
 
     /**
      * Render the settings form template object
      */
     public function settings_form($attrib = [])
     {
         $rcmail = rcmail::get_instance();
         $storage = $this->get_storage($rcmail->get_user_name());
         $factors = $storage ? (array)$storage->enumerate() : [];
         $drivers = (array)$rcmail->config->get('kolab_2fa_drivers', []);
         $out = '';
         $env_methods = [];
 
         foreach ($drivers as $j => $method) {
             $out .= $this->settings_factor($method, $attrib);
             $env_methods[$method] = [
                 'name'   => $this->gettext($method),
                 'active' => 0,
             ];
         }
 
         $me = $this;
         $factors = array_combine(
             $factors,
             array_map(function ($id) use ($me, &$env_methods) {
                 $props = ['id' => $id];
 
                 if ($driver = $me->get_driver($id)) {
                     $props += $this->format_props($driver->props());
                     $props['method'] = $driver->method;
                     $props['name'] = $me->gettext($driver->method);
                     $env_methods[$driver->method]['active']++;
                 }
 
                 return $props;
             }, $factors)
         );
 
         $this->api->output->set_env('kolab_2fa_methods', $env_methods);
         $this->api->output->set_env('kolab_2fa_factors', !empty($factors) ? $factors : null);
 
         return html::div(['id' => 'kolab2fapropform'], $out);
     }
 
     /**
      * Render the settings UI for the given method/driver
      */
     protected function settings_factor($method, $attrib)
     {
         $out = '';
         $rcmail = rcmail::get_instance();
         $attrib += ['class' => 'propform'];
 
         if ($driver = $this->get_driver($method)) {
             $table = new html_table(['cols' => 2, 'class' => $attrib['class']]);
 
             foreach ($driver->props() as $field => $prop) {
                 if (!$prop['editable']) {
                     continue;
                 }
 
                 switch ($prop['type']) {
                     case 'boolean':
                     case 'checkbox':
                         $input = new html_checkbox(['value' => '1']);
                         break;
 
                     case 'enum':
                     case 'select':
                         $input = new html_select(['disabled' => !empty($prop['readonly'])]);
                         $input->add(array_map([$this, 'gettext'], $prop['options']), $prop['options']);
                         break;
 
                     default:
                         $input = new html_inputfield([
                                 'size' => !empty($prop['size']) ? $prop['size'] : 30,
                                 'disabled' => empty($prop['editable']),
                         ]);
                 }
 
                 $explain_label = $field . 'explain' . $method;
                 $explain_html = $rcmail->text_exists($explain_label, 'kolab_2fa') ? html::div('explain form-text', $this->gettext($explain_label)) : '';
 
                 $field_id = 'rcmk2fa' . $method . $field;
                 $table->add('title', html::label($field_id, $this->gettext($field)));
                 $table->add(null, $input->show('', ['id' => $field_id, 'name' => "_prop[$field]"]) . $explain_html);
             }
 
             // add row for displaying the QR code
             if (method_exists($driver, 'get_provisioning_uri')) {
                 $gif = 'data:image/gif;base64,R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7';
                 $table->add('title', $this->gettext('qrcode'));
                 $table->add(
                     'pl-3 pr-3',
                     html::div('explain form-text', $this->gettext("qrcodeexplain$method"))
                     . html::tag('img', ['src' => $gif, 'class' => 'qrcode mt-2', 'rel' => $method])
                 );
 
                 // add row for testing the factor
                 $field_id = 'rcmk2faverify' . $method;
                 $table->add('title', html::label($field_id, $this->gettext('verifycode')));
                 $table->add(
                     null,
                     html::tag('input', ['type' => 'text', 'name' => '_verify_code', 'id' => $field_id, 'class' => 'k2fa-verify', 'size' => 20, 'required' => true]) .
                     html::div('explain form-text', $this->gettext("verifycodeexplain$method"))
                 );
             }
 
             $input_id = new html_hiddenfield(['name' => '_prop[id]', 'value' => '']);
 
             $out .= html::tag(
                 'form',
                 [
                     'method' => 'post',
                     'action' => '#',
                     'id'     => 'kolab2fa-prop-' . $method,
                     'style'  => 'display:none',
                     'class'  => 'propform',
                 ],
                 html::tag(
                     'fieldset',
                     [],
                     html::tag('legend', [], $this->gettext($method)) .
                     html::div('factorprop', $table->show()) .
                     $input_id->show()
                 )
             );
         }
 
         return $out;
     }
 
     /**
      * Render the high-security-dialog content
      */
     public function settings_highsecuritydialog($attrib = [])
     {
         $attrib += ['id' => 'kolab2fa-highsecuritydialog'];
 
         $field_id = 'rcmk2facode';
         $input = new html_inputfield(['name' => '_code', 'id' => $field_id, 'class' => 'verifycode', 'size' => 20]);
         $label = html::label(['for' => $field_id, 'class' => 'col-form-label col-sm-4'], '$name');
 
         return html::div(
             $attrib,
             html::div('explain form-text', $this->gettext('highsecuritydialog'))
             . html::div('propform row form-group', $label . html::div('col-sm-8', $input->show('')))
         );
     }
 
     /**
      * Handler for settings/plugin.kolab-2fa-save requests
      */
     public function settings_save()
     {
         $method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
         $data   = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
 
         $rcmail = rcmail::get_instance();
         $storage = $this->get_storage($rcmail->get_user_name());
         $success = false;
         $errors = 0;
         $save_data = [];
 
         if ($driver = $this->get_driver($method)) {
             if ($data === false) {
                 if ($this->check_secure_mode()) {
                     // remove method from active factors and clear stored settings
                     $success = $driver->clear();
                 } else {
                     $errors++;
                 }
             } else {
                 // verify the submitted code before saving
                 $verify_code = rcube_utils::get_input_value('_verify_code', rcube_utils::INPUT_POST);
                 $timestamp = intval(rcube_utils::get_input_value('_timestamp', rcube_utils::INPUT_POST));
                 if (!empty($verify_code)) {
                     if (!$driver->verify($verify_code, $timestamp)) {
                         $this->api->output->command('plugin.verify_response', [
                             'id'      => $driver->id,
                             'method'  => $driver->method,
                             'success' => false,
                             'message' => str_replace('$method', $this->gettext($driver->method), $this->gettext('codeverificationfailed')),
                         ]);
                         $this->api->output->send();
                     }
                 }
 
                 foreach ($data as $prop => $value) {
                     if (!$driver->set($prop, $value)) {
                         $errors++;
                     }
                 }
 
                 $driver->set('active', true);
             }
 
             // commit changes to the user properties
             if (!$errors) {
                 if ($success = $driver->commit()) {
                     $save_data = $data !== false ? $this->format_props($driver->props()) : [];
                 } else {
                     $errors++;
                 }
             }
         }
 
         if ($success) {
             $this->api->output->show_message($data === false ? $this->gettext('factorremovesuccess') : $this->gettext('factorsavesuccess'), 'confirmation');
             $this->api->output->command('plugin.save_success', [
                     'method' => $method,
                     'active' => $data !== false,
                     'id'     => $driver->id] + $save_data);
         } elseif ($errors) {
             $this->api->output->show_message($this->gettext('factorsaveerror'), 'error');
             $this->api->output->command('plugin.reset_form', $data !== false ? $method : null);
         }
 
         $this->api->output->send();
     }
 
     /**
      * Handler for settings/plugin.kolab-2fa-data requests
      */
     public function settings_data()
     {
         $method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
 
         if ($driver = $this->get_driver($method)) {
             $data = ['method' => $method, 'id' => $driver->id];
 
             foreach ($driver->props(true) as $field => $prop) {
                 $data[$field] = $prop['text'] ?: $prop['value'];
             }
 
             // generate QR code for provisioning URI
             if (method_exists($driver, 'get_provisioning_uri')) {
                 try {
                     $uri = $driver->get_provisioning_uri();
 
                     // Some OTP apps have an issue with algorithm character case
                     // So we make sure we use upper-case per the spec.
                     $uri = str_replace('algorithm=sha', 'algorithm=SHA', $uri);
 
                     $qr = new Endroid\QrCode\QrCode();
                     $qr->setText($uri)
                        ->setSize(240)
                        ->setPadding(10)
                        ->setErrorCorrection('high')
                        ->setForegroundColor(['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0])
                        ->setBackgroundColor(['r' => 255, 'g' => 255, 'b' => 255, 'a' => 0]);
                     $data['qrcode'] = base64_encode($qr->get());
                 } catch (Exception $e) {
                     rcube::raise_error($e, true, false);
                 }
             }
 
             $this->api->output->command('plugin.render_data', $data);
         }
 
         $this->api->output->send();
     }
 
     /**
      * Handler for settings/plugin.kolab-2fa-verify requests
      */
     public function settings_verify()
     {
         $method = rcube_utils::get_input_value('_method', rcube_utils::INPUT_POST);
         $timestamp = intval(rcube_utils::get_input_value('_timestamp', rcube_utils::INPUT_POST));
         $success = false;
 
         if ($driver = $this->get_driver($method)) {
             $data = @json_decode(rcube_utils::get_input_value('_data', rcube_utils::INPUT_POST), true);
             if (is_array($data)) {
                 foreach ($data as $key => $value) {
                     if ($value !== '******') {
-                        $driver->$key = $value;
+                        $driver->set($key, $value, false);
                     }
                 }
             }
 
             $success = $driver->verify(rcube_utils::get_input_value('_code', rcube_utils::INPUT_POST), $timestamp);
             $method = $driver->method;
         }
 
         // put session into high-security mode
         if ($success && !empty($_POST['_session'])) {
             $_SESSION['kolab_2fa_secure_mode'] = time();
         }
 
         $this->api->output->command('plugin.verify_response', [
             'method' => $method,
             'success' => $success,
             'message' => str_replace(
                 '$method',
                 $this->gettext($method),
                 $this->gettext($success ? 'codeverificationpassed' : 'codeverificationfailed')
             ),
         ]);
 
         $this->api->output->send();
     }
 
     /**
      *
      */
     protected function format_props($props)
     {
         $rcmail = rcmail::get_instance();
         $values = [];
 
         foreach ($props as $key => $prop) {
             switch ($prop['type']) {
                 case 'datetime':
                     $value = $rcmail->format_date($prop['value']);
                     break;
 
                 default:
                     $value = $prop['value'];
             }
 
             $values[$key] = $value;
         }
 
         return $values;
     }
 
     /**
      * Check whether the session is secured with 2FA (excluding the logon)
      */
     protected function check_secure_mode()
     {
         // Allow admins that used kolab_auth's "login as" feature to act without
         // being asked for the user's second factor
         if (!empty($_SESSION['kolab_auth_admin']) && !empty($_SESSION['kolab_auth_password'])) {
             return true;
         }
 
         if (!empty($_SESSION['kolab_2fa_secure_mode']) && $_SESSION['kolab_2fa_secure_mode'] > time() - 180) {
             return $_SESSION['kolab_2fa_secure_mode'];
         }
 
         return false;
     }
 }
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php
index 1dd2cea9..273dc170 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Base.php
@@ -1,358 +1,338 @@
 <?php
 
 /**
  * Kolab 2-Factor-Authentication Driver base class
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 namespace Kolab2FA\Driver;
 
 /**
  * Kolab 2-Factor-Authentication Driver base class
- *
- * @property string $username
- * @property string $secret
  */
 abstract class Base
 {
     public $method;
     public $id;
     public $storage;
 
     protected $config          = [];
+    protected $config_keys     = [];
     protected $props           = [];
     protected $user_props      = [];
     protected $pending_changes = false;
     protected $temporary       = false;
     protected $allowed_props   = ['username'];
 
     public $user_settings = [
         'active' => [
             'type'     => 'boolean',
             'editable' => false,
             'hidden'   => false,
             'default'  => false,
         ],
         'label' => [
             'type'      => 'text',
             'editable'  => true,
             'label'     => 'label',
             'generator' => 'default_label',
         ],
         'created' => [
             'type'      => 'datetime',
             'editable'  => false,
             'hidden'    => false,
             'label'     => 'created',
             'generator' => 'time',
         ],
     ];
 
     /**
      * Static factory method
      */
-    public static function factory($id, $config)
+    public static function factory($storage, $id, $config)
     {
         [$method] = explode(':', $id);
 
         $classmap = [
             'totp'    => '\\Kolab2FA\\Driver\\TOTP',
             'hotp'    => '\\Kolab2FA\\Driver\\HOTP',
             'yubikey' => '\\Kolab2FA\\Driver\\Yubikey',
         ];
 
         $cls = $classmap[strtolower($method)];
         if ($cls && class_exists($cls)) {
-            return new $cls($config, $id);
+            return new $cls($storage, $config, $id);
         }
 
         throw new Exception("Unknown 2FA driver '$method'");
     }
 
     /**
      * Default constructor
      */
-    public function __construct($config = null, $id = null)
+    public function __construct($storage, $config = null, $id = null)
     {
-        $this->init($config);
+        if (!is_array($config)) {
+            $config = [];
+        }
+
+        $this->storage = $storage;
+        $this->props['username'] = (string) $storage->username;
 
         if (!empty($id) && $id != $this->method) {
             $this->id = $id;
+            if ($this->storage) {
+                $this->user_props = (array) $this->storage->read($this->id);
+                foreach ($this->config_keys as $key) {
+                    if (isset($this->user_props[$key])) {
+                        $config[$key] = $this->user_props[$key];
+                    }
+                }
+            }
         } else { // generate random ID
             $this->id = $this->method . ':' . bin2hex(openssl_random_pseudo_bytes(12));
             $this->temporary = true;
         }
+
+        $this->init($config);
     }
 
     /**
      * Initialize the driver with the given config options
      */
-    public function init($config)
+    protected function init($config)
     {
         if (is_array($config)) {
             $this->config = array_merge($this->config, $config);
         }
-
-        if (!empty($config['storage'])) {
-            $this->storage = \Kolab2FA\Storage\Base::factory($config['storage'], $config['storage_config']);
-        }
     }
 
     /**
      * Verify the submitted authentication code
      *
      * @param string $code      The 2nd authentication factor to verify
      * @param int    $timestamp Timestamp of authentication process (window start)
      *
      * @return bool True if valid, false otherwise
      */
     abstract public function verify($code, $timestamp = null);
 
+    /**
+     * Implement this method if the driver can be provisioned via QR code
+     */
+    /* abstract function get_provisioning_uri(); */
+
     /**
      * Getter for user-visible properties
      */
     public function props($force = false)
     {
         $data = [];
 
         foreach ($this->user_settings as $key => $p) {
             if (!empty($p['private'])) {
                 continue;
             }
 
             $data[$key] = [
                 'type'     => $p['type'],
                 'editable' => $p['editable'] ?? false,
                 'hidden'   => $p['hidden'] ?? false,
                 'label'    => $p['label'] ?? '',
                 'value'    => $this->get($key, $force),
             ];
 
             // format value into text
             switch ($p['type']) {
                 case 'boolean':
                     $data[$key]['value'] = (bool)$data[$key]['value'];
                     $data[$key]['text'] = $data[$key]['value'] ? 'yes' : 'no';
                     break;
 
                 case 'datetime':
                     if (is_numeric($data[$key]['value'])) {
                         $data[$key]['text'] = date('c', $data[$key]['value']);
                         break;
                     }
 
                     // no break
                 default:
                     $data[$key]['text'] = $data[$key]['value'];
             }
         }
 
         return $data;
     }
 
-    /**
-     * Implement this method if the driver can be provisioned via QR code
-     */
-    /* abstract function get_provisioning_uri(); */
-
     /**
      * Generate a random secret string
      */
     public function generate_secret($length = 16)
     {
         // Base32 characters
         $chars = [
             'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', //  7
             'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
             'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
             'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
         ];
 
         $secret = '';
         for ($i = 0; $i < $length; $i++) {
             $secret .= $chars[array_rand($chars)];
         }
 
         return $secret;
     }
 
     /**
      * Generate the default label based on the method
      */
     public function default_label()
     {
         if (class_exists('\\rcmail', false)) {
             return \rcmail::get_instance()->gettext($this->method, 'kolab_2fa');
         }
 
         return strtoupper($this->method);
     }
 
     /**
      * Getter for read-only access to driver properties
      */
     public function get($key, $force = false)
     {
         // this is a per-user property: get from persistent storage
         if (isset($this->user_settings[$key])) {
             $value = $this->get_user_prop($key);
 
             // generate property value
             if (!isset($value) && $force && !empty($this->user_settings[$key]['generator'])) {
                 $func = $this->user_settings[$key]['generator'];
                 if (is_string($func) && !is_callable($func)) {
                     $func = [$this, $func];
                 }
                 if (is_callable($func)) {
                     $value = call_user_func($func);
                 }
                 if (isset($value)) {
                     $this->set_user_prop($key, $value);
                 }
             }
         } else {
-            $value = $this->props[$key] ?? null;
+            $value = $this->get_user_prop($key);
+
+            if ($value === null) {
+                $value = $this->props[$key] ?? null;
+            }
         }
 
         return $value;
     }
 
     /**
      * Setter for restricted access to driver properties
      */
     public function set($key, $value, $persistent = true)
     {
         // store as per-user property
         if (isset($this->user_settings[$key])) {
             if ($persistent) {
                 return $this->set_user_prop($key, $value);
             }
             $this->user_props[$key] = $value;
         }
 
         $setter = 'set_' . $key;
         if (method_exists($this, $setter)) {
             call_user_func([$this, $setter], $value);
         } elseif (in_array($key, $this->allowed_props)) {
             $this->props[$key] = $value;
         }
 
         return true;
     }
 
     /**
      * Commit changes to storage
      */
     public function commit()
     {
         if (!empty($this->user_props) && $this->storage && $this->pending_changes) {
-            if ($this->storage->write($this->id, $this->user_props)) {
+            $props = $this->user_props;
+
+            // Remamber the driver config too. It will be used to verify the code.
+            // The configured one may be different than the one used on code creation.
+            foreach ($this->config_keys as $key) {
+                if (isset($this->config[$key])) {
+                    $props[$key] = $this->config[$key];
+                }
+            }
+
+            if ($this->storage->write($this->id, $props)) {
                 $this->pending_changes = false;
                 $this->temporary = false;
             }
         }
 
         return !$this->pending_changes;
     }
 
-    /**
-     * Dedicated setter for the username property
-     */
-    public function set_username($username)
-    {
-        $this->props['username'] = $username;
-
-        if ($this->storage) {
-            $this->storage->set_username($username);
-        }
-
-        return true;
-    }
-
     /**
      * Clear data stored for this driver
      */
     public function clear()
     {
         if ($this->storage) {
             return $this->storage->remove($this->id);
         }
 
         return false;
     }
 
     /**
      * Checks that a string contains a semicolon
      */
     protected function hasSemicolon($value)
     {
         return preg_match('/(:|%3A)/i', (string) $value) > 0;
     }
 
     /**
      * Getter for per-user properties for this method
      */
     protected function get_user_prop($key)
     {
         if (!isset($this->user_props[$key]) && $this->storage && !$this->pending_changes && !$this->temporary) {
             $this->user_props = (array)$this->storage->read($this->id);
         }
 
         return $this->user_props[$key] ?? null;
     }
 
     /**
      * Setter for per-user properties for this method
      */
     protected function set_user_prop($key, $value)
     {
         $this->pending_changes |= (($this->user_props[$key] ?? null) !== $value);
         $this->user_props[$key] = $value;
         return true;
     }
-
-    /**
-     * Magic getter for read-only access to driver properties
-     */
-    public function __get($key)
-    {
-        // this is a per-user property: get from persistent storage
-        if (isset($this->user_settings[$key])) {
-            return $this->get_user_prop($key);
-        }
-
-        return $this->props[$key];
-    }
-
-    /**
-     * Magic setter for restricted access to driver properties
-     */
-    public function __set($key, $value)
-    {
-        $this->set($key, $value, false);
-    }
-
-    /**
-     * Magic check if driver property is defined
-     */
-    public function __isset($key)
-    {
-        return isset($this->props[$key]);
-    }
 }
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php
index e30b0f5f..dc7bb80d 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/HOTP.php
@@ -1,148 +1,142 @@
 <?php
 
 /**
  * Kolab 2-Factor-Authentication HOTP driver implementation
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 namespace Kolab2FA\Driver;
 
 class HOTP extends Base
 {
     public $method = 'hotp';
 
     protected $config = [
         'digits'   => 6,
         'window'   => 4,
         'digest'   => 'sha1',
     ];
 
+    protected $config_keys = ['digits', 'digest'];
     protected $backend;
 
     /**
      *
      */
     public function init($config)
     {
         parent::init($config);
 
         $this->user_settings += [
             'secret' => [
                 'type'      => 'text',
                 'private'   => true,
                 'label'     => 'secret',
                 'generator' => 'generate_secret',
             ],
             'counter' => [
                 'type'      => 'integer',
                 'editable'  => false,
                 'hidden'    => true,
                 'generator' => 'random_counter',
             ],
         ];
 
         if (!in_array($this->config['digest'], ['md5', 'sha1', 'sha256', 'sha512'])) {
             throw new \Exception("'{$this->config['digest']}' digest is not supported.");
         }
 
         if (!is_numeric($this->config['digits']) || $this->config['digits'] < 1) {
             throw new \Exception('Digits must be at least 1.');
         }
 
         if ($this->hasSemicolon($this->config['issuer'])) {
             throw new \Exception('Issuer must not contain a semi-colon.');
         }
 
         // copy config options
         $this->backend = \OTPHP\HOTP::create(
             null, // secret
             0, // counter
             $this->config['digest'], // digest
             $this->config['digits'] // digits
         );
 
         $this->backend->setIssuer($this->config['issuer']);
         $this->backend->setIssuerIncludedAsParameter(true);
     }
 
     /**
      *
      */
     public function verify($code, $timestamp = null)
     {
         // get my secret from the user storage
         $secret  = $this->get('secret');
-        $counter = $this->get('counter');
 
         if (!strlen($secret)) {
-            // LOG: "no secret set for user $this->username"
-            // rcube::console("VERIFY HOTP: no secret set for user $this->username");
             return false;
         }
 
         try {
-            $this->backend->setLabel($this->username);
+            $this->backend->setLabel($this->get('username'));
             $this->backend->setSecret($secret);
-            $this->backend->setCounter(intval($this->get('counter')));
 
-            $pass = $this->backend->verify($code, $counter, (int) $this->config['window']);
+            $pass = $this->backend->verify($code, $this->get('counter'), (int) $this->config['window']);
 
             // store incremented counter value
             $this->set('counter', $this->backend->getCounter());
             $this->commit();
         } catch (\Exception $e) {
-            // LOG: exception
-            // rcube::console("VERIFY HOTP: $this->id, " . strval($e));
             $pass = false;
         }
 
-        // rcube::console('VERIFY HOTP', $this->username, $secret, $counter, $code, $pass);
         return $pass;
     }
 
     /**
      * Get the provisioning URI.
      */
     public function get_provisioning_uri()
     {
-        if (!$this->secret) {
+        if (!$this->get('secret')) {
             // generate new secret and store it
             $this->set('secret', $this->get('secret', true));
             $this->set('counter', $this->get('counter', true));
             $this->set('created', $this->get('created', true));
             $this->commit();
         }
 
         // TODO: deny call if already active?
 
-        $this->backend->setLabel($this->username);
-        $this->backend->setSecret($this->secret);
+        $this->backend->setLabel($this->get('username'));
+        $this->backend->setSecret($this->get('secret'));
         $this->backend->setCounter(intval($this->get('counter')));
 
         return $this->backend->getProvisioningUri();
     }
 
     /**
      * Generate a random counter value
      */
     public function random_counter()
     {
         return mt_rand(1, 999);
     }
 }
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php
index 6f6a173e..822c3efb 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/TOTP.php
@@ -1,137 +1,133 @@
 <?php
 
 /**
  * Kolab 2-Factor-Authentication TOTP driver implementation
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 namespace Kolab2FA\Driver;
 
 class TOTP extends Base
 {
     public $method = 'totp';
 
     protected $config = [
         'digits'   => 6,
         'interval' => 30,
         'digest'   => 'sha1',
     ];
 
+    protected $config_keys = ['digits', 'digest'];
     protected $backend;
 
     /**
      *
      */
     public function init($config)
     {
         parent::init($config);
 
         $this->user_settings += [
             'secret' => [
                 'type'      => 'text',
                 'private'   => true,
                 'label'     => 'secret',
                 'generator' => 'generate_secret',
             ],
         ];
 
         if (!in_array($this->config['digest'], ['md5', 'sha1', 'sha256', 'sha512'])) {
             throw new \Exception("'{$this->config['digest']}' digest is not supported.");
         }
 
         if (!is_numeric($this->config['digits']) || $this->config['digits'] < 1) {
             throw new \Exception('Digits must be at least 1.');
         }
 
         if (!is_numeric($this->config['interval']) || $this->config['interval'] < 1) {
             throw new \Exception('Interval must be at least 1.');
         }
 
         if ($this->hasSemicolon($this->config['issuer'])) {
             throw new \Exception('Issuer must not contain a semi-colon.');
         }
 
         // copy config options
         $this->backend = \OTPHP\TOTP::create(
             null, //secret
             $this->config['interval'], // period
             $this->config['digest'], // digest
             $this->config['digits'] // digits
         );
 
         $this->backend->setIssuer($this->config['issuer']);
         $this->backend->setIssuerIncludedAsParameter(true);
     }
 
     /**
      *
      */
     public function verify($code, $timestamp = null)
     {
         // get my secret from the user storage
         $secret = $this->get('secret');
 
         if (!strlen($secret)) {
-            // LOG: "no secret set for user $this->username"
-            // rcube::console("VERIFY TOTP: no secret set for user $this->username");
             return false;
         }
 
-        $this->backend->setLabel($this->username);
+        $this->backend->setLabel($this->get('username'));
         $this->backend->setSecret($secret);
 
         // Pass a window to indicate the maximum timeslip between client (mobile
         // device) and server.
         $pass = $this->backend->verify($code, (int) $timestamp, 150);
 
         // try all codes from $timestamp till now
         if (!$pass && $timestamp) {
             $now = time();
             while (!$pass && $timestamp < $now) {
                 $pass = $code === $this->backend->at($timestamp);
                 $timestamp += $this->config['interval'];
             }
         }
 
-        // rcube::console('VERIFY TOTP', $this->username, $secret, $code, $timestamp, $pass);
         return $pass;
     }
 
     /**
      * Get the provisioning URI.
      */
     public function get_provisioning_uri()
     {
-        // rcube::console('PROV', $this->secret);
-        if (!$this->secret) {
+        if (!$this->get('secret')) {
             // generate new secret and store it
             $this->set('secret', $this->get('secret', true));
             $this->set('created', $this->get('created', true));
-            // rcube::console('PROV2', $this->secret);
             $this->commit();
         }
 
         // TODO: deny call if already active?
 
-        $this->backend->setLabel($this->username);
-        $this->backend->setSecret($this->secret);
+        $this->backend->setLabel($this->get('username'));
+        $this->backend->setSecret($this->get('secret'));
 
         return $this->backend->getProvisioningUri();
     }
 }
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php
index 579338e3..a2c3501b 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Driver/Yubikey.php
@@ -1,128 +1,126 @@
 <?php
 
 /**
  * Kolab 2-Factor-Authentication Yubikey driver implementation
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 namespace Kolab2FA\Driver;
 
 class Yubikey extends Base
 {
     public $method = 'yubikey';
 
     protected $backend;
 
     /**
      *
      */
     public function init($config)
     {
         parent::init($config);
 
         $this->user_settings += [
             'yubikeyid' => [
                 'type'     => 'text',
                 'editable' => true,
                 'label'    => 'secret',
             ],
         ];
 
         // initialize validator
         $this->backend = new \Yubikey\Validate($this->config['apikey'], $this->config['clientid']);
 
         // set configured validation hosts
         if (!empty($this->config['hosts'])) {
             $this->backend->setHosts((array)$this->config['hosts']);
         }
 
         if (isset($this->config['use_https'])) {
             $this->backend->setUseSecure((bool)$this->config['use_https']);
         }
     }
 
     /**
      *
      */
     public function verify($code, $timestamp = null)
     {
         // get my secret from the user storage
         $keyid = $this->get('yubikeyid');
         $pass  = false;
 
         if (!strlen($keyid)) {
-            // LOG: "no key registered for user $this->username"
             return false;
         }
 
         // check key prefix with associated Yubikey ID
         if (strpos($code, $keyid) === 0) {
             try {
                 $response = $this->backend->check($code);
                 $pass     = $response->success() === true;
             } catch (\Exception $e) {
                 // TODO: log exception
             }
         }
 
-        // rcube::console('VERIFY Yubikey', $this->username, $keyid, $code, $pass);
         return $pass;
     }
 
     /**
      * @override
      */
     public function set($key, $value, $persistent = true)
     {
         if ($key == 'yubikeyid' && strlen($value) > 12) {
             // verify the submitted code
             try {
                 $response = $this->backend->check($value);
                 if ($response->success() !== true) {
                     // TODO: report error
                     return false;
                 }
             } catch (\Exception $e) {
                 return false;
             }
 
             // truncate the submitted yubikey code to 12 characters
             $value = substr($value, 0, 12);
         }
         // invalid or no yubikey token provided
         elseif ($key == 'yubikeyid') {
             return false;
         }
 
         return parent::set($key, $value, $persistent);
     }
 
     /**
      * @override
      */
     protected function set_user_prop($key, $value)
     {
         // set created timestamp
         if ($key !== 'created' && !isset($this->created)) {
             parent::set_user_prop('created', $this->get('created', true));
         }
 
         return parent::set_user_prop($key, $value);
     }
 }
diff --git a/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php b/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php
index d068f2aa..bae8e41b 100644
--- a/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php
+++ b/plugins/kolab_2fa/lib/Kolab2FA/Storage/Base.php
@@ -1,125 +1,126 @@
 <?php
 
 /**
  * Abstract storage backend class for the Kolab 2-Factor-Authentication plugin
  *
  * @author Thomas Bruederli <bruederli@kolabsys.com>
  *
  * Copyright (C) 2015, Kolab Systems AG <contact@kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
  * published by the Free Software Foundation, either version 3 of the
  * License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU Affero General Public License for more details.
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
 namespace Kolab2FA\Storage;
 
 use Kolab2FA\Log;
 
 abstract class Base
 {
     public $username = null;
+
     protected $config = [];
     protected $logger;
 
     /**
      *
      */
     public static function factory($backend, $config)
     {
         $classmap = [
             'ldap' => '\\Kolab2FA\\Storage\\LDAP',
             'roundcube' => '\\Kolab2FA\\Storage\\RcubeUser',
             'rcubeuser' => '\\Kolab2FA\\Storage\\RcubeUser',
         ];
 
         $cls = $classmap[strtolower($backend)];
         if ($cls && class_exists($cls)) {
             return new $cls($config);
         }
 
         throw new Exception("Unknown storage backend '$backend'");
     }
 
     /**
      * Default constructor
      */
     public function __construct($config = null)
     {
         if (is_array($config)) {
             $this->init($config);
         }
     }
 
     /**
      * Initialize the driver with the given config options
      */
     public function init(array $config)
     {
         $this->config = array_merge($this->config, $config);
 
         // use syslog logger by default
         $this->set_logger(new Log\Syslog());
     }
 
     /**
      *
      */
     public function set_logger(Log\Logger $logger)
     {
         $this->logger = $logger;
 
         if (!empty($this->config['debug'])) {
             $this->logger->set_level(LOG_DEBUG);
         } elseif (!empty($this->config['loglevel'])) {
             $this->logger->set_level($this->config['loglevel']);
         }
     }
 
     /**
      * Set username to store data for
      */
     public function set_username($username)
     {
         $this->username = $username;
     }
 
     /**
      * Send messager to the logging system
      */
     protected function log($level, $message)
     {
         if ($this->logger) {
             $this->logger->log($level, $message);
         }
     }
 
     /**
      * List keys holding settings for 2-factor-authentication
      */
     abstract public function enumerate();
 
     /**
      * Read data for the given key
      */
     abstract public function read($key);
 
     /**
      * Save data for the given key
      */
     abstract public function write($key, $value);
 
     /**
      * Remove the data stored for the given key
      */
     abstract public function remove($key);
 }