Как подключить авторизацию через соц сети - vorst.ru

Авторизация


Регистрация и авторизация через социальные сети

Объяснение принципа авторизации с помощью аккаунта в социальной сети. Инкапсуляция обработки данных различных социальных сетей и приведение данных к единому виду. Инкапсуляция процесса регистрации пользователя. Упрощение процесса авторизации в контроллере. Определение виджета, не зависящего от расширения и работающего с любыми иконками.

Авторизация

Регистрация и авторизация через социальные сети

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

Авторизация с помощью социальной сети позволяет избежать повторного заполнения формы регистрации. При этом защищенность вашего аккаунта в социальной сети никак не страдает. Вы просто подтверждаете, что вы это вы. Как подключить эту услугу к проекту используя расширение yiisoft/yii2-authclient?


Когда пользователь пытается войти на сайт с помощью стороннего сервиса происходит запрос данных пользователя на этом сервисе. Данные возвращаются в объекте client.

Стандартный запрос возвращает имя пользователя, а вот email может уже и не передать. В социальной сети вы можете установить флаг в настройках - "Передавать email сторонним сервисам" (или подобный флаг). Но не факт, что так поступят все посетители вашего сайта. И это первая проблема.

Вторая в том, что названия возвращаемых атрибутов разные для разных социальных сетей.

Инкапсуляция получения данных

Напишем класс common/components/SocialContact.php, в конструктор которого передадим объект client. Конструктор определяет, от какой социальной сети вернулся объект и, в зависимости от этого, формирует данные.

Если email не передан, то генерируется нечто похожее на email. Это необходимо для выполнения в дальнейшем процедуры регистрации пользователя.

