Статьи

Использование AlterEgo для добавления двухфакторной аутентификации на ваш сайт WordPress

Хотя WordPress отлично справляется с базовыми задачами безопасности, его вход в систему по-прежнему безопасен, как ваше имя пользователя и пароль — точно так же, как вход почти на каждом веб-сайте.

До сих пор не принято решение о том, каким будет метод входа в будущем, но на данный момент лучшим кандидатом представляется двухфакторная аутентификация: объединение имени пользователя и пароля на основе логина с дополнительным, ограниченным по времени одноразовым токеном, генерируемым и проверенным. третьей стороной.

К счастью для нас, WordPress предоставляет фильтры, которые позволяют подключаться к потоку аутентификации и расширять его в плагинах и темах. Итак, в этом уроке я покажу вам, как это сделать, создав плагин для добавления двухфакторной аутентификации в WordPress, используя AlterEgo , сервис двухфакторной аутентификации MailChimp.

AlterEgo — не единственное решение для двухфакторной аутентификации, но оно интересное, в основном из-за возможности входа в систему с помощью телефона без ввода пароля. Вот как AlterEgo описывается на его веб-сайте:

AlterEgo добавляет дополнительный уровень безопасности вашему веб-приложению, чтобы предотвратить фишинговые атаки и нарушения, вызванные небезопасными паролями.

Он генерирует временные коды авторизации, доступные только на мобильных устройствах пользователей, и делает их обязательными для входа. Даже если пароли скомпрометированы, AlterEgo блокирует доступ к учетной записи, если у пользователя нет временного кода авторизации.

Чтобы завершить этот урок, вам понадобится работающая установка WordPress, работающая на установке PHP с включенными расширениями cURL (мы будем использовать cURL для отправки запросов в API AlterEgo ).

При тестировании плагина, если вы хотите иметь возможность настроить аутентификацию, ваш сервер WordPress должен использовать SSL: когда пользователь включает AlterEgo для своей учетной записи, специфичный для пользователя токен аутентификации отправляется на ваш сервер в виде HTTP параметр. Для обеспечения безопасной передачи этого параметра AlterEgo требуется безопасное соединение.

Поскольку у AlterEgo нет отдельного режима тестирования, это требование безопасности, которое имеет большой смысл в производственном режиме, применяется также при разработке плагинов …

В этом руководстве я расскажу вам о частях кода плагина, которые важны для понимания изменений в процессе аутентификации; однако, чтобы получить полное представление о плагине и попробовать его самостоятельно, вы можете скачать полный исходный код с GitHub по ссылке справа.

В этом уроке мы создадим новый экран для ввода пароля AlterEgo.

Экран будет показан для всех пользователей, которые включили AlterEgo в своих учетных записях сразу после того, как пользователь завершил стандартную регистрацию в WordPress.

Чтобы это произошло, мы подключимся к потоку аутентификации WordPress, используя один из двух фильтров аутентификации, которые WordPress предоставляет нам разработчикам: authenticate и wp_authenticate_user . Фильтры похожи друг на друга по функциональности, разница в том, что authenticate вызывается до того, как WordPress завершит любую свою собственную аутентификацию, а wp_authenticate_user только после завершения базовой аутентификации.

Для этого плагина я решил использовать authenticate поскольку он дает нам немного больше гибкости в изменении потока входа в систему.

Мы подробно расскажем о том, как это сделать. Но сначала нам нужно создать плагин и добавить несколько экранов администратора, необходимых для включения аутентификации.

Весь код в этом руководстве будет помещен в плагин WordPress под названием «Двухфакторный аутентификатор AlterEgo». Создание плагинов WordPress уже хорошо документировано во многих учебных пособиях ( вот хороший пример от Tuts +), поэтому я не буду снова рассказывать о базовом ведении домашнего хозяйства в этом учебном пособии. Вы можете обратиться к исходному коду для получения полной информации.

Давайте сразу перейдем к изменениям экрана администратора:

  • Сначала мы добавим новое поле настроек на страницу « Общие настройки» для ввода ключа AlterEgo API.
  • Затем мы получим ключ API от AlterEgo и сохраним его, используя только что созданное поле настроек.
  • Наконец, мы добавим возможность для ваших пользователей включить аутентификацию AlterEgo для их учетных записей. Это будет сделано на страницах профиля пользователя .

Давайте начнем!

Во-первых, важно помнить, что каждый сайт WordPress, на котором запущен этот плагин, считается собственным приложением и, следовательно, должен иметь свой собственный ключ API. Итак, нам нужно хранить ключ API в поле настроек, а не жестко кодировать его в коде плагина.

Поскольку нам нужно только одно поле настроек, имеет смысл сделать это, расширив существующую страницу настроек, а не создавая собственную страницу настроек для плагина.

Внутри класса плагина добавьте следующие функции, чтобы создать новое поле настроек на странице « Общие настройки» .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/**
 * Adds a General Settings field for storing the AlterEgo API key.
 */
