How to add additional condition to Laravel 6 Auth attempt() method? - laravel

I'm using Larvel 6
I want to add this feature to my app:
The app receives User's mobile number, then generates a mobile_number_verification_code like 145787 and updates user's table! then the generated code will be sent as SMS for user! and he/she enters the code to login!
Notice: the code can be used just 2 minutes after the generation time
this will be the User model
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique();
$table->string('username')->unique()->nullable();
$table->string('mobile_number')->unique()->nullable();
$table->string('mobile_number_verification_code')->nullable();
$table->string('mobile_number_verification_code_expires_at')->nullable();
$table->bigInteger('mobile_number_verified_at')->nullable();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
I want to use the Laravel default auth way, so I can use this code for attempt to login:
/**
* Attempt to log the user into the application.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function attemptLogin(Request $request)
{
return $this->guard('users')->attempt(
[
'mobile_number' => '9128889966', // the mobile number
'mobile_number_verification_code' => '145787' // the code which user has received
]
, $request->filled('remember')
);
}
everything seems alright but one important point is missed:
the code can be used just 2 minutes after the generation time
How can I check the code is expired or not?
I know that I can check it after user login, then if the code is expired, use Auth::logout(); to logout him/her! but is it the best way?
It is not better to edit the attempt() method?

You can check expiration datetime on attemptLogin method of your LoginController like this:
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
//...
/**
* Attempt to log the user into the application.
*
* #param \Illuminate\Http\Request $request
* #return bool
*/
protected function attemptLogin(Request $request)
{
return \App\User::where('mobile_number', $request->mobile_number)
->whereDate('mobile_number_verification_code_expires_at', '>=', now())
->exists()
&& $this->guard()->attempt([
'mobile_number' => $request->mobile_number
'mobile_number_verification_code' => $request->mobile_number_verification_code
], $request->filled('remember')
);
}
Another way is to define a custom UserProvider that implements Illuminate\Contracts\Auth\UserProvider interface or extends Illuminate\Auth\EloquentUserProvider and override validateCredentials and/or retrieveByCredentials methods. Then register your customized UserProvider and set it as custom driver on config/auth.php file. See Laravel docs form more info about this.

Related

Instantly Logging in users without them verifying their email in laravel 8 authentication

So I used this preset for the authentication for my laravel app and it is sending me verification emails but I want to redirect them to a certain page telling them to verify their emails first before logging in but instead, it will automatically log them into my app without them verifying their email.
This is my code
Homecontroller
public function __construct()
{
$this->middleware(['auth','verified']);
}
web.php
Auth::routes(['verify' => true]);
then my user model is this
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
I watch tutorials similar to mine and it works on them with the changes above but on my end it seems it's not working, it will directly go to my dashboard.
This is my database migration
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
you can by removing this in RegisteredUserController.php after event(new Registered($user)) :
Auth::login($user); // this function is responsible to user logged in automatically to your website
and in your web.php file try this :
Route::middleware(['auth', 'verified'])->group(function () {
// your routes here exemeple :
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});
instead of : Auth::routes(['verify' => true]);
i hope it was useful

Laravel authorization/policy prevents access for everyone

