Печать

Авторизация через ВКонтакте, Mail.ru и другие для самых начинающих

Итак, начнем с ВКонтакте. Заходим на страницу подключения сайта, в дальнейшем настройки подключенного сайта будут доступны вам на странице управления приложениями, там мы узнаем ID приложения и секретный ключ, который естественно раскрывать никому нельзя.
На странице, на которой предполагается кнопочка «Войти через ВКонтакте» требуется добавить в head страницы

<script type="text/javascript" src="http://userapi.com/js/api/openapi.js?34"></script>


И инициализировать приложение:

VK.init({apiId: __APP_ID___});


Теперь нужно показать пользователю кнопку через вызов виджета. В качестве параметров Auth принимает id элемента, в который нужно отобразить виджет, ширину и адрес страницы, куда мы будем перенаправлены после попытки доступа.

      <div id="vk_auth"></div>
      <script type="text/javascript">
      VK.Widgets.Auth("vk_auth", {width: "300px", authUrl: '/vklogin.php?'});
      </script>


Что увидит пользователь:


После щелчка на «Войти через ВКонтакте» пользователя кидает на страницу вида vkontakte.ru/widget_auth.php?act=a_auth_user&app=__APP_ID__&hash=d2d47b3c85d1a091a8, затем на url, указанный вами в параметре AuthUrl при вызове виджета. Кидает со следующими GET параметрами:
first_name (имя), hash (используется для проверки того действительно ли запрос пришел от контакта, а не хакер Вася пытается авторизоваться под чужими данными), last_name (Фамилия), photo (большая аватарка, 119 пикселей шириной), photo_rec (маленькая аватарка, 50х50), uid (id пользователя). Пришедшие к нам параметры я после фильтрации сохранил с такими же именами в глобальной области видимости.
Теперь нам нужно сделать скрипт vklogin.php, в котором мы будем проверять правильность пришедших данных и, либо авторизовывать пользователя если он уже есть в нашей базе, либо создавать новый аккаунт для пользователя зашедшего к нам впервые.