function register_admin_settings() {
    add_settings_field(
        ‘alterego_api_key’,
        ‘AlterEgo API key’,
        array( $this, ‘alterego_api_key_setting_callback’ ),
        ‘general’
    );
    register_setting( ‘general’, ‘alterego_api_key’ );
}
 
/**
 * Callback function for rendering the API key settings field.
 */
function alterego_api_key_setting_callback() {
    echo ‘<input name=»alterego_api_key» type=»text» class=»regular-text» value=»‘ . get_option( ‘alterego_api_key’ ) . ‘»/>’;
}

API настроек WordPress будет обрабатывать сохранение настроек, так что нам остается их зарегистрировать ( строки 5-11 ) и определить способ визуализации поля настроек, используя функцию обратного вызова ( строки 17-19 ), определенную в строке 8 :

1
array( $this, ‘alterego_api_key_setting_callback’ )

Наш плагин аутентификации AlterEgo создается объектно-ориентированным способом как класс PHP, поэтому каждый раз, когда мы передаем функцию обратного вызова в хуки WordPress, нам нужно передавать как объект ( $this ), так и вызываемую функцию.

Прежде чем настройка появится на странице « Общие настройки» , необходимо вызвать функцию register_admin_settings . Чтобы это произошло, давайте создадим конструктор для класса плагина и внутри него хук действия WordPress. Как это:

1
2
3
4
5
6
7
/**
 * Sets up the plugin by hooking it to the right WordPress
 * actions and filters.
 */
public function __construct() {
    add_action( ‘admin_init’, array( $this, ‘register_admin_settings’ ) );
}

Вот и все. Поле настроек готово:

Теперь мы создали элемент настроек для хранения ключа AlterEgo API в настройках WordPress. Далее, чтобы протестировать приложение, нам нужно его получить.

Чтобы получить ключ API, сначала перейдите в AlterEgo и создайте учетную запись.

После создания учетной записи AlterEgo и входа в систему вы увидите панель управления, которая выглядит следующим образом. Нажмите на разработчика в правом верхнем углу.

На странице разработчика вы найдете список приложений (теперь пустых) и кнопку для регистрации нового приложения.

Нажмите « Зарегистрировать новое приложение», чтобы создать новое приложение для вашего сайта WordPress:

Каждое поле в форме, кроме значка URL, является обязательным.

Поле « Веб-сайт» необходимо для включения AlterEgo для входа пользователя в WordPress: AlterEgo перенаправит ваших пользователей только на URL-адрес, который является подстраницей указанного здесь адреса. В то время как AlterEgo позволит вам ввести любой действительный адрес (он проверяет, существует ли веб-сайт), было бы хорошо взглянуть на требования к этому URL уже на этом этапе.

Итак, мы столкнулись с двумя требованиями, которые, в зависимости от вашей настройки WordPress, могут дать вам дополнительную работу: во-первых, на этом этапе AlterEgo должен иметь доступ к вашему серверу, чтобы он мог проверить его действительность. Во-вторых, сервер должен использовать HTTPS.

Таким образом, если ваша установка WordPress еще не использует SSL и не доступна через Интернет, вам необходимо отредактировать конфигурацию, прежде чем вы сможете продолжить включение аутентификации AlterEgo. Как это сделать, в основном выходит за рамки данного руководства, но я поделюсь кратким обзором своего решения:

  • Я использую MAMP для создания самозаверяющего сертификата и запуска своего локального сервера в режиме HTTPS, используя этот сертификат.
  • Я делаю сервер доступным для Интернета, используя динамический DNS-сервис от No-IP .

Конечно, это не единственный способ настроить сервер, но он работает — и не требует много времени для настройки.

Когда вы настроите свой сервер, заполните информацию на экране AlterEgo «Зарегистрируйте свое приложение» и нажмите « Регистрация» . Если все пойдет хорошо, данные будут приняты, и вы будете перенаправлены обратно в список приложений.

Теперь, с включенным недавно созданным приложением, список будет выглядеть так:

Скопируйте значение ID авторизации приложения и сохраните его в настройках WordPress как ключ API AlterEgo .

Теперь, когда мы включили AlterEgo на общем уровне, теперь нужно добавить поле настроек, которое пользователи могут использовать для включения аутентификации AlterEgo для своих учетных записей.

Это делается путем перенаправления пользователя на страницу AlterEgo, чтобы авторизовать веб-сайт для использования его учетной записи AlterEgo. Как только пользователь принял запрос, он перенаправляется обратно на ваш сайт WordPress, где наш плагин затем захватывает и сохраняет возвращенный ключ авторизации для будущего использования.

Это специфическая для пользователя задача, поэтому естественным местом для настройки является страница профиля пользователя.

Страница профиля не поддерживает API настроек WordPress, который мы использовали для создания поля настроек API. Вместо этого мы будем использовать ловушку действия show_user_profile для вызова нашей функции register_profile_page_field для отображения поля настроек.

В этом случае, поскольку поле настроек на самом деле является просто ссылкой на AlterEgo, такой подход, на самом деле, кажется очень естественным.

