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;
}
}
1 ответ
У меня есть решение, в котором используется дух всего в этом вопросе и ответе.
/**
* @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
Попробуйте изменить свой код на это:
$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'
];
надеюсь, это поможет