Laravel authenticate using phone or email and password - php

My database has below structure
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique()->nullable();
$table->string('phone')->unique()->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->timestamp('phone_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
DB::statement('ALTER TABLE users ADD CONSTRAINT chk_phone_or_email CHECK (email IS NOT NULL OR email IS NOT NULL);');
}
Basically I have two fields phone and email that can be null but both of then cannot be null. A user can pass either their phone or email but this is passed to the backend as username. With this I need to attempt to login with either and if successful, return token
Below is my code
public function login(ServerRequestInterface $request)
{
$usernames = ['phone', 'email'];
for ($i = 0; $i < sizeof($usernames); $i += 1){
$credentials = ['password' => request()->get('password')];
$credentials[$usernames[$i]] = request()->get('username');
if (Auth::guard('web')->attempt($credentials)) {
return $this->withErrorHandling(function () use ($request) {
return $this->convertResponse(
$this->server->respondToAccessTokenRequest($request, new Psr7Response)
);
});
}
}
return response()->json([
'message' => 'Invalid username or password'
], 401);
}
If I login with valid email and password, I am able to login but if I try to login using phone and password, I get unauthorised,
How can I resolve this?

You can check for users with queries and login with Auth
https://laravel.com/docs/8.x/authentication#authenticate-a-user-instance
for example in your controller:
public function login(ServerRequestInterface $request)
{
$username = request()->get('username');
$password = request()->get('password');
$user = User::where('phone', $username)->orWhere('email', $username)->first();
if ($user === null || !Hash::check($password, $user->password)) {
return response()->json([
'message' => 'Invalid username or password'
], 401);
}
Auth::guard('web')->login($user);
// ...
}

I found below, hope it helps someone,
We can override findForPassport function in the users model to achieve this
public function findForPassport($identifier)
{
return $this->orWhere('email', $identifier)
->orWhere('phone', $identifier)
->orWhere('username', $identifier)
->first();
}
}
See Customizing the Username field

Related

Validation Rule for social login

I was trying to implement social login with laravel. Everything seems OK. User can login . But problem is same email with facebook & google + also logged in. So i don't want to let login with same email. How do i implement validation rule for social login?
Here is the code i i was trying for validation but it throws in $validator
public function redirectToProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
public function handleProviderCallback($provider,Request $request)
{
if (!$request->has('code') || $request->has('denied'))
{
return redirect('/');
}
$user = Socialite::driver($provider)->user();
$authUser = $this->findOrCreateUser($user, $provider);
Auth::login($authUser, true);
}
public function findOrCreateUser($user, $provider)
{
$authUser = User::where('provider_id', $user->id)->first();
if ($authUser)
{
return $authUser;
}
$validator = Validator::make($user->toArray(), [
'email' => 'max:255|unique:users',
]);
if ($validator->passes())
{
return User::create([
'name' => $user->name,
'email' => $user->email,
'provider' => $provider,
'provider_id' => $user->id,
]);
}
return Redirect::to('login')->with('errors',$validator->errors());
}
I didn't really understand your question but i tried to refactor your function
public function findOrCreateUser($user, $provider)
{
$authUser = User::where('provider_id', $user->getId())->first();
if ($authUser)
{
return $authUser;
}
$email = User::where('email', $user->getEmail())->first();
if (! $email)
{
return User::create([
'name' => $user-> getName(),
'email' => $user-> getEmail(),
'provider' => $provider,
'provider_id' => $user->getId(),
]);
}
return Redirect::to('login')->with('errors',"Sorry ! This email Already Exists");
}
I didn't understand the question but here's my guess on what you need.
My Guess: You want to keep email in users table unique.
You can create an additional table to keep records of social login methods named Connected Accounts. Keep basic user info in users table and link the social accounts to the same email record in users table. You give login on the basis of users table and add different social login accounts to connected accounts table.
Users Table
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('username');
$table->string('firstname');
$table->string('lastname')->nullable();
$table->string('email')->unique();
$table->string('password')->nullable();
$table->rememberToken();
$table->timestamps();
});
Connected Accounts Table
Schema::create('connected_accounts', function(Blueprint $table){
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('account_type');
$table->string('provider')->nullable();
$table->string('provider_id');
$table->text('token')->nullable();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
});
Find or Create user code (if user exists add social login information to connected accounts else create user and add social login information to connected accounts):
public function findOrCreateUser($user, $provider) {
$userExists = User::where('email', $user->email)->first();
if($userExists) {
$connectedAccount = ConnectedAccount::where('provider_id', $user->id)->first();
if ($connectedAccount) {
return $connectedAccount->user;
}
else{
$connectedAccount = new ConnectedAccount();
$connectedAccount->user_id = $userExists->id;
$connectedAccount->account_type = 'social';
$connectedAccount->provider = $provider;
$connectedAccount->token = $user->token;
$connectedAccount->provider_id = $user->id;
$connectedAccount->save();
return $userExists;
}
}
else{
$firstName = '';
$lastName = '';
if($provider == 'facebook'){
$name = explode(' ',$user->name);
$firstName = $name[0];
$lastName = $name[1];
$profile_pic = $user->avatar_original;
}
else if($provider == 'google'){
$firstName = $user->user['name']['givenName'];
$lastName = $user->user['name']['familyName'];
$profile_pic = $user->avatar_original;
}
else if($provider == 'linkedin'){
$firstName = $user->user['firstName'];
$lastName = $user->user['lastName'];
$profile_pic = $user->avatar_original;
}
else if($provider == 'github'){
$name = explode(' ',$user->name);
$firstName = $name[0];
if(isset($name[1])){
$lastName = $name[1];
}
$profile_pic = $user->avatar;
}
$uploadedPic = $this->transferProfilePicture($profile_pic);
$userNew = User::create([
'firstname' => $firstName,
'lastname' => $lastName,
'username' => md5($user->email),
'email' => $user->email,
'profile_pic' => $uploadedPic,
'is_verified' => 1
]);
$connectedAccount = new ConnectedAccount();
$connectedAccount->user_id = $userNew->id;
$connectedAccount->account_type = 'social';
$connectedAccount->provider = $provider;
$connectedAccount->token = $user->token;
$connectedAccount->provider_id = $user->id;
$connectedAccount->save();
return $userNew;
}
}