Эта строка идет в конструктор плагина:

1
add_action( ‘show_user_profile’, array( $this, ‘register_profile_page_field’ ) );

А вот функция, которая будет отображать поле настроек, а затем некоторые пояснения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * Renders the profile page option for enabling AlterEgo authentication for
 * the currently logged in user.
 *
 * @param User $user Current user’s WordPress user object
 */
function register_profile_page_field( $user ) {
?>
    <table class=»form-table»>
    <tr>
        <th>
            <label for=»alterego_enabled»><?php _e( ‘AlterEgo Authentication’, ‘alterego_login’ );
        </th>
        <td>
            <?php if ( $this->get_user_auth_key( $user, $username ) ) : ?>
                <p>
                    <?php _e( ‘AlterEgo two-factor authentication <strong>enabled</strong>.’, ‘alterego_login’ );
                    <a href=»<?php echo home_url( ‘index.php?alterego_setup=3’ ); ?>»><?php _e( ‘Disable’, ‘alterego_login’ );
                </p>
            <?php else : ?>
                <p>
                    <a href=»<?php echo home_url( ‘index.php?alterego_setup=1’ ); ?>»>
                        <?php _e( ‘Enable AlterEgo two-factor authentication’, ‘alterego_login’ );
                    </a>
                </p>
            <?php endif;
        </td>
    </tr>
    </table>
<?php
}

Большая часть этого кода представляет собой просто HTML-код для отображения поля настроек AlterEgo на странице профиля пользователя.

Однако стоит заметить, как в строке 15 мы вызываем функцию с именем get_user_auth_key . Это простая вспомогательная функция для получения ключа аутентификации AlterEgo пользователя из метаданных пользователя.

Позже, когда мы делаем запросы API к AlterEgo, ключ аутентификации используется в качестве параметра для идентификации пользователя. В этом случае мы используем его просто для проверки, подключил ли пользователь свою учетную запись WordPress к AlterEgo или нет, чтобы мы могли показать другую ссылку (включить / отключить AlterEgo) в зависимости от статуса пользователя.

Поле настроек выглядит следующим образом (когда пользователь не включил аутентификацию AlterEgo):

Чтобы включить AlterEgo, пользователь нажимает на ссылку «Включить двухфакторную аутентификацию AlterEgo». Продолжая приведенный выше фрагмент кода ( строка 23 ), взгляните на URL этой ссылки:

1
<a href=»<?php echo home_url( ‘index.php?alterego_setup=1’ ); ?>

Можно было бы перенаправить пользователя прямо в AlterEgo, но это позволило бы хакеру пропустить базовую проверку подлинности WordPress и перейти прямо к AlterEgo. Это не был бы ужасный сценарий, но все же лучше закрыть эту дверь.

Мы сделаем это, передав одноразовый токен вместе с запросом AlterEgo. Таким образом, когда пользователь перенаправляется обратно на ваш сайт, мы можем проверить, является ли пользователь тем, кем он или она является.

Таким образом, вместо перенаправления пользователя прямо в AlterEgo, поток начинается с перенаправления на тот же сайт WordPress, используя новый параметр запроса alterego_setup=1 чтобы отметить этот специальный запрос, который должен обрабатываться плагином AlterEgo.

Для этого мы сначала зарегистрируем параметр alterego_setup в качестве новой переменной запроса, а затем создадим функцию для подключения к действию WordPress parse_requests для получения запроса до его обработки WordPress.

Сначала регистрируем параметр запроса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
/**
 * Adds new query variables to WordPress so that we can catch AlterEgo
 * authentication requests in «sniff_alterego_requests».
 *
 * @param Array $vars Current query variables
 *
 * @return The $vars array with our added variables.
 */
public function add_query_vars( $vars ){
    $vars[] = ‘alterego_login’;
    $vars[] = ‘alterego_key’;
    $vars[] = ‘alterego_sig’;
    $vars[] = ‘alterego_setup’;
    $vars[] = ‘alterego_notice’;
     
    return $vars;
}

Как вы заметили, функция добавляет новые переменные в список передаваемых ей переменных, а затем возвращает список. alterego_setup , интересующая нас на данный момент переменная является одной из них — остальные связаны с потоком входа в систему: отправка пароля AlterEgo на экране входа в систему выполняется таким же образом.

Чтобы добавить эти переменные запроса, нам нужно заставить WordPress вызывать эту новую функцию. Для этого добавьте новый обработчик фильтра в конструктор класса плагина:

1
add_filter( ‘query_vars’, array( $this, ‘add_query_vars’ ), 0 );

Добавление переменной запроса само по себе мало что дает. Нам также нужна функция для прохождения запроса и получения запросов, которые используют наши собственные переменные запроса. Мы вызовем эту функцию sniff_alterego_requests и добавим ее в качестве действия (снова в конструкторе):

1
add_action( ‘parse_request’, array($this, ‘sniff_alterego_requests’), 0);

