[Из песочницы] Авторизация в Laravel, через социальные сети (Ulogin). Просто, гибко и эффективно

Привет, друзья. Хочу сегодня поделится своим взглядом на то, как можно сделать простую и эффективную авторизацию/регистрацию пользователей через любую социальную сеть, используя плагин Ulogin. Почему через этот плагин? Потому что он может избавить разработчика от кучи головной боли, которая возникнет при синхронизации с каждой социальной сетью в отдельности. Плюс вы сможете получить данные из плагина в едином красивом формате.

Я исхожу из того, что читатель разбирается во фреймворке Laravel 5.3 Поэтому я не буду разжевывать простые вещи. Итак, с чего начать? Для начала нам нужно на странице регистрации и авторизации подключить JS плагин. Лично я делаю это через создание отдельного шаблона auth/social.blade.php В который помещаю следующий простой код:
{{-- Social buttons--}}

@section('js') @endsection

Немного объяснений к коду. urlencode используем для кодирования строки к нормальному для передачи по http виду. На redirect_uri будет приходить ответ от сервера Ulogin со статусами и данными. В поле fields мы указываем, какие данные нам нужно получить из социальной сети. В конце шаблона я подключаю сам плагин. Обратите внимание, что здесь происходит вставка кода в блок 'js', который должен быть прописан в главном шаблоне: yield ('js')

Да, один момент. Если вы работаете по SSL, то вместо 'http://' указывайте 'https://'. Тогда не будет предупреждения о небезопасной передаче данных.

Когда шаблон готов, то мы его просто подключаем на странице регистрации и залогинивания в том месте, где нам нужно, просто вставив в шаблоне одну строчку: include ('auth.social')

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

Теперь нам нужно написать бэкенд часть. А именно создать контроллер и роут. Роут будет получать ответ от сервера Ulogin и передавать его в наш контроллер.

Создаем контроллер с названием UloginController.php и прописываем в нем публичный метод login в который передаем запрос.

Создаем роут: Route: post ('ulogin', 'UloginController@login');

Есть еще один нюанс! В Laravel есть защита от CSRF атак. А значит, передача данных с другого сервера не будет иметь токена безопасности и случится ошибка безопасности. Для того, чтобы это пофиксить, необходимо в посреднике (middleware) VerifyCsrfToken.php прописать исключение для нашего роута (пути): protected $except = ['ulogin'];

Теперь, когда сервер вернет ответ на '/ulogin', токен безопасности будет проигнорирован и ошибки не произойдет.

Теперь напишем сам контроллер:

first();

        // Check exist user.
        if (isset($userData->id)) {

            // Check user status.
            if ($userData->status) {

                // Make login user.
                Auth::loginUsingId($userData->id, TRUE);
            }
            // Wrong status.
            else {
                \Session::flash('flash_message_error', trans('interface.AccountNotActive'));
            }

            return Redirect::back();
        }
        // Make registration new user.
        else {

            // Create new user in DB.
            $newUser = User::create([
                'nik' => $user['nickname'],
                'name' => $user['first_name'] . ' ' . $user['last_name'],
                'avatar' => $user['photo'],
                'country' => $user['country'],
                'email' => $user['email'],
                'password' => Hash::make(str_random(8)),
                'role' => 'user',
                'status' => TRUE,
                'ip' => $request->ip()
            ]);

            // Make login user.
            Auth::loginUsingId($newUser->id, TRUE);

            \Session::flash('flash_message', trans('interface.ActivatedSuccess'));

            return Redirect::back();
        }
    }
}

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

Какие поля в базе данных видно из кода. Для вывода текстового сообщения я использую функцию перевода trans (), что позволяет мне использовать любые языки на сайте.

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