trying to write Policy for enabling comment of posts in Laravel

In laravel I have a Follower table that I use to check if a User is folowing another User and also if he can comment on Posts.
The table is like this:
Schema::create('followers', function (Blueprint $table) {
$table->unsignedInteger('publisher_id')->unsigned();
$table->unsignedInteger('follower_id')->unsigned();
$table->boolean('enable_follow')->default('1');
$table->unique(['publisher_id', 'follower_id']);
$table->timestamps();
$table->foreign('publisher_id')
->references('id')
->on('users')
->onDelete('cascade');
$table->foreign('follower_id')
->references('id')
->on('users')
->onDelete('cascade');
});
and these are the checks that I make to decide if a User can comment a Post:
public function canComment(User $user, Post $post)
{
$following = Follower::where('follower_id', $user->id)->where('publisher_id', $post->user_id)->select('enable_follow')->get();
if (!$following->isEmpty()) {
$enabled = $following[0]['enable_follow'];
if ($enabled != '0') {
return true;
} else {
return false;
}
} else if ($following->isEmpty()) {
return true;
}
}
And this is the controller part for storing, as You can see I'm trying to authorize like this: $this->authorize('canComment', $post[0]);
public function store(Request $request)
{
//on_post, from_user, body
// define rules
$rules = array(
'post_id' => 'required',
'body' => 'required'
);
$validator = Validator::make(Input::all(), $rules);
$post_id = $request->input('post_id');
$post = Post::findOrFail($post_id);
if ($validator->fails()) {
return Response()->json($validator);
} else {
$this->authorize('canComment', $post);
//prepares object to be stored in DB
$comment = new Comment();
$comment['user_id'] = $request->user()->id;
$comment['post_id'] = $post_id;
$comment['body'] = $request->input('body');
$comment->save();
if ($comment) {
$comment['user_name'] = $request->user()->username;
$comment['comment_id'] = $comment->id;
$comment['token'] = $request->input('_token');
}
return Response()->json($comment);
}
}
The problem here is I get a 403 (Forbidden) error in a situation where I have $following empty and where following is enabled. The Policy is not working as expected.
Source code for authorize method in Gate facade:
public function authorize($ability, $arguments = [])
{
$result = $this->raw($ability, $arguments);
if ($result instanceof Response) {
return $result;
}
return $result ? $this->allow() : $this->deny();
}
Maybe I am not correct returing true or false in the policy as this code expect the result to be an instance of Response but so what do you return to grant or deny access??
The problem was putting the policy inside commentPolicy and so it expected to receive a Comment not a Post, moving it to postPolicy solved it.

Laravel 5.1 login with mfa token