Вот код, связанный с настройкой AlterEgo из этой функции:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Listens to HTTP requests, captures the AlterEgo specific requests and
 * passes them to the correct handlers.
 */
public function sniff_alterego_requests() {
    global $wp;
 
    // Enabling / disabling AlterEgo authentication («alterego_setup»)
     
    if ( isset( $wp->query_vars[‘alterego_setup’] ) ) {
        if ( is_user_logged_in() ) {
            $user = wp_get_current_user();
            switch ( $wp->query_vars[‘alterego_setup’] ) {
                // Step 1: start enabling process
                case 1:
                    $this->redirect_to_alterego( $user );
                    exit;
                 
                // Step 2: verify and save data from AlterEgo
                case 2:
                    $this->alter_ego_auth_callback( $user );
                    exit;
                         
                // Disable AlterEgo
                case 3:
                    $this->disable_alter_ego_auth( $user );
                    wp_redirect( admin_url( ‘profile.php?updated=1’ ) );
                    exit;
                 
                default:
                    break;
            }
        } else {
            // User not logged in, cannot enable / disable AlterEgo
            wp_redirect( home_url() );
        }
    }
}

Во-первых, вы заметите, что, как следует из названия функции, мы «анализируем» переменные запроса WordPress, чтобы посмотреть, передается ли одна из наших собственных переменных в запросе. В этом случае alterego_setup .

Когда код находит переменную, он копает глубже и перенаправляет поток WordPress, вызывая одну из функций обработчика запросов плагина.

Поскольку пользователь просто щелкнул ссылку «Включить аутентификацию AlterEgo» на своей странице профиля , значение alterego_setup будет равно 1 , и процесс начнется.

Для обработки первого шага в потоке авторизации AlterEgo метод сниффера вызывает функцию redirect_to_alterego , которая добавляет временный токен и затем перенаправляет пользователя в AlterEgo:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/**
 * Starts the process of setting up the AlterEgo authentication for current user:
 * redirects the user to AlterEgo to allow our application to use his/her
 * AlterEgo account.
 */
function redirect_to_alterego( $user ) {
    $app_id = get_option( ‘alterego_api_key’ );
         
    // Create temporary token and store it in user data
    $token = $this->generate_random_token();
    $this->set_temp_signature( $user, $token, ‘auth-token’ );
 
    // The token is also included in the redirect url to check when user gets back from AlterEgo
    $redirect_url = urlencode( home_url( ‘index.php?alterego_setup=2&token=’ . $token, ‘https’ ) );
 
    $url = ‘https://alteregoapp.com/account/authorize-app?id=’ .
    wp_redirect( $url );
    exit;
}

В строках 10 и 11 функция создает временный ключ и сохраняет его в профиле пользователя.

Вместе с этим временным ключом он также сохраняет отметку времени, которая будет проверена на следующем шаге, чтобы убедиться, что между шагами не прошло слишком много времени. Таким образом, если кто-то получит доступ к временной подписи и попытается использовать ее для последующего вызова URL, запрос больше не будет принят.

Для создания подписи мы используем две вспомогательные функции: generate_random_token создает подпись, а set_temp_signature сохраняет ее в профиле пользователя.

Как только подпись установлена, функция использует ее для создания URL-адреса перенаправления ( строка 14 ), на который AlterEgo отправит пользователя после завершения авторизации. Обратите внимание, что этот URL включает два параметра:

  • alterego_setup=2 : когда функция sniff_alterego_requests перехватывает этот параметр, он перенаправляет поток на правильный обработчик.
  • token=$token : после того, как пользователь вернется из AlterEgo, этот токен будет сравниваться с токеном, сохраненным в метаданных пользователя.

Наконец, в строках 16-18 функция объединяет все это в URL и перенаправляет пользователя в AlterEgo:

После нажатия кнопки « Разрешить» пользователь перенаправляется обратно на URL-адрес, указанный в $redirect_url . Пришло время перейти к следующему шагу в процессе авторизации.

Когда пользователь перенаправляется обратно в WordPress, наша функция alter_ego_auth_callback переменных запроса перехватывает запрос и замечает, что пришло время для второго шага в потоке аутентификации AlterEgo, и вызывает функцию alter_ego_auth_callback :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Receives and stores (if all is OK) the authorization token sent by AlterEgo
 * after the user has accepted the request.
 */
function alter_ego_auth_callback( $user ) {
    $token = $_POST[‘token’];
    $key = $_POST[‘key’];
         
    if ( $this->verify_temp_signature( $user, $token, ‘auth-token’ ) ) {
        update_user_meta( $user->ID, $this->alterego_auth_meta_field_name, $key );
        $this->delete_temp_signature( $user, ‘auth-token’ );
             
        wp_safe_redirect( admin_url( ‘profile.php?updated=1’ ) );
        exit;
    } else {
        wp_die( __( ‘Invalid security token’, ‘alterego_login’ ) );
    }
}

Функция довольно проста, но давайте кратко рассмотрим ее:

Сначала в строке 9 функция проверяет действительность одноразового токена.

