Socialite: объединяйте учетные записи с одинаковым адресом электронной почты

я использую Socialite, чтобы дать пользователям возможность войти в систему через facebook или github. но когда пользователь входит в систему через facebook, а затем через github, создаются 2 отдельные учетные записи. Итак, мой вопрос: есть ли способ объединить эти 2 аккаунта в один? например, если пользователь, который вошел в систему через facebook, использует тот же адрес электронной почты для входа в github, новая учетная запись не будет создана, и они просто войдут в систему.

    <?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('profile');
            $table->string('slug');
            $table->string('provider_id');
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

код входа/регистрации

/**
 * Redirect the user to the provider authentication page.
 *
 * @return Response
 */
public function redirectToProvider($provider)
{
    return Socialite::driver($provider)->redirect();
}

/**
 * Obtain the user information from the provider.
 *
 * @return Response
 */
public function handleProviderCallback($provider)
{
    $SocialUser = Socialite::driver($provider)->stateless()->user();

    $user = $this -> findOrCreateUser($SocialUser,$provider);

    auth()->login($user,true);


    return redirect('/');

}

protected function findOrCreateUser($SocialUser,$provider)
{
    $user = User::firstOrNew(['provider_id' => $SocialUser->id]);

        if ($user->exists) return $user;
    $user->fill([
    'name' => $SocialUser->nickname?:$SocialUser->name,
    'slug' => str_slug($SocialUser->nickname?:$SocialUser->name).'-'.uniqid(),
        'email' => $SocialUser->email,
        'avatar' => $SocialUser->avatar,
        'profile' => Hash::make('no pic'),
        'password' => Hash::make('no need for password token based'),
    // 'website' => 'add a website',
    // 'github_profile' => 'add github profile',
    'email_notifications' => 1
    ])->save();
    $user->assignRole('user');
    \Mail::to($user)->send(new Welcome($user));
    session()->flash('message','Welcome to '.config('app.name').' '.$user->name);
    return $user;
}

}


person kristi tanellari    schedule 30.11.2018    source источник
comment
Привет, Кристи. Не могли бы вы опубликовать, как выглядит ваша пользовательская таблица?   -  person Josh    schedule 30.11.2018
comment
Здравствуй. только что добавил мою таблицу пользователей выше   -  person kristi tanellari    schedule 30.11.2018
comment
что вы можете сделать, так это вместо использования только одного provider_id использовать facebook_id, github_id и twitter_id для их индивидуального хранения.   -  person Joe    schedule 30.11.2018
comment
Привет, Кристи. Не могли бы вы добавить несколько примеров дублирующихся данных?   -  person Josh    schedule 30.11.2018
comment
@josh я вхожу/регистрирую пользователей на основе идентификатора провайдера. поэтому я проверяю, существует ли идентификатор провайдера в базе данных, затем войдите в систему пользователя, иначе создайте нового пользователя.   -  person kristi tanellari    schedule 30.11.2018
comment
@joe, как это изменит ситуацию?   -  person kristi tanellari    schedule 30.11.2018
comment
я добавил код входа/регистрации выше   -  person kristi tanellari    schedule 30.11.2018
comment
@kristi Что ж, согласно предоставленной вами миграции, у вас не может быть 2 записей с одним и тем же адресом электронной почты, поэтому вы не должны получать дубликаты учетных записей пользователей.   -  person Josh    schedule 30.11.2018
comment
@josh, мне жаль, что миграция немного устарела. мне пришлось изменить таблицу вручную, и я разрешаю дублировать электронные письма. Я сожалею о том, что. не заметил   -  person kristi tanellari    schedule 30.11.2018
comment
@kristitanellari использует User::where('email', $SocialUser->email)->first() вместо проверки provider_id   -  person Joe    schedule 30.11.2018
comment
@kristi Я пришел к тому же выводу, что и Джо. Кроме того, то, как вы это написали, будет регистрировать каждого пользователя от первого лица, чтобы использовать этого провайдера. см. мое решение ниже   -  person Josh    schedule 30.11.2018


Ответы (2)


У меня есть решение, использующее дух всего в этом вопросе и ответе.

/**
 * @param  string $provider
 * @param  \Laravel\Socialite\Contracts\User $sUser
 * @return \App\User|false
 */
protected function findOrCreateUser($provider, $sUser)
{
    $oauthProvider = OAuthProvider::where('provider', $provider)
        ->where('provider_user_id', $sUser->id)
        ->first();

    if ($oauthProvider) {
        $oauthProvider->update([
            'access_token' => $sUser->token,
            'refresh_token' => $sUser->refreshToken ?? null,
        ]);

        return $oauthProvider->user;
    }

    $user = User::firstWhere('email', $sUser->email);

    if ($user) {
        return $this->createUser($provider, $sUser, $user);
    }

    return $this->createUser($provider, $sUser);
}

/**
 * If a User already exists for the email, skip user creation
 * and add this provider to the list of `$user->oauthProviders`.
 * @param  string $provider
 * @param  \Laravel\Socialite\Contracts\User $sUser
 * @param \App\User $user
 * @return \App\User
 */
protected function createUser($provider, $sUser, User $user = null)
{
    if (!$user) {
        $user = User::create([
            'name' => $sUser->name,
            'email' => $sUser->email,
            'email_verified_at' => now(),
        ]);
    } else if ($user->email_verified_at === null) {
        $user->email_verified_at = now();
        $user->save();
    }

    $user->oauthProviders()->create([
        'provider' => $provider,
        'provider_user_id' => $sUser->id,
        'access_token' => $sUser->token,
        'refresh_token' => $sUser->refreshToken ?? null,
    ]);

    return $user;
}

Раньше у него была проверка на User::where('email', $sUser->email), и если это так, отклоняйте запрос с сообщением «электронная почта уже занята».

