База данных

У одного пользователя может быть несколько аккаунтов в разных социальных сетях (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.