Затем, если все в порядке, он продолжает, сохраняя ключ авторизации AlterEgo в метаданных пользователя, удаляя временную подпись и, наконец, перенаправляя пользователя обратно на страницу профиля.

На этой странице AlterEgo теперь включен:

После этого шага мы можем перейти к фактическому входу в AlterEgo.

Теперь, когда сервер готов, давайте начнем с реального входа в AlterEgo.

Как вы помните из введения, мы прервем основной процесс входа в WordPress, чтобы запросить пароль AlterEgo сразу после проверки имени пользователя и пароля.

Чтобы добавить этот шаг, мы будем использовать фильтр authenticate WordPress, который вызывается в тот момент, когда пользователь отправил форму входа в систему и WordPress начинает работу с аутентификацией.

Создайте новую функцию и добавьте ее в качестве фильтра (это идет в конструкторе):

1
add_filter( ‘authenticate’, array( $this, ‘authenticate’ ), 10, 3 );

Тогда сама функция. Во-первых, вот код, а затем мы рассмотрим его построчно.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * Authentication implementation with AlterEgo support.
 *
 * This method gets called through the «authenticate» filter hook.
 *
 * If the user has enabled AlterEgo, the login is handled here with an
 * added AlterEgo step.
 * regular WordPress authentication.
 */
function authenticate( $user = », $username = », $password = » ) {
    if ( $this->get_user_auth_key( $user, $username ) ) {
        // Verify that the user’s AlterEgo key is still valid and in use
        $ping = $this->alterego_api_call( get_user_by( ‘login’, $username ), ‘/check/ping.json’ );
        if ( $ping != ‘PONG!’) {
            $this->disable_alter_ego_auth( get_user_by( ‘login’, $username ) );
            return $user;
        }
     
        // Do the basic authentication manually
        $user = wp_authenticate_username_password( $user, $username, $password );
        if ( is_wp_error( $user ) ) {
            return $user;
        }
     
        // Create a temporary signature (valid for 5 minutes), store it in user
        // meta data and pass as a parameter in the login form.
        // will be checked at the end of the authentication process to make sure the user
        // has passed the regular authentication and is allowed to do the AlterEgo
        // authentication.
        $signature = $this->generate_random_token();
        $this->set_temp_signature( $user, $signature, ‘login’ );
                                                     
        $this->render_alterego_login_page( $user );
        exit;
    } else {
        // The user is not using AlterEgo, let WordPress handle the login
        return $user;
    }
}

В строке 11 вы увидите ту же проверку, которую мы использовали на странице настроек. Эта строка проверяет, активировал ли пользователь AlterEgo или нет. В ветви else, в строках 39-42 , вы заметите, что если пользователь не включил AlterEgo, фильтр просто возвращает данные пользователя, не внося в них никаких изменений. В этом случае WordPress вступает во владение и завершает вход в систему обычным способом.

Строки 12-17 являются дополнительной проверкой, чтобы увидеть, что пользователь не отключил авторизацию AlterEgo на стороне AlterEgo. Мы скоро сделаем вызовы API в ближайшее время. На данный момент достаточно знать, что этот метод возвращает PONG, если все в порядке. Если метод API возвращает что-то еще, вызов ping завершается неудачно, и (в строке 16 ) функция отключает AlterEgo для этого пользователя и позволяет WordPress обрабатывать вход в систему.

Поскольку мы используем фильтр проверки подлинности, на данный момент проверка подлинности еще не завершена. Итак, наша первая задача (в строке 20 ) — вызвать WordPress для базовой аутентификации.

Теперь обычный вход в WordPress просто установит файл cookie авторизации и перенаправит пользователя на панель администратора WordPress. Мы еще не закончили: пришло время показать экран входа в AlterEgo.

В строках 30-31 , используя функцию подписи, описанную ранее, функция создает маркер безопасности, который будет включен в переменные записи, когда пользователь отправит свой пароль AlterEgo. Таким образом, при проверке пароля AlterEgo плагин также может убедиться, что пользователь также выполнил обычную регистрацию имени пользователя и пароля.

Наконец, в строке 33 пришло время отобразить экран входа для ввода пароля AlterEgo. Для этого плагин вызывает функцию render_alterego_login_page . Обратите внимание, что на следующей строке нам нужно прервать выполнение, так как в противном случае WordPress попытался бы продолжить с обычным входом в систему — что привело бы к ряду сообщений об ошибках и странному поведению …

Функция render_alterego_login_page инициализирует количество переменных, необходимых для визуализации страницы, а затем включает файл шаблона login_page.php .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * Renders the AlterEgo login page with all required user information.
 *
 * @uses login_page.php
 */
function render_alterego_login_page( $user, $error = null ) {
    if ( !($user instanceof WP_User) ) {
        return;
    }
 
    $alterego_login_url = home_url( ‘index.php?alterego_login=1’ );
    $signature_meta = get_user_meta( $user->ID, $this->alterego_sig_meta_field_name, true );
         
    $signature = »;
    if ( is_array( $signature_meta ) ) {
        $signature = $signature_meta[‘signature’];
    }
         
    // Forward WordPress’s cookie related parameters
    $redirect_to = $_POST[‘redirect_to’];
    $remember_me = $_POST[‘remember_me’];
         
    $error_message = $error;
         
    require( ‘login_page.php’ );
}

