Socialite: Merge accounts that have the same email - php

im using socialite to give the users the option to log in with either facebook or github. but when a user logs in with facebook and after that with github, 2 separate accounts are created. So my question is , is there a way to combine these 2 accounts into one? for example if a user that has logged in with facebook is using the same email address to log in with github, no new account will be created and they will simply be logged in
<?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');
}
}
login/register code
/**
* 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;
}
}

I have a solution that's using the spirit of everything in this question and answer.
/**
* #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;
}
Before, it had a check for if User::where('email', $sUser->email), and if so, reject the request with an "email already taken" message.
With the oauth_providers table and $user->oauthProviders relationship (User hasMany OAuthProviders), rather than create a new User in the users table every time someone uses oauth, it attaches that oauth record with the existing user $user = User::firstWhere('email', $sUser->email);
If anyone wants a little more, I modified this repo here to make both GitHub and Twitter oauth work: https://github.com/cretueusebiu/laravel-vue-spa. Base yourself around OAuthController.
With the above code, I can register a user via the registration form to capture an email, then login as GitHub and Twitter and have my user plus two oauth providers.
Most of the magic of my solution comes in with the 3rd param on createUser. It will remain to be seen if it works better to leave createUser as always creating, and then make a new method called addProviderToUser. That might be slightly more code, but it might also be simpler and more friendly to unit tests.
Here are my oauth redirect and callback methods too, for science reasons:
/**
* 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
You'd have to look in the above sample repo to figure out how those env variables are being consumed, but hint: look at spa.blade.php, vuex, and api.php

try changing your code to this:
$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;
You are best also adding this to your User model:
protected $casts = [
'provider_id' => 'array'
];
I hope this helps

Related

problem when using policy with laravel spatie

i have using laravel spatie for permission management: and it is not working with policy, I tried this:
in UserPolicy:
public function view(User $user, User $model)
{
if($user->can('display')) {
return true;
}
}
in controller UserController:
public function index()
{
$this->authorize('view', Auth::user());
$users = User::paginate(10);
return view('users.index', compact('users'));
}
/**
* Show the form for creating a new resource.
*
* #return \Illuminate\Http\Response
*/
public function create()
{
$permissions = Permission::all();
return view('users.create', compact('permissions'));
}
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'min:3'],
'email' => ['email', 'required', 'unique:users'],
'password' => ['required', 'confirmed', 'min:6'],
]);
try {
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$user->syncPermissions($request->permissions, []);
return redirect()->route('users.index')->with('msg', 'user has created successfully');
}catch(\Exception $e) {
return redirect()->back()->with('msg', 'User not registered');
}
}
I have tried index function with user has many permissions including (display) and show me the (Forbbeden page) for all users even with display permission

Change login system by API in Laravel 8

I make my first app with API.
I am beginner in Laravel and php.
I have this migration:
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('company_id')->unsigned();
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
$table->string('name');
$table->string('surname')->nullable();
$table->string('phone')->nullable();
$table->mediumText('description')->nullable();
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('api_token', 80)
->unique()
->nullable()
->default(null);
$table->string('menuroles');
$table->boolean('status')->default(false);
$table->string('slug', 160);
$table->rememberToken();
$table->timestamps();
$table->softDeletes();
});
and this is my AuthController:
class AuthController extends Controller
{
/**
* Create a new AuthController instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth:api', ['except' => ['login', 'register']]);
}
/**
* Register new user.
*
* #return \Illuminate\Http\JsonResponse
*/
public function register(Request $request){
$validate = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email|unique:users',
'password' => 'required|min:4|confirmed',
]);
if ($validate->fails()){
return response()->json([
'status' => 'error',
'errors' => $validate->errors()
], 422);
}
// $user = new User;
// $user->name = $request->name;
// $user->email = $request->email;
// $user->password = bcrypt($request->password);
// $user->status = 'Active';
// $user->save();
return response()->json(['status' => 'success'], 200);
}
/**
* Get a JWT via given credentials.
*
* #return \Illuminate\Http\JsonResponse
*/
public function login(Request $request)
{
$credentials = request(['email', 'password']);
if (! $token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token, $request->email);
}
/**
* Log the user out (Invalidate the token).
*
* #return \Illuminate\Http\JsonResponse
*/
public function logout()
{
auth()->logout();
return response()->json(['message' => 'Successfully logged out']);
}
/**
* Refresh a token.
*
* #return \Illuminate\Http\JsonResponse
*/
public function refresh()
{
return $this->respondWithToken(auth()->refresh());
}
/**
* Get the token array structure.
*
* #param string $token
*
* #return \Illuminate\Http\JsonResponse
*/
protected function respondWithToken($token, $email)
{
$user = User::select('menuroles as roles')->where('email', '=', $email)->first();
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth()->factory()->getTTL() * 60,
'roles' => $user->roles
]);
}
Login work's fine. In addition to the login and password, I need to verify the status column. If status = 1 - then login is possible. How can I do this?
Please help me :)
How can I make it?
You can add more scopes to user auth attempt
if (Auth::attempt(['email' => $email, 'password' => $password, 'status' => 1])) {
// Authentication was successful...
}
Laravel Documentation