I'm trying to get the a login with mfa to work. I'm using the https://github.com/antonioribeiro/google2fa package.
Basically the user-migration looks like this
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('email')->unique();
$table->string('password', 60);
$table->rememberToken();
$table->string('google2fa_secret');
$table->boolean('useMfa')->default(false);;
$table->timestamps();
});
}
...
If the user has not yet activated mfa I create a new secret every time the user opens the profile page.
if(!$user->useMfaToken()){
$google2fa = new Google2FA();
$user->google2fa_secret = $google2fa->generateSecretKey();
$user->save();
$google2fa_url = $google2fa->getQRCodeGoogleUrl(
'DatenPro.de',
$user->email,
$user->google2fa_secret
);
}
If the user enters the secret for finalizing the activation of mfa this will be executed:
public function saveMfa(){
$user = \Auth::user();
$secret = \Input::get('secret');
$google2fa = new Google2FA();
$valid = $google2fa->verifyKey($user->google2fa_secret, $secret);
if($valid){
$user->useMfa = true;
$user->save();
return redirect()->back()->withMessage('mfa sucessfully activated');
}
...
Now I'm working on the login with a mfa-token. I want that the user has the option to enter the token at the login page, if he has already activated it, otherwise if the mfa-Checkbox is deselected the "secret" text-input is hidden.
Email: __________
Password: __________
Use Mfa: [x]
Secret: __________
Where do I have to put the checks of the mfa token? I have read about it to check it through a middleware and a session-variable, but this seems kind of wrong.
Just figured it out before posting.
You can implement a "authenticated"-method in the AuthController.
This could look like this:
public function authenticated($request, $user){
if($user->useMfaToken()){
$secret = \Input::get('secret');
$google2fa = new Google2FA();
$validMfaToken = $google2fa->verifyKey($user->google2fa_secret, $secret);
}else{
$validMfaToken = true;
}
if($validMfaToken){
return redirect()->intended('dashboard');
}
Auth::logout();
return redirect($this->loginPath)
->withInput($request->only('email', 'remember'))
->withErrors([
'secret' => 'mfa token was not corret',
]);
}

Laravel 5.1 extra field for authentication

I'm making my first big project using Laravel 5.1 and I'd like to add an extra check during user login to see if the user has activated their account.
This is the schema of the users table
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('username');
$table->string('email')->unique();
$table->string('password', 60);
$table->rememberToken();
$table->boolean('is_admin')->default(true);
$table->boolean('is_active')->default(true);
$table->timestamps();
});
I've tried adding a $credentials['is_active'] = true; after $credentials = $this->getCredentials($request); in AutheticatesUser#postLogin and it works but I want to have a custom error if the user's account isn't active because the default one(These credentials do not match our records.) is not that intuitive for the user.
Any suggestions in achieving that?
Thank you!
You can override the postLogin method in your AuthController and check whether the user is active or not like this.
class AuthController extends Controller
{
public function postLogin(Request $request){
$this->validate($request, [
'email' => 'required|email', 'password' => 'required',
]);
$credentials = $this->getCredentials($request);
// This section is the only change
if (Auth::validate($credentials)) {
$user = Auth::getLastAttempted();
if ($user->is_active) {
Auth::login($user, $request->has('remember'));
return redirect()->intended($this->redirectPath());
} else {
return redirect($this->loginPath()) // Change this to redirect elsewhee
->withInput($request->only('email', 'remember'))
->withErrors([
'active' => 'Please active your account'
]);
}
}
return redirect($this->loginPath())
->withInput($request->only('email', 'remember'))
->withErrors([
'email' => $this->getFailedLoginMessage(),
]);
}
}
You can check following way
if(Auth::attempt(['email'=>$email,'password'=>$password,'is_admin'=>1]))
{
return redirect()->intended('admin/dashboard');
}

Laravel Auth::attempt() Fails after password change

My Laravel authentication works perfectly. I included password change feature. After changing the password,the login works fine for all users except the first user(uid="1"). Only the default password hash works well for this user. For other password hashes the login attempt fails. My codes are given below:
User Controller Signin
public function postSignin() {
if (Auth::attempt(array('email'=>Input::get('email'), 'password'=>Input::get('password')))) {
return Redirect::to('users/dashboard')->with(array('message' => 'You are now logged in!', 'email' => Input::get('email')));
} else {
return Redirect::to('users/login')
->with('message', 'Your username/password combination was incorrect')
->withInput();
}
}
Table Schema
Schema::create('users', function($table)
{
$table->increments('id');
$table->string('firstname', 20);
$table->string('lastname', 20);
$table->string('email', 100)->unique();
$table->string('password', 255);
$table->string('remember_token', 255);
$table->timestamps();
});
Password Change Controller function
public function postUpass() {
$user = User::find(Input::get('uid'));
if((Input::get('password'))==(Input::get('passconf'))){
$user->password = Hash::make(trim(Input::get('password')));
$user->save();
echo "Done";
//echo Hash::make(trim(Input::get('password')));
}
else {
echo "Check Passwords Again";
}
Someone please help me with this.
I can't comment yet, so I have to ask for your view by posting an answer. Though I would like to also suggest changing the way you are accessing the user in the postUpass function as that could easily be changed by the end user to update another user's password.
//change this
$user = User::find(Input::get('uid'));
//to this
$user = Auth::user();
//since the user needs to be logged in
//to change their password anyway
Also, are you saying that once you post to the postUpass function you are always being returned the 'Check passwords again' notice?

Categories