Страница входа была создана путем копирования и редактирования стандартного шаблона входа WordPress и представляет собой довольно простую HTML-форму. Для получения подробной информации, посмотрите на приложенный исходный файл.

Самая интересная часть — это сама форма и переменные, определенные выше:

  • $alterego_login_url используется как действие формы. Мы рассмотрим это в следующем разделе.
  • login : имя пользователя, проходящего процесс входа в систему.
  • alterego_sig : одноразовый токен используется для проверки того, что никто не может отправить код AlterEgo, не пройдя сначала вход в WordPress.
  • redirect_to : знакомый по умолчанию для входа в WordPress, этот параметр используется для определения того, на какую страницу будет заходить пользователь после завершения входа в систему.
  • remember_me : другая переменная входа в WordPress, используемая для определения продолжительности файла cookie авторизации.
01
02
03
04
05
06
07
08
09
10
11
12
13
<form name=»loginform» id=»loginform» method=»post» action=»<?php echo esc_url( $alterego_login_url ) ?>»>
    <input type=»hidden» name=»login» value=»<?php echo $user->user_login; ?>»/>
    <input type=»hidden» name=»alterego_sig» value=»<?php echo $signature; ?>»/>
    <input type=»hidden» name=»redirect_to» value=»<?php echo $redirect_to; ?>»/>
    <input type=»hidden» name=»remember_me» value=»<?php echo $remember_me; ?>»/>
             
    <label for=»alterego_key»><?php _e(‘Enter AlterEgo Code:’, ‘alterego_login’) ?><br />
    <input type=»password» name=»alterego_key» class=»input» /></label>
                 
    <input type=»submit» name=»wp-submit» id=»wp-submit» class=»button button-primary button-large» value=»<?php esc_attr_e(‘Log In’); ?>» />
     
    <a href=»#» id=»send-challenge»>Login with phone</a>
</form>

Форма выглядит так:

Когда пользователь отправляет форму, нажимая на кнопку « Войти» , поток переходит к YOURSITE/index.php?alterego_login=1 . Как вы помните ранее, alterego_login — это одна из пользовательских переменных запроса, которую мы добавили для запросов add_query_varsadd_query_vars ).

Что приводит нас к следующему шагу.

Когда пользователь отправляет свой код AlterEgo, запрос (как и каждый запрос) проходит через функцию sniff_alterego_requests . Итак, чтобы прервать поток по умолчанию, давайте расширим его обработчиком для переменной запроса alterego_login :

01
02
03
04
05
06
07
08
09
10
11
12
// AlterEgo login requests («alterego_login»)
         
if ( isset( $wp->query_vars[‘alterego_login’] ) ) {
    $password = $wp->query_vars[‘alterego_key’];
    $signature = $wp->query_vars[‘alterego_sig’];
             
    $username = $_POST[‘login’];
    $user = get_user_by( ‘login’, $username );
             
    $this->do_alterego_authentication( $user, $username, $password, $signature );
    exit;
}

Это дополнение собирает данные (пароль, подпись, имя пользователя) и передает их в функцию аутентификации do_alterego_authentication которая будет выполнять фактическую аутентификацию.

Вот функция с последующим объяснением:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * Checks the AlterEgo passcode entered by user to finish the login.
 *
 * Called by sniff_alterego_requests when it finds an AlterEgo login request.
 */
function do_alterego_authentication( $user, $username, $password, $signature ) {
    if ( !$this->verify_temp_signature( $user, $signature ) ) {
        // Bad or missing signature.
        wp_redirect( home_url( ‘wp-login.php’ ) );
        exit;
    }
             
    if ( $password == null || trim( $password ) == » ) {
        $this->render_alterego_login_page( $user, __( ‘AlterEgo code cannot be empty’, ‘alterego_login’ ) );
        exit;
    }
         
    // Parameters OK, do the login.
    $response = $this->alterego_api_call( $user, ‘check/password.json’, array( ‘pass’ => $password ) );
    if ( $response == true ) {
        // AlterEgo code OK, finish login
        $this->delete_temp_signature( $user, ‘login’ );
                 
        $remember_me = ( $_POST[‘remember_me’] == ‘forever’ ) ?
        wp_set_auth_cookie( $user->ID, $remember_me );
         
        // If redirect was set in parameters, use that one.
        $redirect_to = ( isset( $_POST[‘redirect_to’] ) ) ?
        wp_safe_redirect( $redirect_to );
    } else {
        $this->render_alterego_login_page( $user, __( ‘Invalid AlterEgo code’, ‘alterego_login’ ) );
    }
}