С таблицей oauth_providers и отношением $user->oauthProviders (User hasMany OAuthProviders) вместо того, чтобы создавать нового пользователя в таблице пользователей каждый раз, когда кто-то использует oauth, он прикрепляет эту запись oauth к существующему пользователю $user = User::firstWhere('email', $sUser->email);

Если кому-то нужно немного больше, я изменил этот репозиторий здесь, чтобы GitHub и Twitter работали с oauth: https://github.com/cretueusebiu/laravel-vue-spa. Основывайтесь на OAuthController.

С помощью приведенного выше кода я могу зарегистрировать пользователя через регистрационную форму, чтобы получить электронное письмо, затем войти в систему как GitHub и Twitter, и мой пользователь плюс два провайдера oauth.

Большая часть волшебства моего решения связана с третьим параметром createUser. Еще неизвестно, будет ли работать лучше, если оставить createUser всегда создающим, а затем создать новый метод с именем addProviderToUser. Это может быть немного больше кода, но он также может быть проще и удобнее для модульных тестов.

Вот мои методы перенаправления и обратного вызова oauth, по научным причинам:

/**
 * Redirect the user to the provider authentication page. Twitter uses OAuth1.0a, and does not support
 * Socialite::driver($provider)->stateless(), so library `abraham/twitteroauth` is used to handle everything.
 *
 * @param  string $provider
 * @return \Illuminate\Http\RedirectResponse
 */
public function redirectToProvider($provider)
{
    if ($provider === 'twitter') {
        $tempId = Str::random(40);

        $connection = new TwitterOAuth(config('services.twitter.client_id'), config('services.twitter.client_secret'));
        $requestToken = $connection->oauth('oauth/request_token', array('oauth_callback' => config('services.twitter.callback_url').'?user='.$tempId));

        \Cache::put($tempId, $requestToken['oauth_token_secret'], 86400); // 86400 seconds = 1 day

        $url = $connection->url('oauth/authorize', array('oauth_token' => $requestToken['oauth_token']));
    } else {
        $url = Socialite::driver($provider)->stateless()->redirect()->getTargetUrl();
    }

    return [
        'url' => $url,
    ];
}

/**
 * Obtain the user information from the provider.
 *
 * @param  string $driver
 * @return \Illuminate\Http\Response
 */
public function handleProviderCallback(Request $request, $provider)
{
    if ($provider === 'twitter') {
        $connection = new TwitterOAuth(config('services.twitter.client_id'), config('services.twitter.client_secret'), $request->oauth_token, \Cache::get($request->user));
        $access_token = $connection->oauth('oauth/access_token', ['oauth_verifier' => $request->oauth_verifier]);

        $connection = new TwitterOAuth(config('services.twitter.client_id'), config('services.twitter.client_secret'), $access_token['oauth_token'], $access_token['oauth_token_secret']);
        $user = $connection->get('account/verify_credentials', ['include_email' => 'true']);

        $user->token = $access_token['oauth_token'];
    } else {
        $user = Socialite::driver($provider)->stateless()->user();
    }
    $user = $this->findOrCreateUser($provider, $user);

    $this->guard()->setToken(
        $token = $this->guard()->login($user)
    );

    return view('oauth/callback', [
        'token' => $token,
        'token_type' => 'bearer',
        'expires_in' => $this->guard()->getPayload()->get('exp') - time(),
    ]);
}

config/services.php

'github' => [
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'callback_url' => env('GITHUB_CALLBACK_URL'),
    'provider_name' => env('GITHUB_PROVIDER_NAME', 'GitHub'),
],

'twitter' => [
    'client_id' => env('TWITTER_CLIENT_ID'),
    'client_secret' => env('TWITTER_CLIENT_SECRET'),
    'callback_url' => env('TWITTER_CALLBACK_URL'),
    'provider_name' => env('TWITTER_PROVIDER_NAME', 'Twitter'),
],

.env

# localhost
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK_URL=https://valet.test/api/oauth/github

TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=
TWITTER_CALLBACK_URL=https://valet.test/api/oauth/twitter/callback

Вам нужно будет заглянуть в приведенный выше пример репозитория, чтобы выяснить, как используются эти переменные env, но подсказка: посмотрите на spa.blade.php, vuex и api.php.

person agm1984    schedule 07.05.2020

попробуйте изменить свой код на это:

$user = User::where('email', $SocialUser->email)->first();

if (!empty($user) && in_array($SocialUser->id, $user->provider_id) ) {

    session()->flash('message','Welcome to '.config('app.name').' '.$user->name);

    return $user;

}

if (empty($user) ) {

    $user = User::create([
        'name' => $SocialUser->nickname?:$SocialUser->name,
        'slug' => str_slug($SocialUser->nickname?:$SocialUser->name).'-'.uniqid(),
        'email' => $SocialUser->email,
        'avatar' => $SocialUser->avatar,
        'profile' => Hash::make('no pic'),
        'password' => Hash::make('no need for password token based'),
        // 'website' => 'add a website',
        // 'github_profile' => 'add github profile',
       'email_notifications' => 1,
       'provider_id' => [$SocialUser->id]
    ]);

    $user->assignRole('user');

    \Mail::to($user)->send(new Welcome($user));

    session()->flash('message','Welcome to '.config('app.name').' '.$user->name);

    return $user;

}

$providers = array_push($user->provider_id, $SocialUser->id);

$user->update([
    'provider_id' => $providers
]);

session()->flash('message','Welcome to '.config('app.name').' '.$user->name);

return $user;

Лучше всего добавить это в свою модель пользователя:

protected $casts = [
    'provider_id' => 'array'
];

надеюсь, это поможет

person Josh    schedule 30.11.2018