I found similar title and similar asked question on this website when I was researching to solve the problem. But none of posted answers helped me. This question might be duplicated but I could not solve the problem using existing questions on StackOverflow.
I'm trying to prevent access to users who are not logged in OR who are not member of "School" model!
In "web.php" file I used "middleware("auth")" to prevent access to users who are not logged in.
Now I created a "Policy" named "SchoolPolicy" to prevent access to users who are not member of "Schools" database/model.
When I call "view" method from SchoolPolicy, it prevents access for all authorized and unauthorized users!
I also checked and I realized "School" model returns "null" when I try to catch "user_id" foreign key from "schools" table.
The below piece of code is the way I created "Schools" table using Migration:
Schema::create('schools', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('school_name');
$table->string('school_address')->nullable();
$table->string('school_email');
$table->string('school_phone')->nullable();
$table->string('url');
$table->longText('descriptions')->nullable();
$table->timestamps();
});
This is the route to view any school which is created by any user (URL can be dynamic)
Route::group(['middleware' => 'auth'], function () {
Route::get('/schools/{url}', [ViewSchool::class, 'index'])->name('yourschool.show');
});
And this is "School" model. I used php artisan make:model School command to create this model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Auth;
class School extends Model{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'user_id',
'school_name',
'school_address',
'school_email',
'school_phone',
'url',
'descriptions'
];
}
In this section I created School Policy. However I used Laravel 8 but I also registered created Policy manually
SchoolPolicy
<?php
namespace App\Policies;
use App\Models\School;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class SchoolPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* #param \App\Models\User $user
* #return mixed
*/
public function viewAny(User $user)
{
//
}
/**
* Determine whether the user can view the model.
*
* #param \App\Models\User $user
* #param \App\Models\School $school
* #return mixed
*/
public function view(User $user, School $school)
{
return $user->id == $school->user_id;
}
}
In AuthServiceProvider.php I registered SchoolPolicy like this:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\User;
use App\Models\School;
use App\Policies\SchoolPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
School::class => SchoolPolicy::class
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
}
}
"ViewSchool.php" file where I want to use authorize method:
<?php
namespace App\Http\Controllers\Schools;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\School;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
class ViewSchool extends Controller
{
public function index (School $school) {
$this->authorize('view', $school);
return view('layouts.viewschool');
}
}
I tried many ways to solve the problem, but none of them properly worked:
First Try:
public function index (School $school) {
$this->authorize('view', $school);
}
Second Try:
public function index () {
$this->authorize('view', School::class);
}
I even tried to print any output from School model but I receive "null":
public function index (School $school) {
dd($school->user_id);
}
I followed all tutorials on YouTube and official Laravel website, but in my examples I gave you, authorization doesn't work properly.
Please help me to solve this problem.
Thank you

only approved column returned back on any relationship

i have table products.
i need make something in model only return product where approved is equal to 1
this my schema
Schema::create('products', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->text('description');
$table->unsignedInteger('quantity');
$table->unsignedInteger('subcategory_id')
->references('id')->on('subcategories')->onDelete('cascade');
$table->decimal('price');
$table->decimal('discount_price');
$table->decimal('super_discount')->nullable();
$table->string('cover');
$table->unsignedInteger('brand_id')
->references('id')->on('brands')->onDelete('cascade');
$table->unsignedInteger('category_id')
->references('id')->on('categories');
$table->string('color');
$table->string('size_id')->references('id')->on('sizes')->nullable();
$table->decimal('rate');
$table->enum('made_in',['turkey','china','egypt']);
$table->string('serial');
$table->boolean('approved')->default(0);
$table->timestamps();
$table->unique(['name','size_id','color']);
});
i hope this is valid.
You can make use of Global Scopes
Global scopes allow you to add constraints to all queries for a given
model. Laravel's own soft deleting functionality utilizes global
scopes to only pull "non-deleted" models from the database. Writing
your own global scopes can provide a convenient, easy way to make sure
every query for a given model receives certain constraints.
Writing Global Scopes Writing a global scope is simple. Define a class
that implements the Illuminate\Database\Eloquent\Scope interface. This
interface requires you to implement one method: apply. The apply
method may add where constraints to the query as needed:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class ApprovedScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* #param \Illuminate\Database\Eloquent\Builder $builder
* #param \Illuminate\Database\Eloquent\Model $model
* #return void
*/
public function apply(Builder $builder, Model $model)
{
return $builder->where('approved', 1);
}
}
There is not a predefined folder for scopes in a default Laravel
application, so feel free to make your own Scopes folder within your
Laravel application's app directory.
Applying Global Scopes To assign a global scope to a model, you should
override a given model's boot method and use the addGlobalScope
method:
<?php
namespace App;
use App\Scopes\ApprovedScope;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new ApprovedScope);
}
}
After adding the scope, a query to Product::all() will produce the
following SQL:
select * from `products` where `approved` = 1
Docs

How to prevent access to Model by id with JWTAuth and Laravel?