После некоторой проверки параметров фактический вход в систему AlterEgo начинается с вызова API AlterEgo метода API check/password.json в строке 19 . Мы рассмотрим функцию alterego_api_call более подробно всего за секунду, но alterego_api_call по остальной части функции, чтобы получить общее представление.

Если AlterEgo принимает код доступа, функция продолжает вход в систему, устанавливая cookie-файл аутентификации WordPress, auth_cookie в строке 25, а затем перенаправляя пользователя на панель мониторинга или на URL-адрес, указанный в атрибуте redirect_to .

Однако если пользователь ввел неправильный код, экран входа AlterEgo снова отображается вместе с сообщением об ошибке.

Вот и все. Теперь давайте рассмотрим функцию, которую мы использовали для вызова API:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
 * Makes a call to the AlterEgo API using cURL.
 *
 * @param User $user The WordPress user object making the call
 * @param string $method The API method to call
 * @param Array $params The parameters to be sent to the API
 *
 * @return the API call response as parsed from JSON
 */
function alterego_api_call( $user, $method, $params = array() ) {
    // ‘key’ is used in every API call, so it’s easiest to add it here
    $params[‘key’] = $this->get_user_auth_key( $user );
    $url = $this->api_endpoint .
 
    $params_as_json = json_encode( $params );
 
    $ch = curl_init();
    if ( $ch ) {
        curl_setopt_array(
            $ch, array(
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_CUSTOMREQUEST => «POST»,
                CURLOPT_POSTFIELDS => $params_as_json,
                CURLOPT_HTTPHEADER => array(
                    ‘Content-Type: application/json’,
                    ‘Content-Length: ‘ .
                )
            )
        );
                         
        $output = curl_exec( $ch );
             
        if ( $output) {
            $json_data = json_decode( $output, true );
        }
 
        curl_close( $ch );
    }
             
    return $json_data;
}

В строке 13 функция создает URL-адрес, по которому она отправит запрос API. URL создается с использованием следующих строительных блоков:

  • Конечная точка API ( $api_endpoint ): это URL-адрес API, который хранится в отдельной переменной для ясности и упрощения обновления.
  • Метод API ( $method ): метод API для вызова. В приведенном выше коде входа check/password.json .
  • Пользовательский ключ аутентификации AlterEgo : в документации AlterEgo говорится о ключе API, но параметр, который передается как параметр GET в URL-адресе API, фактически является ключом аутентификации пользователя.

Затем функция выполняет базовый HTTPS-вызов API с параметрами, закодированными в строку JSON. Ответ также возвращается в формате JSON.

Теперь, когда все собрано, попробуй. После входа в WordPress сайт запрашивает у вас имя для входа в AlterEgo, а когда вы вводите его (используя приложение для смартфона AlterEgo), вы входите в систему и перенаправляетесь на панель администратора WordPress.

Вход в систему работает сейчас, но мы все еще можем добавить: вход в систему с помощью телефона без необходимости вводить код.

Ранее, когда мы смотрели на экран входа в AlterEgo, вы могли заметить небольшую HTML-ссылку с надписью «Войти с телефона». Теперь пришло время добавить функциональность.

Когда пользователь нажимает на эту ссылку, Ajax-запрос будет отправлен на ваш сайт WordPress, который, в свою очередь, скажет AlterEgo отправить запрос на вход на смартфон пользователя:

После отправки запроса сайт опрашивает AlterEgo, чтобы узнать, одобрил ли пользователь этот запрос (в документации AlterEgo это называется вызовом ). Наконец, когда пользователь принял вызов, вход в систему завершается, и пользователь перенаправляется на панель управления WordPress.

Теперь давайте сложим кусочки вместе. Во-первых, вызов API. Это идет внутри плагина:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Pushes a new AlterEgo challenge to the user’s mobile phone.
 */
function ajax_send_challenge() {
    $signature = $_POST[‘sig’];
    $username = $_POST[‘login’];
    $user = get_user_by( ‘login’, $username );
                 
    // Verify the signature passed with the AJAX request
    if ( $this->verify_temp_signature( $user, $signature ) ) {
        $challenge_id = $this->alterego_api_call( $user, ‘challenge/new.json’ );
        echo $challenge_id;
    } else {
        echo ‘-1’;
    }
         
    die();
}

Код довольно прост и использует многие из элементов, которые мы уже рассмотрели: он проверяет подпись аутентификации и, если подпись действительна, отправляет новый вызов API AlterEgo, challenge/new.json . Затем он возвращает идентификатор запроса, полученный в качестве ответа на вызов API.

Для проверки задачи нам нужна вторая функция:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * Polls AlterEgo to see if authentication challenge has been accepted.
 */
function ajax_check_challenge() {
    $signature = $_POST[‘sig’];
    $challenge_id = $_POST[‘id’];
    $remember_me = $_POST[‘remember_me’];
         
    $username = $_POST[‘login’];
    $user = get_user_by( ‘login’, $username );
         
    if ( $this->verify_temp_signature( $user, $signature ) ) {
        $response = $this->alterego_api_call( $user, ‘challenge/check.json’, array( ‘id’ => $challenge_id ) );
             
        if ( $response == 1 ) {
            // AlterEgo challenge OK, finish login
            $this->delete_temp_signature( $user, ‘login’ );
     
            $remember_me = ($remember_me == ‘forever’) ?
            wp_set_auth_cookie( $user->ID, $remember_me );
        }
         
        echo $response;
    } else {
        echo ‘-1’;
    }
     
    die();
}