if ($_REQUEST['hash']==md5('2445355'.$uid.'__SECRET_KEY__')) {
  //доверяем вконтактику, и далее полагаем, что пользователь действительно авторизован там
  //для учетных записей пользователей я решил выделить логины вида vk-********
  $result = mysql_query("SELECT id, random, password FROM tracker_users WHERE username = 'vk-$uid'");
  setcookie('uid','');
  setcookie('pass','');
  if (mysql_num_rows($result)) {
    //пользователь авторизован, просто пересоздадим куки
    $user = mysql_fetch_assoc($result);
    mysql_query("UPDATE tracker_users SET name = '$name' WHERE username = 'vk-$uid' LIMIT 1");
    setcookie('pass',md5($user['random'].$user['password'].$user['random']));
    setcookie('uid',$user['id']);
  } else {
    //добавим запись в таблицу пользователей
    $random = mt_rand(100000,999999);
    $pwd = $uid . 'verysecretlonglongword-';
    $pid=md5(uniqid(rand(),true));
    mysql_query("INSERT INTO tracker_users
      (username, name, password, random, id_level, email, style, language, flag, joined, lastconnect, pid, time_offset) VALUES
      ('vk-$uid', '$name', '"
. md5($pwd) . "', $random, 3, '', 5, 7, 0, NOW(), NOW(),'$pid', '0')");
    //вставили строчку, теперь создадим куки и перебросим на другую страницу
    setcookie('pass',md5($random.md5($pwd).$random));
    setcookie('uid',mysql_insert_id());
  }
  header("Location: /index.php");
}


* This source code was highlighted with Source Code Highlighter.


На сайте, на котором мне необходимо было сделать авторизацию в куках сохраняется id пользователя и хеш от соли и хеша пароля. Решение, конечно, не идеальное, но менять полностью весь движок в мои планы не входило. Если пользователь у нас уже есть мы его авторизуем, создав куки, если он впервые, то добавляем его в базу. Также при каждом заходе мы обновляем данные об именах, потому что пользователь мог его поменять, а мы хотим актуальности.
Кроме этого необходимо перед началом использования подобной авторизации убедиться, что в базе нет пользователей с логинами вида vk- (или с теми, которые вы хотите использовать). Также необходимо запретить регистрироваться через обычную регистрацию используемого движка с логинами вида vk- и запретить пользователям из социальной сети менять себе пароль и, опционально, аватарку с отображаемым именем. Для сайтов, в которых критично использование электронной почты зарегистрированных пользователей необходимо будет также ознакомить ваш движок с тем фактом, что у этих пользователей нет электронной почты.
Опционально можно возле каждого вывода имени пользователя на сайте отдельно выделять, что он авторизовался через социальную сеть:


Продолжаем с майл.ру, facebook и твиттером.
Для начала как всегда нам необходимо зарегистрировать в социальной сети свое приложение (у майла четкое разделение между приложениями и сайтами, но я по привычке буду говорить «приложение»). Кстати в процессе регистрации вы увидите от трех до пяти js ошибок, когда-нибудь mail.ru их исправит. После регистрации вам доступны ID приложения, приватный ключ (ничего приватного, используется для js-api вызовов, виден всем) и секретный ключ (используется для сервер-сервер взаимодействия, не говорите никому и не забывайте в комментариях).
На странице настройки приложения мы видим еще один интересный параметр «Адрес страницы receiver.html». Если не ставит целью узнать для чего же она нужна, то единственное что мы узнаем из справки:

Для корректной работы API сайтов необходимо разместить файл receiver.html на вашем домене.


Если не планируете использовать JS API, то этот файл вам не нужен. На страничке авторизации/регистрации мы разместим кнопку. PR-отдел майла очень строго запрещает что-либо менять в логотипе, поэтому мы используем его в строго таком виде, каким он нам предоставлен.

Ссылка на картинке будет либо такая:

connect.mail.ru/oauth/authorize?client_id=APP_ID&response_type=token&redirect_uri=_REDIRECT_URI&host=http://HOST.com


в которой вам необходимо указать номер вашего приложения, ваш домен в параметре host и URI для перенаправления в параметре redirect_uri.
Второй вариант — сделать промежуточную страницу, использующую JS API, ниже родной пример с майла с убивающими отступами в один пробел.

<script type="text/javascript" src="http://cdn.connect.mail.ru/js/loader.js"></script>
<script type="text/javascript">

  mailru.loader.require('api', function() {
   mailru.connect.init('_APP_ID_', '__PRIVATE_KEY__' );
   mailru.events.listen(mailru.connect.events.login, function(session){
   window.location.reload();
   });
   mailru.events.listen(mailru.connect.events.logout, function(){
   window.location.reload();
   });
   mailru.connect.getLoginStatus(function(result) {
   if (result.is_app_user != 1) {
    mailru.connect.initButton();
   } else {
    mailru.common.users.getInfo(function(result){console.log(result[0].uid)});
    location.href='/mail.login.php';
   }
   });
  });

</script>
<a class="mrc__connectButton">вход@mail.ru</a>


* This source code was highlighted with Source Code Highlighter.


Зачем может понадобиться второй вариант? Как показала жизнь если пользователь щелкнет по ссылке из первого варианта и попадет на страничку с вводом имени пользователя и пароля (если он не авторизован в майле в данный момент), то хоть и в строчке адреса https, и домен mail.ru, но пользователь может запаниковать и подумать, что у него пытаются украсть логин и пароль. Непонятно почему, но если показывать эти же поля во всплывающем окне (видимо более привычном и чаще используемом), то паникующих пользователей меньше.
Итак, вне зависимости от того какой вариант мы выбрали, в любом случае в результате пользователя перебросит на страницу mail.login.php или другую, указанную вами. В случае успешной авторизации пользователя на стороне mail.ru на вашем домене он создает cookie с названием mrc, которую можно разобрать следующим образом:
parse_str(urldecode($_COOKIE['mrc']),$array);
И тут выясняются не сильно приятные моменты: для подписи/проверки подписи API использует сравнение md5(список всех параметров запроса, отсортированных в алфавитном порядке без знаков &) и параметр sig из mail.ru-шной cookie. Поэтому нам нужны две небольшие функции, предложенные нам командой майла:

function sign_client_server(array $request_params, $uid, $private_key) {
 ksort($request_params);
 $params = '';
 foreach ($request_params as $key => $value) {
   $params .= "$key=$value";
 }
 return md5($uid . $params . $private_key);
}

function sign_server_server(array $request_params, $secret_key) {
 ksort($request_params);
 $params = '';
 foreach ($request_params as $key => $value) {
   if ($key!='sig') {
   $params .= "$key=$value";
   }
 }
 return md5($params . $secret_key);
}


* This source code was highlighted with Source Code Highlighter.


Функцию sign_server_server я немного подправил, добавив проверку того, чтобы ключ элемента массива не был равен 'sig'. Это сделано для того, чтобы перед каждой проверкой не удалять элемент из массива или не копировать массив в другой для проверки.
Для начала проверим действительно ли ответ пришел от социальной сети (в массиве $array у нас информация из майлрушной куки):

if (sign_server_server($array,$secretkey)==$array['sig']) {


Что удивительно если бы мы использовали «родную» функцию для проверки подписи не удалив элемент sig из массива, то сравнение возвращало бы ложь. Так как изначальная информация от майла достаточно скудна (только идентификаторы и время истечения сессии пользователя на майле, которое нам собственно незачем), нам необходимо получить имя, фамилию и номер кредитки через метод users.getInfo. Это делается вот таким образом:

  //получим анкетную инфу
  $params = array(
    "method"=>"users.getInfo",
    "app_id"=>"640345",
    "session_key"=>$array['session_key'],
    "uids"=>$array['vid'],
    "secure"=>"1"
  );
  
  $url = "http://www.appsmail.ru/platform/api?".http_build_query($params)."&sig=".sign_server_server($params,$secretkey);
  $response = json_decode(file_get_contents($url));


* This source code was highlighted with Source Code Highlighter.


Для формирования запроса велосипед, как делают многие, изобретать не нужно, есть встроенная функция http_build_query. $response возвращает нам массив объектов пользователей (в нашем случае в массиве 1 элемент), из которого мы получим имя, фамилию, ник, идентификатор и аватарку. Мы делаем это перед запросами к базе, потому что в любом случае нам эти данные понадобятся (мы обновим имя, фамилию и аватарку).
После того как мы получили данные от социальной сети, пора поработать на нашей стороне. Приняв критику и немного абстрагировавшись от типа используемой базы данных:

$stmt = $dbh->prepare("SELECT id FROM tracker_users WHERE username = :username");
$stmt->bindParam(":username", "mm-{$array['vid']}", PDO::PARAM_STR, 23);
$stmt->execute();


Для пользователей из социальной сети МойМир я решил выделить логины вида «mm-20цифр». Да, именно 20-тизначное число в качестве айдишника, по запарке не сделайте приведение к обычному целому. После этого если пользователь уже есть в нашей базе, мы просто обновим его данные, создадим ему наши куки и перебросим на главную страницу, если же его нет, то мы создадим запись в таблице и выполним действия из прошлого пункта.

if ($stmt ->fetchColumn() > 0) {
//если пользователь есть
} else {
//если пользователя нет
}


Интереса в полном изложении всех запросов и мелочей, как выяснилось из прошлой статьи, ни у кого нет, поэтому остановимся на следующем этапе интеграции — аватарки из социальных сетей. Маленькая картинка (идеально для отображения возле комментариев) находится в $response[0]->pic_small, а вконтактовскую аватарку из прошлой статьи можно получить из GET параметров $_GET['photo_rec']. Надо отметить, что майловские аватарки имеют размер 45 на 45, в то время как из ВКонтакта — 50 на 50. Радует, что оба варианта квадратные и их можно привести к одному размеру. Если хочется привести к 50 на 50, то из майла лучше взять увеличенную аватарку 90 на 90.

Интересная статья? Поделись ей с другими: