перейти к части...
База данных
У одного пользователя может быть несколько аккаунтов в разных социальных сетях (Facebook, Twitter, Instagram, и т.д.). Давайте создадим отдельную таблицу для хранения информации о всех аккаунтах пользователя. Выполните команду php artisan make:model SocialAccount -m
, чтобы создать новую модель и связанный файл миграции. Отредактируйте миграцию следующим образом:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSocialAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('social_accounts', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->string('provider_id');
$table->string('provider');
$table->string('token');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('CASCADE');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('social_accounts');
}
}
user_id
ссылается на пользователя, которому принадлежит аккаунт; provider_id
- id аккаунта в социальной сети; provider
- идентификатор/имя социальной сети; token
- токен, предоставляемый социальной сетью после успешной аутентификации. На самом деле токен нам не нужен, так как мы не планируем делать запросы к социальной сети от имени пользователя, однако мы оставим его просто на всякий случай. Создайте новую таблицу в БД выполнив команду миграции php artisan migrate
.
Наконец, откройте файл модели app/SocialAccount.php
и добавьте отношение с сущностью пользователя user()
. Также, отключите проверку на mass assignment задав пустой массив свойству $guarded
.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class SocialAccount extends Model
{
protected $guarded = [];
public function user()
{
return $this->belongsTo('App\User');
}
}
Ключ API и настройки Facebook
Нам нужно зарегистрировать наше приложение в Facebook и получить необходимые ключи. Перейдите на Facebook для разработчиков и добавьте новое приложение. Затем перейдите в раздел базовых настроек приложения (basic settings
) и добавьте домен вашего сайта. Нажмите + Add Platform
, выберите Website
и введите URL вашего сайта. Я пользуюсь встроенным сервером Laravel (artisan), поэтому в качестве домена я указал localhost
, а в качестве URL http://localhost:8000
. Сохраните настройки.
Откройте файл config/services.php
и добавьте следующее в конец файла конфигурации:
'facebook' => [
'client_id' => '',
'client_secret' => '',
'redirect' => '/social-auth/facebook/callback',
],
Скопируйте и вставьте client_id
и client_secret
со страницы настроек Settings
(на сайте Facebook эти поля называются App ID
и App Secret
соответственно).
Вернитесь в браузер и перейдите в раздел Product -> Facebook Login -> Settings
. Добавьте "http://localhost:8000/social-auth/facebook/callback" в поле Valid OAuth redirect URIs
. Замените localhost:8000
в ссылке на ваш реальный домен (если вы не используете встроенный локальный сервер). Сохраните изменения.
Маршруты
API социальных сетей работают по протоколу OAuth. Для работы с ним нам нужно добавить 2 маршрута. Откройте файл routes/web.php
и добавьте следующее:
Route::get('/social-auth/{provider}', 'Auth\SocialController@redirectToProvider')->name('auth.social');
Route::get('/social-auth/{provider}/callback', 'Auth\SocialController@handleProviderCallback')->name('auth.social.callback');
Как вы, возможно, поняли, наши маршруты не привязаны к какой-то конкретной социальной сети.
View
Добавьте ссылку на авторизацию через Facebook где-нибудь в одном из ваших view (шаблонов). Один и тот же маршрут будет использован как для регистрации, так и для авторизации (поэтому я бы использовал отдельный включаемый шаблон Blade для этой ссылки на вашем месте). Наша ссылка должна выглядеть примерно так (я использую бесплатные иконки от Font Awesome):
<a href="{{ route('auth.social', 'facebook') }}" title="Facebook">
<i class="fa fa-2x fa-facebook-square"></i>
</a>
Контроллер
Пришло время поработать над сердцем нашей системы аутентификации. Давайте создадим контроллер выполнив команду php artisan make:controller 'Auth\SocialController'
. Для начала мы создадим метод, который будет перенаправлять пользователей на страницу аутентификации Facebook.
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;
class SocialController extends Controller
{
public function redirectToProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
}
Зайдите на свой сайт и проверьте кнопку Facebook'а - перенаправление (редирект) должно работать. Нажмите кнопку подтверждения на сайте Facebook и вас далжно перекинуть обратно на наш сайт на адрес /social-auth/facebook/callback
. Пока что у нас нет метода в контроллере, который отвечал бы на этот маршрут. Давайте создадим его.
public function handleProviderCallback($provider)
{
$user = Socialite::driver($provider)->user();
}
Здесь мы можем получить базовую информацию о пользователе от Facebook'а. Видите как это все просто делается с помощью Socialite? Вы можете посмотреть какую информацию вовзращет Facebook, выведя ее через dd($user)
. Дальше нам нужно либо создать нового пользователя, либо найти существующего в нашей БД основываясь на полученных данных. После этого мы авторизируем пользователя на нашем сайте и перенаправим на нужную страницу.
public function handleProviderCallback($provider)
{
$socialiteUser = Socialite::driver($provider)->user();
$user = $this->findOrCreateUser($provider, $socialiteUser);
auth()->login($user, true);
return redirect('/');
}
Замените путь внутри redirect('/')
на то, что вам нужно. Если вы не хотите "запоминать" авторизированного пользователя, не передавайте true
в качестве второго параметра в метод login()
.
Теперь нам нужно создать метод findOrCreateUser()
. Этод метод будет выполнять несколько действий. Сначала мы постараемся найти существующего пользователя в таблице social_accounts
по его id в сети Facebook. В случае неудачи мы выполним поиск в таблице users
по полю email
. Если и это не даст результата - мы создадим ("зарегистрируем") нового пользователя.
public function findOrCreateUser($provider, $socialiteUser)
{
if ($user = $this->findUserBySocialId($provider, $socialiteUser->getId())) {
return $user;
}
if ($user = $this->findUserByEmail($provider, $socialiteUser->getEmail())) {
$this->addSocialAccount($provider, $user, $socialiteUser);
return $user;
}
$user = User::create([
'name' => $socialiteUser->getName(),
'email' => $socialiteUser->getEmail(),
'password' => bcrypt(str_random(25)),
]);
$this->addSocialAccount($provider, $user, $socialiteUser);
return $user;
}
Думаю, логика метода понятна. Если пользователь пытается зарегистрироваться с одного и того же аккаунта социальной сети - он будет авторизирован на нашем сайте. Если пользователь пытается войти с другого аккаунта другой сети, но с тем же самым email адресом - этот другой аккаунт будет привязан к тому же пользователю в нашей БД. Если пользователь не был найден ни по аккаунту соц сети, ни по email адресу - новый пользователь будет создан. В качестве пароля для нового пользователя просто генерируем случайную большую строку, которую потенциально никто не сможет угадать.
Ниже недостающие методы. Надеюсь, здесь все понятно без объяснений.
public function findUserBySocialId($provider, $id)
{
$socialAccount = SocialAccount::where('provider', $provider)
->where('provider_id', $id)
->first();
return $socialAccount ? $socialAccount->user : false;
}
public function findUserByEmail($provider, $email)
{
return User::where('email', $email)->first();
}
public function addSocialAccount($provider, $user, $socialiteUser)
{
SocialAccount::create([
'user_id' => $user->id,
'provider' => $provider,
'provider_id' => $socialiteUser->getId(),
'token' => $socialiteUser->token,
]);
}
Важная поправка
Важно учесть, что к некоторым аккаунтам соц сетей не привязан email, так как пользователь зарегистрировался по номеру телефона. Большинство социальных сетей (включая Facebook) не предоставляют доступ к номерам телефонов пользователей по API. Поэтому, в таких случаях нам не получится связать 2 разных аккаунта одного и того же пользователя вместе, вместо этого на нашем сайте будут созданы 2 разных пользователя.
Исходя из вышесказанного нам нужно отредактировать наш код. $socialiteUser->getEmail()
может вернуть null
и это нормально. Но, нам нужно отредактировать файл миграции для таблицы users
и заменить ->unique()
на ->nullable()
у поля email
.
Вот только теперь метод findUserByEmail()
не будет работать как надо. Он будет возвращать первого попавшего пользователя с email = null
если у аккаунта соц сети нет email адреса. Давайте это исправим:
public function findUserByEmail($provider, $email)
{
return !$email ? null : User::where('email', $email)->first();
}
Как подключить другие социальные сети
Другие социальные сети подключаются аналогично. Просто добавьте необходимые ключи в файл config/services.php
и ссылку для редиректа на соц сеть во view. Смотрите документацию Laravel, если у вас остались вопросы. Socialite поддерживает очень ограниченное количество социальных сетей, но к счастью есть множество сторонних провайдеров, разрабатываемых и поддерживаемых сообществом на сайте Socialite Providers.
Все материалы на сайте voerro абсолютно бесплатны и написаны автором в свободное от основной работы время. Если уроки сайта оказались для вас полезными, пожалуйста, помогите проекту. Спасибо!