namespace common\components;
use Yii;
use yii\base\BaseObject;
use yii\base\InvalidValueException;
use common\models\SocialLink;
use common\models\User;
class SocialContact extends BaseObject
{
  public $id;
  public $name;
  public $email;
  /**
   * Retrieve id, name, email and, may be more.
   * 
   * @param object social client
   * @param array config
   */
  public function __construct($client, $config = [])
  {
    $client_id = $client->getId();
    $attributes = $client->getUserAttributes();
    $this->id = (string)$attributes['id'];
    switch ($client_id) {
    case 'yandex' :
      $this->name = $attributes['first_name'] . ' ' . 
        $attributes['last_name'];
      $this->email = isset($attributes['default_email']) 
        ? $attributes['default_email'] 
        : false;
      break;
    case 'facebook' :
      $this->name = $attributes['name'];
      $this->email = isset($attributes['email']) 
        ? $attributes['email'] 
        : false;
      break;
    // cases for other clients
    }
    // if email not setted then make it
    if (!$this->email)
      $this->email = "{$attributes['id']}@{$client_id}.net";
    parent::__construct($config);
  }

Связь между User и социальной сетью

Получив объект от стороннего сервиса и приведя данные к единому виду нужно добавить нового пользователя. При этом новому пользователю присваивается случайный пароль. В дальнейшем имя и пароль будут использоваться для входа. Пользователь никогда не узнает свой пароль и даже может не подозревать, что и email используется не его, а сгенерированный. Почему, тем не менее, все будет работать верно?

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

Пользователь может быть зарегистрирован в разных социальных сетях, поэтому нужна связь между социальной сетью и аккаунтом пользователя на вашем сайте. Создадим модель common/models/SocialLink.php. Где source это "yandex", "vkontakte" и прочее, а source_id это внутренний код социальной сети.

<?php
namespace common\models;
use yii\db\ActiveRecord;
use common\models\User;
class SocialLink extends ActiveRecord
{
  public static function tableName()
  {
    return '{{%social_link}}';
  }
  public function rules()
  {
    return [
      [['user_id', 'source', 'source_id'], 'required'],
      ['user_id', 'integer'],
      [['source', 'source_id'], 'string', 'max'=>255],
    ];
  }
  public function getUser()
  {
    return User::findOne($this->user_id);
  }
}

И таблицу.

<?php
use yii\db\Migration;
class m180608_123330_create_social_link extends Migration
{
  public $table = '{{%social_link}}';
  public function up()
  {
    $tableOptions = null;
    if ($this->db->driverName === 'mysql') {
      $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
    }
    $this->createTable($this->table, [
      'id' => $this->primaryKey(),
      'user_id' => $this->integer()->notNull(),
      'source' => $this->string(255)->notNull(),
      'source_id' => $this->string(255)->notNull(),
    ], $tableOptions);
    $this->addForeignKey ('FK_social_link_user', 
      $this->table, 'user_id', '{{%user}}', 'id', 'CASCADE');
  }
  public function down()
  {
    $this->dropTable($this->table);
  }
}

User registration

Добавим процедуру регистрации в класс common/components/SocialContact.php. Если пользователь пытается войти с помощью социальной сети, а он уже прошел процедуру обычной регистрации, то выдаем сообщение, что надо бы использовать логин и пароль, указанные при регистрации. Иначе создаем нового пользователя и связываем его с социальной сетью. Если что-то не получается, выдаем сообщение об ошибке.

class SocialContact extends BaseObject
{
  ...
  public function registration($client_id)
  {
    if (User::find()->where(['email' => $this->email])->exists()) {
      Yii::$app->getSession()->setFlash('error', [
        Yii::t('app', 'User with {email} for {client} have been exist, ' .
          'but not linked to each other. Try to login with name and password.', [
          'email' => $this->email,
          'client' => $client_id,
        ]),
      ]);
    } else {
      $password = Yii::$app->security->generateRandomString(6);
      $user = new User([
        'name' => $this->name,
        'email' => $this->email,
        'password' => $password,
        'status' => User::STATUS_ACTIVE,
      ]);
      $user->generateAuthKey();
      $user->generatePasswordResetToken();
      $transaction = $user->getDb()->beginTransaction();
      if ($user->save()) {
        $social_link = new SocialLink([
          'user_id' => $user->id,
          'source' => $client_id,
          'source_id' => $this->id,
        ]);
        if ($social_link->save()) {
          $transaction->commit();
          Yii::$app->user->login($user);
        } else {
          throw new InvalidValueException($this->showErrors($social_link)); 
        }
      } else {
        throw new InvalidValueException($this->showErrors($user)); 
      }
    }
  }
  private function showErrors($model)
  {
    $out = 'Can\'t save ' . $model->tableName() . "\n";
    foreach($model->getErrors() as $field => $messages) {
      $out .= "«{$field}»\n";
      foreach($messages as $message) {
        $out .= "{$message}\n";
      }
      $out .= "\n";
    }
    return $out;
  }
}

Controller

Действие в контроллере frontend/controllers/SiteController.php.

class SiteController extends Controller
{
  public function actions()
  {
    return [
      'auth' => [
        'class' => 'yii\authclient\AuthAction',
        'successCallback' => [$this, 'onAuthSuccess'],
      ],
    ],
  }
  public function onAuthSuccess($client)
  {
    $social_contact = new SocialContact($client);
    $social_link = SocialLink::find()->where([
      'source' => $client->getId(),
      'source_id' => $social_contact->id,
    ])->one();
    if (Yii::$app->user->isGuest) {
      if ($social_link) { // authorization
        Yii::$app->user->login($social_link->user);
      } else { // registration
        $social_contact->registration($client->getId());
      }
    } else { // the user is already registered
      if (!$social_link) { // add external service of authentification
        $social_link = new SocialLink([
          'user_id' => Yii::$app->user->id,
          'source' => $client->getId(),
          'source_id' => $social_contact->id,
        ]);
        $social_link->save();
      }
    }
  }

Регистрация приложений

Когда отправляется запрос стороннему сервису (социальной сети), с просьбой сообщить данные пользователя, можно представить, что ответ присылает приложение. Приложения создаются в каждой социальной сети по разному. Например для сети ВКонтакте смотрите здесь. Получив два параметра - идентификатор и секретный ключ приложения, нужно зарегистрировать их в конфигурации common/config/main.php. По Id и секрету приложение определяет, что ваш сайт имеет право на запрос данных пользователя.

<?php
return [
  'components' => [
    'authClientCollection' => [
      'class' => 'yii\authclient\Collection',
      'clients' => [
        'yandex' => [
          'class' => 'yii\authclient\clients\Yandex',
          'clientId' => 'yandex application id',
          'clientSecret' => 'yandex client secret string',
        ],
        'vkontakte' => [
          'class' => 'yii\authclient\clients\VKontakte',
          'clientId' => 'vk application id',
          'clientSecret' => 'vk client secret string',
        ],

Widget

Осталось разместить ссылки в форме входа. Наряду с обычным вариантом входа на сайт, предложить вход с помощью ссылок на социальные сети. Те, что зарегистрированы в файле конфигурации. Ссылки размещает виджет frontend/widgets/SocialCredentials.php, используя информацию о зарегистрированных приложениях.

<?php 
namespace frontend\widgets; 
use yii\base\Widget; 
class SocialCredentials extends Widget 
{ 
  public $view = 'socialCredentials'; 
  public $credentials = []; 
  public $icons = []; 
  public function init() { 
    parent::init(); 
    if(!$this->credentials) 
      $this->credentials = \Yii::$app->get('authClientCollection')->clients; 
    if(!$this->icons) 
      $this->icons = \Yii::$app->params['icons']; 
  } 
  public function run() 
  { 
    echo $this->render($this->view, [ 
      'credentials' => $this->credentials, 
      'icons' => $this->icons, 
    ]); 
  } 
}

Ссылки удобно выводить с помощью иконок. Например Font Awesome. Можно определить соответствие социальных сетей и иконок в параметрах при вызове виджета или в файле frontend/config/params.php.

<?php   
return [
'icons' => [ 
    'yandex' => 'yandex', 
    'vkontakte' => 'vk', 
    'github' => 'github', 
    'google' => 'google-plus-g', 
  ],

Вариант представления frontend/widgets/views/socialCredentials.php используемый на данном сайте.

<?php
use yii\helpers\Url;
?>
<p><?= \Yii::t('app', 'You can log in using the social network.') ?></p>
<ul class="list-inline">
<?php foreach($credentials as $id => $client): ?>
  <span class="fa-stack fa-lg">
    <li class="fa fa-circle fa-stack-1x">
      <a href="<?= Url::to(['site/auth', 'authclient' => $id]) ?>" 
        title="<?= \Yii::t('app', 'Login with') . ' ' . ucfirst($client->id) ?>">
        <i class="fa fa-circle fa-stack-2x"></i>
        <i class="fab fa-<?= $icons[$id] ?> fa-stack-1x fa-inverse"></i>
      </a>
    </li>
  </span>
<?php endforeach; ?>
</ul>

Как это работает на данном сайте.

Заключение

В конструкторе класса common/components/SocialContacts.php присваиваются значения переменным, в зависимости от социальной сети. Если возникнет необходимость добавить еще одну, не определенную еще, социальную сеть, то придется переписывать класс. Попробуем переписать конструктор так, чтобы новые возможности не приводили к изменениям. Но об этом в следующей статье.

При подготовке статьи использовано руководство Quick start расширения yiisoft/yii2-authclient. Код, приведенный в статье, используется в расширении sergmoro1/yii2-user.

Оставьте комментарий

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