In a laravel 5.8 API project, I want users to login via their social accounts. So far I have been able to use Socialite to retrieve user info from the provider and use it to create a new user record. But when I try to have the user log in again, it throws up the following error
Call to undefined method Laravel\Socialite\Two\User::createToken()
Here's the code I am working with
<?php
namespace App\Http\Controllers;
use App\User;
use Socialite;
use App\SocialAccount;
use App\Http\Resources\UserResource;
class SocialAuthController extends Controller
{
...
public function handleProviderCallback($provider)
{
$socialUser = Socialite::driver($provider)->stateless()->user();
$userSocialAccount = SocialAccount::where('provider_id', $socialUser->id)->where('provider_name', $provider)->first();
/*
if account exist, return the social account user
else create the user account, then return the new user
*/
if ($userSocialAccount) {
// generate access token for use
$token = $socialUser->createToken('********')->accessToken;
// return access token & user data
return response()->json([
'token' => $token,
'user' => (new UserResource($userSocialAccount))
]);
} else {
$user = User::create([
'firstname' => $socialUser->name,
'lastname' => $socialUser->name,
'username' => $socialUser->email,
'email_verified_at' => now()
]);
if ($user) {
SocialAccount::create([
'provider_id' => $socialUser->id,
'provider_name' => $provider,
'user_id' => $user->id
]);
}
// assign passport token to user
$token = $user->createToken('********')->accessToken;
return response()->json(['token' => $token, 'user' => new UserResource($user)]);
}
}
}
I haven't been able to spot the reason why I am getting the error when the user attempts a second login but there is no error if it's the first time the user logs in with a social account.
Why does it complain about Laravel\Socialite\Two\User::createToken() method? If I try adding this line use Laravel\Socialite\Two\User vscode intelephsense flags it as a duplicate of App\User so what is really going on in my code?
I think your last sentence hits the problem: the Laravel\Socialite\Two\User and App\User are two fully separate entities.
The Socialite::driver($provider)->stateless()->user() provides you with a Socialite User whereas User::create creates an App\User.
The second $token = $user->createToken('********')->accessToken; works because App\User has the createToken function and the other does not.
First of all the problem I was having with having a token generated by passport for users authentication after the first social login was because I was calling the createToken method on the user returned by Socialite. As explained by #JorisJ1 Socialite does not have the createToken function so my initial code threw an error.
Here's how I fixed it
public function handleProviderCallback($provider)
{
// retrieve social user info
$socialUser = Socialite::driver($provider)->stateless()->user();
// check if social user provider record is stored
$userSocialAccount = SocialAccount::where('provider_id', $socialUser->id)->where('provider_name', $provider)->first();
if ($userSocialAccount) {
// retrieve the user from users store
$user = User::find($userSocialAccount->user_id);
// assign access token to user
$token = $user->createToken('Pramopro')->accessToken;
// return access token & user data
return response()->json([
'token' => $token,
'user' => (new UserResource($user))
]);
} else {
...
}
}
Comments are welcomed if there is a better way for adding social authentication to API.
Related
I am trying to create a login token for a user after they register on my application and have verified their mobile device using an OTP sent via sms. The problem is that, when a user logs in, the token is created perfectly. However, when I try to create this token on registration, no token is generated. When a user registers, I want to immediately log them into my app.
Note: This is an app using an API. The login logic works perfectly.
Question Is there anywhere I might be missing it. Have been debugging but no success.
<?php
namespace App\Http\Controllers\Admin;
use App\Models\User;
use App\Exceptions\Handler;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use App\Http\Resources\LoginResource;
class RegisterController extends Controller
{
public function verifyOTP(Request $request){
$validate = Validator::make($request->all(), [
'otp' =>'required|digits:4',
'cellphone' =>'required|digits:10',
]);
if ($validate->fails()){
return response($validate->errors(), 400);
}
$user = DB::table('users')
->where('cellphone', $request->cellphone)
->where('otp', $request->otp)
->first();
if( !$user ){
return response('Wrong OTP. Try again.', 400);
}else{
$updatedUser = DB::table('users')
->where('cellphone', $request->cellphone)
->update([
'status' => 1,
'otp' => NULL,
'account_verified' => 1,
]);
//allocate the user with an authentication token
$loggedInUser = new LoginResource($user);
/******THE FOLLOWING LINE IS THE ONE WITH THE PROBLEM WHERE THE TOKEN IS NOT BEING CREATED. SAYS UNDEFINED METHOD createToken******/
$token = $user->createToken('registration-login-token');
return response([
'user' => $loggedInUser,
'token' => $token->plainTextToken,
], 200);
}
}
}
The error i get is
Error: Call to undefined method stdClass::createToken() in file .../app/Http/Controllers/Admin/RegisterController.php on line 78
Instead of DB you have to use Eloquent Model so use User model instead of DB in below Query :
$user = DB::table('users')
->where('cellphone', $request->cellphone)
->where('otp', $request->otp)
->first();
Is there any way that I can allow user to login only from one device?
Thanks in advance
Well, you would need to check at a central place, if there is an already existing session for the user that currently want to log in - and if yes, delete all existing sessions.
The central place would proably be when the login happens or inside an auth middleware.
To delete all existing sessions for the user you can run
DB::table('sessions')->where('user_id', $user->id)->delete();
Log in only from one device, f. ex. Laptop
That is probably not possible as each device would need to send a unique identifier - which it doesn't. As example, your Laptop would need to send a unique identifier to the Laravel system, so that your Laravel application would know, that it is the Laptop the login is coming from.
The login forms normally only takes a username/email and a password, so no unique property to identify your Laptop.
You could probably check for browser user agent or things like this, but that is all fakeable and does not guarantee a 100% proof identification of the device.
You can use deviceInspect middleware and check user agent (it could be fake as #codedge said) and use it after auth middleware
As you can see the user will be authenticated but routes will be protected by device
Create middleware
class DeviceInspect
{
public function handle($request, Closure $next)
{
$user = Auth::user(); //or $request->user()
// TODO get enabled device/s from datebase for $user - by userId
$enabledDevice = "Dalvik/2.2.0 (Linux; U; Android 10.0.1; AM-A89R Build/NMB55D)"; //example
$currentDevice = $request->userAgent(); //or $_SERVER['HTTP_USER_AGENT'];
//it could be fake like codedge said
if ($enabledDevice !== $currentDevice) {
$data = array(
"device" => false,
"message" => "your message to user",
);
return response([$data], 401); // or something else
}
return $next($request);
}
}
add this to App\Http\Kernel
protected $routeMiddleware = [
...
'device' => 'App\Http\Middleware\DeviceInspect',
];
and use it like below
//in controller
class SomeController extends Controller {
public function __construct() {
parent::__construct();
$this->middleware(['auth', "device"]);
}
}
or
//Or in routes
Route::get('/profil', function () {
//
})->middleware(['auth', 'device']);
or
Route::group(['prefix' => '/v1/data', 'namespace' => 'Api\V1', 'as' => 'api.', 'middleware' => ['auth:api', 'device']], function () {
Route::resource('activity', 'Data\DataController', ['only' => ['index', 'show']]);
});
User doesn't stay logged in when I use setIdentity.
$user = $this->Users->get(1);
$this->Authentication->setIdentity($user);
$userInfo = $this->Authentication->getIdentity(); // Returns good.
$this->redirect('Somewhere');
Somewhere:
$userInfo = $this->Authentication->getIdentity(); // Returns null.
I am not using form. There is no POST happening only dynamically setting user based on some algo...
Application.php
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService([
'unauthenticatedRedirect' => '/login',
'queryParam' => 'redirect',
]);
/* $fields = [
'username' => 'username',
'password' => 'password'
];
$service->loadIdentifier('Authentication.Password', compact('fields')); */
// Load the authenticators, you want session first
$service->loadAuthenticator('Authentication.Session');
return $service;
}
You are setting the principal information on the Authentication but you loose it on the next request because it's not persisted (I'm sparing you the "http is stateless" song...)
Part of your setIdentity should also be persisting the identity. This can be achieved in different ways:
in the session, when using sessions
in a JWT token, when using tokens
Here is how AuthenticationService does it in persistIdentity. I suggest you also have a look at the JWT auth configuration.
My registration form additionally accepts the name of the user's company, which I want to insert in a separate table "Holdings". All data is successfully saved to the Holdings and Users table, but an error occurs at the last step when redirecting to the home page.
ResgisterController:
protected function create(array $data)
{
//Mail::to($data['email'])->send(new Welcome($data['name']));
$id = User::insertGetId([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'role' => 8
]);
$oHolding = new Holdings;
$oHolding->shortname = $data['orgname'];
$oHolding->creator = $id;
$oHolding->save();
DB::table('users')->where('id', $id)->update([
'holding_id' => $oHolding->id,
]);
$user = DB::table('users')->select('*')->where('id', $id)->get();
return $user;
}
Error Message:
Argument 1 passed to Illuminate\Auth\SessionGuard::login() must implement interface Illuminate\Contracts\Auth\Authenticatable, instance of Illuminate\Support\Collection given, called in /Users/admin/Sites/jetime/vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php on line 35
Login Function
public function login(AuthenticatableContract $user, $remember = false)
{
$this->updateSession($user->getAuthIdentifier());
// If the user should be permanently "remembered" by the application we will
// queue a permanent cookie that contains the encrypted copy of the user
// identifier. We will then decrypt this later to retrieve the users.
if ($remember) {
$this->ensureRememberTokenIsSet($user);
$this->queueRecallerCookie($user);
}
// If we have an event dispatcher instance set we will fire an event so that
// any listeners will hook into the authentication events and run actions
// based on the login and logout events fired from the guard instances.
$this->fireLoginEvent($user, $remember);
$this->setUser($user);
}
I will be glad of any help, how to fix the error?
You are returning a collection rather than an instance of a class that implements Authenticatable.
You can see this happening here:
$user = DB::table('users')->select('*')->where('id', $id)->get();
return $user;
If you have the User model that ships with Laravel, then you'll actually want to do:
$user = User::find($id);
return $user;
Although your whole create method could be cleaned up to streamline all of this, however that isn't the topic of your question.
I am trying to set up Socialite in Laravel for facebook logging. I got user logged in , but now I have a problem storing the details in the database.
Also I would like to know how to notice my app that User is logged in? For normal loggin I used native AuthController and everything goes smooth.
Now if user try to log in again, then Laravel automatically redirect it to the url without logging, with appended code like this ?code=AQBCM-KbFqB-VJepSJ-45nFURnsvPPdpdqOu...
So how can I logout the user completely and how can I store the details for the next logging??
This is what I am using but it is not working:
public function getSocialAuth($provider=null)
{
if(!config("services.$provider")) abort('404'); //just to handle providers that doesn't exist
return $this->socialite->with($provider)->redirect();
}
public function getSocialAuthCallback($provider=null)
{
if($user = $this->socialite->with($provider)->user()){
dd($user);
/* This is also not working:
$user1 = new User;
$user1->facebook_id = $user->getId();
$user1->email=$user->email();
$user1->fullname=$user->name();
$user1->provider="facebook";
$user1->save();*/
// not working:
User::create([
'facebook_id' => $user->getId(), 'email' => $user->email(),
'fullname' => $user->name()
]);
}else{
return 'something went wrong';
}
}