Комментарии (26)

  • 20 января 2017 в 17:58

    0

    Вопрос: при авторизации ВКонтакте E-mail возвращает?
    • 20 января 2017 в 17:58

      0

      Да, возвращает.
    • 20 января 2017 в 18:11

      0

      Смотря как пользователь зарегистрирован в ВК. Не все сейчас регистрируются через эмейл. В этом случае эмейла нет
      • 20 января 2017 в 18:16

        0

        Ну этого нюанса я не знаю, честно говоря. Если сервер мыло не передаст, то скрипт может бросить исключение. Нужно тогда ставить проверку на существование мыла.
  • 20 января 2017 в 18:25

    0

    Для проверки существования мыла, нужно вставить проверку:
    // Check exist email.
            if (isset($user['email']) && !empty($user['email'])) {
                // Здесь основной код
            }
            \Session::flash('flash_message_error', trans('interface.NotEmail'));
    
            return Redirect::back();
    
  • 20 января 2017 в 18:36

    0

    Ааа, господа и дамы. Стормозил, простите. Если какого-либо поля в социальной сети не будет, включая мыло, то во всплывшем окошке ulogin попросит пользователя заполнить недостающие поля. Поэтому, если Вконтакте не содержит мыло, то пользователю предложат его ввести отдельно во время авторизации на вашем сайте. Так что проблемы как бы и нет.
  • 20 января 2017 в 18:45 (комментарий был изменён)

    0

      // Find user in DB.
            $userData = User::where('email', $user['email'])->first();
    
            // Check exist user.
            if (isset($userData->id)) {
               ...
            }
            // Make registration new user.
            else {
               ...
                ]);

    А почему бы не использовать findOrNew? И к тому же, можно проверку сделать намного проще в Вашем случае:


    if($userData)

    Ведь если такой пользователь не будет найдет, в $userData будет null.

    • 20 января 2017 в 18:49

      0

      firstOrCreate, если точнее.

      • 20 января 2017 в 18:57

        0

        Можно, конечно можно так, как и вы предлагаете. Так будет даже проще. Но все равно нужно будет делать условие для вывода разных сообщений. Поэтому я решил и не морочить себе голову.
        • 20 января 2017 в 19:07

          0

          Сообщений каких?


          \Session::flash('flash_message', trans('interface.ActivatedSuccess'));
          \Session::flash('flash_message_error', trans('interface.AccountNotActive'));

          Этих?

          • 20 января 2017 в 19:11

            0

            Да.
            • 20 января 2017 в 19:14 (комментарий был изменён)

              0

              Так ведь firstOrCreate вернет Вам инстанс юзера, его статус и можно проверять для вывода сообщения (или его отсутствия). Еще есть замечательный метод exists, который поможет Вам в выводе сообщения об успешной регистрации.

              • 20 января 2017 в 19:21

                0

                Согласен, но ведь все равно нужно будет писать условие. А значит как именно сделать проверку и регистрацию становится не особо принципиальным. Хотя вы говорите про Ларавел вей, что очень правильно.
              • 20 января 2017 в 19:25

                0

                Да, кстати. Почему не стоит тут применять firstOrCreate. Дело в том, что аргументы нужно прописывать в методе, полученные от социальной сети. И все они должны совпадать с существующим профилем. Но может получится так, что пользователь изменил некоторые данные в профиле. Тогда будет произведена попытка регистрации и ошибка по не уникальности емаил.
              • 20 января 2017 в 19:54

                0

                если сделать так
                $newUser = User::firstOrCreate([
                                'nik' => $user['nickname'],
                                'name' => $user['first_name'] . ' ' . $user['last_name'],
                                'avatar' => $user['photo'],
                                'country' => $user['country'],
                                'email' => $user['email'],
                                'password' => Hash::make(str_random(8)),
                                'role' => 'user',
                                'status' => TRUE,
                                'ip' => $request->ip()
                            ]);
                

                то последует попытка создать нового пользователя, т.к. Hash: make ('any_string') будет все время различаться
                • 20 января 2017 в 20:07

                  0

                  Кстати, тоже хорошее замечание.
  • 20 января 2017 в 19:47

    +1

    https://github.com/laravel/socialite — почему не использовали этот способ?
    • 20 января 2017 в 20:06

      0

      Потому что то решение, которое написал я, быстрее написать, чем установить и настроить пакет socialite. Плюс вся «магия» и головная боль по работе с социалками лежит на сервере ulogin. Я же просто работаю с json массивом. Плюс ко всему прочему, я использую всякие диалоговые окошки для мгновенной авторизации, формируя их аяксом на лету, и прочие плюшки. Поэтому мне гораздо проще работать со своим решением (простым, как угол дома, всего с одним роутом и одним контроллером), чем допиливать напильником чужое. Хотя каждый выбирает то, что удобно/выгодно/и т.д. ему конкретно. Я же люблю иногда создавать свои решения под мои конкретные задачи. И если случается где-то трабла, то я не жду, пока автор пакета его допилит, пофиксит, выпустит новый релиз. Я свое решение смогу доработать/пофиксить в течении нескольких минут. Но, опять таки, каждый выбирает свой путь. И очень часто многие разработчики хотят поставить пакет и забыть. Но я часто использую нетрадиционные и нестандартные подходы, поэтому большинство готовых решений мне просто не подходит.
    • 20 января 2017 в 20:10

      0

      Плюс, этот пакет работает только с рядом социалок: Laravel Socialite provides an expressive, fluent interface to OAuth authentication with Facebook, Twitter, Google, LinkedIn, GitHub and Bitbucket. А ulogin со всеми существующими.
      • 20 января 2017 в 20:20

        0

        тут смотря с какой стороны посмотреть и для чего это будет использоваться. мне спокойнее, если я как можно меньше завишу от внешних сервисов, да и никто не мешает сделать с socialite так:
        public function redirectToProvider($driver)
            {
                if(empty(config('services.'.$driver))){//нет драйвера - нужно что-то сделать
                    abort(404);
                }
                return Socialite::driver($driver)->redirect();
            }
        

        этот пакет работает только с рядом социалок:

        нужно больше социалок? не вопрос — https://socialiteproviders.github.io/
        • 20 января 2017 в 20:29

          0

          Я с вами согласен в том, что чем меньше от внешних сервисов зависишь, тем лучше. Но от этого все равно не уйти, к сожалению. Сегодняшние web решения много от чего внешнего зависят. Да хоть те же скрипты для бутстрапа, фронтенд фреймворка и т.д. А написать свое грамотное и независимое решение по авторизации через социалки из коробки — это очень приличный кусок работы. А сделать его еще и гибким, с легкой интеграцией в фронтенд фроймворки, с красивой передачей данных через свое API и т.д., то в какой то момент приходит понимание, что уже ничего не хочется.
          • 20 января 2017 в 20:33 (комментарий был изменён)

            0

            библиотека и внешний сервис — немного разные зависимости. Если библиотека окажется с багом — я могу отправить pull-request туда, а если не примут — использовать свой форк на крайний случай. А если тот же самый ulogin ляжет — что тогда делать?
            • 20 января 2017 в 20:38

              0

              Тогда в ритме танго отключать виджет (закомментировать в шаблоне одну строчку) и всем юзерам придется жать кнопку восстановления своего пароля. Если вспомнят свой email. Торба будет! Тут без вариантов. Но вот пару дней назад глюканул фэйсбук по всему миру, и народ не мог зайти в свои аккаунты. Так что тут тоже вопрос интересный. Если пользователь новый, то он может зарегистрироваться в родной форме регистрации. А для зареганных можно хранить очень долгую куку.
        • 20 января 2017 в 20:32

          0

          Но огромный минус внешнего сервера авторизации в том, что если ляжет он, то и наш сайт тоже приболеет. Это очевидно. Но как тут быть, что лучше/проще/дешевле — это уже философский вопрос.
  • 20 января 2017 в 20:16

    0

    uLogin — штука хорошая, удобная, но — спорная. Не зависит ни от какого фреймворка, написано куча плагинов… Но жесткая привязка авторизации к чужому сайту у меня в свое время перевесило чашу весов в пользу как вы выражаетесь — «головной боли», и hybridauth
    • 20 января 2017 в 20:22

      0

      Здесь с вами спорить тяжело. Это сложный выбор. Но взять и ручками написать авторизацию с кучей социалок, следить за изменением их API и так далее, это тоже задача непростая. Везде завести сигнатуры, везде зарегаться, как разработчик и еще куча всего. Если речь идет о трех-шести социалках, то может через OAuth и надежнее будет. Но когда социалок много — это headache еще та. Выбор сложный.

© Habrahabr.ru