Insert in two tables when registering users in Laravel

I'm using the default Laravel 5.1 user registration. I have two tables: users and shops. When user registers, the app should insert a user in the table users, get the id and use it to register a shop. I've been reading the default AuthController.php but i didn't find anything. Here is the AuthController if it helps.
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
/**
* Create a new authentication controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
//'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
return User::create([
//'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
/**
* Get the path to the login route.
*
* #return string
*/
public function loginPath()
{
return route('login');
}
/**
* Get the post register / login redirect path.
*
* #return string
*/
public function redirectPath()
{
return route('home');
}
}
Solved, but now I have a Integrity constraint violation. Is this code correct?
protected function create(array $data)
{
$user = new User([
'email' => $data['email'],
'password' => bcrypt($data['password'])
]);
$user->role = 'shop_owner';
$user->remember_token = str_random(10);
$user->save();
$userId = $user->id;
Shop::create([
'name' => $data['s_name'],
'address' => $data['s_address'],
'CP' => $data['s_pcode'],
'Telephone' => $data['s_tlf'],
'contact_name' => $data['cp_name'],
'contact_num' => $data['cp_tlf'],
'id_user' => $userId
]);
return $user;
}
There you go:
protected function create(array $data)
{
$user = User::create([
//'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
$userId = $user->id;
Shop::create([... use $userId here ...]);
return $user;
}
This goes to your controller:
public function store(Request $request) {
$user = User::create(Input::all());
$user->save();
$shop = Shop::create([..enter shop attributes or leave blank..]);
$user->shop()->save($shop);
}
You need to place the following code at the top of the Auth Controller
use App\Shop;

Admin login laravel 4

I edited the store method , now the problem is that when i try to login it redirect to www.example.com/admin but it shows a NotFoundHttpException.
The routes.php file
Route::get('/admin', 'SessionsController#create');
Route::get('/logout', 'SessionsController#destroy');
Route::get('profile', function()
{
return "welcome! Your username is" . Auth::admin()->username;
});
Route::resource('sessions', 'SessionsController', ['only' => ['index', 'create', 'destroy', 'store']]);
here is the controller SessionsController.php
<?php
class SessionsController extends \BaseController {
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create()
{
return View::make('admins');
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$rules = array('username' => 'required', 'password' => 'required');
$validator = Validator::make(Input::all(), $rules);
if($validator -> passes()){
$credentials = array(
'username' => Input::get('username'),
'password' => Input::get('password')
);
if(Auth::admin($credentials,true)){
$username = Input::get('username');
return Redirect::to("admin/{$username}");
} else {
return Redirect::to('/admin')->withErrors('Username or password invalid');
}
} else {
return Redirect::to('/admin')->withErrors($validator->messages());
}
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id)
{
Auth::logout();
return Redirect::home();
}
}
the admins.blade.php
{{Form::open(array('route' => 'sessions.store'))}}
<h1>ADMIN LOGIN </h1>
{{Form::label('username', 'Username')}}
{{Form::text('username')}}
{{Form::label('password', 'Password')}}
{{Form::password('password')}}
{{Form::submit('Login')}}
{{$errors->first()}}
{{Form::close()}}
and here is the model Admin.php
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class Admin extends Eloquent implements UserInterface, RemindableInterface {
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'admins';
}
I also installed ollieread multiauth
and here is auth.php file
return array(
'multi' => array(
'admin' => array(
'driver' => 'database',
'model' => 'Admin',
'table' => 'admins'
),
'user' => array(
'driver' => 'eloquent',
'model' => 'User',
'table' => 'users'
)
),
'reminder' => array(
'email' => 'emails.auth.reminder',
'table' => 'password_reminders',
'expire' => 60,
),
);
In your admin template you set the goto url as sessions.store which hits SessionsController::store in that method you have a debug function dd() which is throwing that string. It get's called because auth::attempt() returns false as by your own code:
if($attempt) return Redirect::intended('/');
So the behavior is exactly doing what it should. If you are succesfully logged in, you are redirected otherwise it will dump through dd()
What you have to do is filter. You have to add custom error message on app/filter.php
Like the following
Route::filter('auth', function()
{
if (Auth::guest())
{
if (Request::ajax())
{
return Response::make('Unauthorized', 401);
}
else
{
return Redirect::guest('/')->with('message', 'Please login first');
}
}
});
Above code, I redirect to / and gave Please login first message.

Laravel Auth attempt failing

I really try to debug my issues on my own before I bring them here, but I seriously cannot find a solution to my laravel auth problem, though it seems to be a common issue.
My authentication will not login. It always returns false and I don't understand why.
I've read through some other questions here, and their solutions haven't solved my particular situation.
My User model implements UserInterface and Remindable Interface.
My password is hashed upon creating it to the database.
My password field in my database is varchar 100, which should be more than enough to hash the password.
The user I'm logging is has been created and activated in the database.
Thank you so much for any insight.
User Model
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
protected $fillable = array('email', 'username', 'password', 'password_temp', 'code', 'active');
public $timestamps = false;
protected $softDelete = false;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'Users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = 'password';
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
Account Controller
class AccountController extends BaseController {
public function getLogin() {
return View::make('account.login');
}
public function postLogin() {
$validator = Validator::make(Input::all(),
array(
'email' => 'required',
'password' => 'required'
)
);
if($validator->fails()) {
return Redirect::route('login')
->withErrors($validator);
} else {
$auth = Auth::attempt(array(
'email' => Input::get('email'),
'password' => Input::get('password'),
'active' => 1
));
if($auth) {
return Redirect::route('Create-Account');
}
}
return Redirect::route('login')
->with('global', 'There was a problem logging you in. Please check your credentials and try again.');
}
public function getCreate() {
return View::make('account.create');
}
public function getviewReturn() {
return View::make('account.return');
}
public function postCreate() {
$validator = Validator::make(Input::all(),
array(
'email' => 'required|max:50|email|unique:Users',
'username' => 'required|max:15|min:4|unique:Users',
'password' => 'required|min:6',
'password2' => 'required|same:password'
)
);
if ($validator->fails()) {
return Redirect::route('Post-Create-Account')
->withErrors($validator)
->withInput();
}
else {
$email = Input::get('email');
$username = Input::get('username');
$password = Input::get('email');
$code = str_random(60);
$user = User::create(array(
'email' => $email,
'username' => $username,
'password' => Hash::make($password),
'code' => $code,
'active' => 0));
});
return Redirect::to('account/return')
Routes
Route::group(array('before' => 'guest'), function() {
Route::group(array('before' => 'csrf'), function() {
Route::post('/account/create', array(
'as' => 'Post-Create-Account',
'uses' => 'AccountController#postCreate'
));
Route::post('/account/login', array(
'as' => 'postlogin',
'uses' => 'AccountController#postLogin'
));
});
Route::get('/account/login', array(
'as' => 'login',
'uses' => 'AccountController#getLogin'
));
Route::get('/account/create', array(
'as' => 'Create-Account',
'uses' => 'AccountController#getCreate'
));
Route::get('/account/activate/{code}', array(
'as' => 'Activate-Account',
'uses' => 'AccountController#getActivate'
When creating the user you've done
$password = Input::get('email');
It should be
$password = Input::get('password');
so if you try and login with the "email" as the password - it will work! :)
So if you change this
else {
$email = Input::get('email');
$username = Input::get('username');
$password = Input::get('email');
$code = str_random(60);
$user = User::create(array(
'email' => $email,
'username' => $username,
'password' => Hash::make($password),
'code' => $code,
'active' => 0));
});
to this
else {
$user = User::create(array(
'email' => Input::get('email'),
'username' => Input::get('username'),
'password' => Hash::make(Input::get('password');),
'code' => str_random(60),
'active' => 0));
});
that cleans up your code and fixes the issue.
Your code looks right to me, so you have to check some things:
1) A manual attempt works for you?
dd( Auth::attempt(['email' => 'youremail', 'password' => 'passw0rt']) );
2) The user hash checks manually?
$user = User::find(1);
var_dump( Hash::check($user->password, 'passw0rt') );
dd( Hash::check($user->password, Input::get('password')) );
Try to add primaryKey field in your user model. It should be something like that:
protected $primaryKey = 'user_id';
I think Apache version problem. You need to update Apache2.4.

Categories