I'm building some API endpoints with Laravel, and I'm using JWTAuth as the token provider for authorizing requests.
I've gotten the setup to protect a group of API routes that works correctly using:
Route::group(['prefix' => '/v1', 'middleware' => 'jwt.auth'], function() {
Route::resource('messages', 'MessagesController');
});
The Message model belongs to a User
I'm attempting to perform some requests using that relationship while keeping the request from providing data that didn't belong to the user:
Get a list of the logged in user's messages
Get an individual message of the logged in user
The main question I have is how to prevent the user from accessing a Message that does not belong to them. I have this in my controller:
public function show($message_id)
{
$message = Message::findOrFail($message_id);
return $message;
}
That obviously will return the Message regardless of whether or not it belongs to that user. Any suggestions on how to improve that to restrict accessing other user's messages?
For the listing of all messages, I was able to do the following in the controller:
public function index()
{
$user_id = Auth::User()->id;
$messages = Message::where('user_id', $user_id)->paginate(10);
return $messages;
}
This works, but I'm not sure this is the best way to do it. Maybe it is, but some feedback would be appreciated. I'm confused as to whether or not the middleware should be handling what the user has access to or if it should be part of the eloquent query?
Your question is,
how to prevent the user from accessing a Message that does not belong
to them.
Thats falls under Authorization
You can use Gates to Authorize users and I think it's the best approach here. Fist of all you'd need a Policy object.
<?php
namespace App\Policies;
use App\User;
use App\Message;
class MessagePolicy
{
/**
* Determine if the given Message can be viewed by the user.
*
* #param \App\User $user
* #param \App\Message $Message
* #return bool
*/
public function view(User $user, Message $Message)
{
return $user->id === $Message->user_id;
// Here this User's id should match with the user_id of the Massage
}
}
You can even generate the boilerplate like this,
php artisan make:policy MessagePolicy --model=Message
Then you can register it in your AuthServiceProvider like this,
<?php
namespace App\Providers;
use App\Message;
use App\Policies\MessagePolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Message::class => MessagePolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('view-message', 'MessagePolicy#view');
}
}
And in your Controller you can use it like this,
public function show($message_id)
{
if (Gate::allows('view-message', Message::findOrFail($message_id)) {
return $message;
}
}
Note: Please use this code snippet as a reference or a starting point since I haven't tested it properly. But underlying concept is correct. Use this as pseudocode. If something wrong found, please update it here :)

How can I make the user switch languages in Laravel 5?

I have created a bilingual laravel 5 application that contains two locales, en and ar.
What I want is for the site visitor to be able to change the language of the website by clicking on a link labeled with the language name.
Option 1:
Store user language in database, I have mine in users table. This is to avoid asking user each time they login to your application.
You can set 'en' as default. However if user is a guest we store locale in session.
So your migration might look like this:
<?php
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('email')->unique();
$table->string('password', 60);
$table->string('locale', 5)->default('en');
$table->rememberToken();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('users');
}
}
When user or guest clicks on a certain language link then update user locale in database or store guest choice in a session
Example :
For authenticated user or guest in your controller
public function setLocale($locale){
if(Auth::check()){
$user = User::find(Auth::user()->id);
$user->update(['locale'=>$locale]);
}else{
Session::put('locale',$locale);
}
}
We need to find a way of setting locale on each request because Laravel does not store locale set with App::setLocale() hence we are going to use a Middleware to setLocale on each request.
To understand how Laravel handles App::setLocale() here is the method in Illuminate\Foundation\Application.php that handles setting of locale
public function setLocale($locale)
{
$this['config']->set('app.locale', $locale);
$this['translator']->setLocale($locale);
$this['events']->fire('locale.changed', array($locale));
}
This method calls another method in Translator.php show below:
/**
* Set the default locale.
*
* #param string $locale
* #return void
*/
public function setLocale($locale)
{
$this->locale = $locale;
}
As you can see nothing like caching or session is used to remember locale so we must set it on each request. So lets create a Middleware for it. I will call it LocaleMiddleware.
<?php namespace App\Http\Middleware;
use Closure, Session, Auth;
class LocaleMiddleware {
public function handle($request, Closure $next)
{
if(Auth::user()){
app()->setLocale(Auth::user()->locale);
}elseif($locale = Session::has('locale')){
app()->setLocale($locale);
}
return $next($request);
}
}
Lets set the middleware to run on each request by adding it to $middleware stack in App\Http\Kernel.php
protected $middleware = [
'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
'Illuminate\Cookie\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
'App\Http\Middleware\VerifyCsrfToken',
'App\Http\Middleware\LocaleMiddleware'
];

Resources