Опять же, прежде чем продолжить вызов API, мы проверяем, действительна ли временная подпись. Затем, если все в порядке, в строке 13 функция выполняет вызов API, challenge/check.json , чтобы проверить, нажал ли пользователь « Одобрить» на своем телефоне. Если проверка прошла успешно, и пользователь принял вызов, можно выполнить вход в систему, установив файл cookie авторизации.

Обратите внимание, что вызов API использует идентификатор ajax_send_challenge полученный в ajax_send_challenge .

Чтобы сделать эти функции доступными через вызовы AJAX, нам нужно определить их в конструкторе нашего плагина. Поскольку эти функции AJAX нужны только вне области администратора, мы будем использовать префикс wp_ajax_nopriv_ для наших действий.

1
2
add_action( ‘wp_ajax_nopriv_send_challenge’, array($this, ‘ajax_send_challenge’));
add_action( ‘wp_ajax_nopriv_check_challenge’, array($this, ‘ajax_check_challenge

Теперь у нас есть строительные блоки. Последний кусок — это код JavaScript, который вызовет функции и соберет все вместе.

Это можно найти в нижней части представления файла login_form.php :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<script type=»text/javascript» >
    var ajaxurl = «<?php echo admin_url( ‘admin-ajax.php’ ); ?>»;
    var redirectUrl = «<?php echo $redirect_to; ?>»;
 
    /**
     * Does an AJAX call to check if challenge has been accepted.
     * If not yet, tries again in 3 seconds.
     */
    function checkChallenge(id) {
        var data = {
            ‘action’: ‘check_challenge’,
            ‘id’: id,
            ‘login’: ‘<?php echo $user->user_login;
            ‘sig’: ‘<?php echo $signature;
            ‘remember_me’: ‘<?php echo $remember_me;
        };
        jQuery.post(ajaxurl, data, function(res) {
            if (res == true) {
                jQuery(«#waiting-notice»).hide();
                jQuery(«#login-ok-notice»).show();
     
                window.location.replace( redirectUrl );
            } else {
                setTimeout(function() { checkChallenge(id); }, 3000);
            }
        });
    }
     
    jQuery(document).ready(function($) {
        jQuery(«#send-challenge»).click(function() {
            jQuery(«#loginform»).hide();
            jQuery(«#login_error»).hide();
            jQuery(«#sending-notice»).show();
                 
            var data = {
                ‘action’: ‘send_challenge’,
                ?>’,
                ?>’
            };
 
            $.post(ajaxurl, data, function(response) {
                jQuery("#sending-notice").hide();
                jQuery("#waiting-notice").show();
                     
                // After 3 seconds, check if challenge has been accepted
                setTimeout( function() { checkChallenge(response); }, 3000 );
            });
        })
    });
</script>

В строках 2 и 3 код передает некоторые переменные из WordPress в JavaScript: URL-адрес для отправки запросов AJAX и URL-адрес перенаправления, на который должен быть отправлен пользователь после завершения входа в систему.

Строки 9-27 — это функция проверки вызова. Он просто вызывает функцию AJAX, ajax_check_challengeкоторую мы создали ранее (которая в свою очередь вызывает AlterEgo). Затем, если все в порядке, он перенаправляет пользователя на URL перенаправления, заданный в строке 2 . Если задание еще не принято, мы используем setTimeOutдля создания таймера, который будет опрашивать задание снова через три секунды.

Строка 30 и далее — это то место, где мы используем все это. Когда пользователь нажимает на ссылку с идентификатором send-challenge, функция jQuery выполняет некоторые модификации пользовательского интерфейса, скрывая форму и показывая простой экран состояния:

Затем в строке 41 он отправляет запрос AJAX, чтобы отправить вызов на телефон пользователя. Если запрос прошел через OK, он устанавливает таймер для запуска с опросом, как описано выше.

Хорошей идеей для улучшения может быть добавление счетчика и прекращение опроса через некоторое время (при решении, сколько времени вам нужно учитывать время, необходимое для того, чтобы пойти и забрать телефон). Я пропустил это, как заметил во время обратного инжиниринга собственного кода MailChimp, что MailChimp также не имеет функции тайм-аута. Может быть, риск того, что кто-то опросит слишком долго, не так уж и велик.

Вот и все. Теперь вы узнали, как добавить шаг аутентификации AlterEgo в поток входа в WordPress, включая забавный метод аутентификации с помощью вызова, отправленного на телефон пользователя.

Будете ли вы использовать AlterEgo в следующем проекте или нет, я надеюсь, что это руководство дало представление о расширении потока входа в WordPress и использовании API через WordPress.