Laravel Fortify Customize Authentication Redirect - laravel

In Laravel fortify on customization authentication process, i can not able to redirect to login page with error message which we were able to do in Auth.
Here is the customization documentation link: https://jetstream.laravel.com/1.x/features/authentication.html#customizing-the-authentication-process
if ($user && Hash::check($request->password, $user->password) && $user->status == 'active') {
return $user;
} elseif ($user->status == 'inactive') {
//redirect with some error message to login blade
} elseif ($user->status == 'blocked') {
//redirect with some error message to login blade
}
Please help me out on this.

For those coming from google search who use Laravel Jetstream (which uses Fortify):
Snapey of Laracasts answered this question and made a tutorial which uses your own LoginResponse to override the default login behavior.
I made mine like this but of course you should create your own according to your needs.
// app/Http/Responses/LoginResponse.php
namespace App\Http\Responses;
use Illuminate\Support\Facades\Auth;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class LoginResponse implements LoginResponseContract
{
/**
* #param $request
* #return mixed
*/
public function toResponse($request)
{
// replace this with your own code
// the user can be located with Auth facade
$home = Auth::user()->is_admin ? config('fortify.dashboard') : config('fortify.home');
return $request->wantsJson()
? response()->json(['two_factor' => false])
: redirect($home);
}
}
The next step it to modify JetstreamServiceProvider to use your LoginReponse
public function boot()
{
$this->configurePermissions();
Jetstream::deleteUsersUsing(DeleteUser::class);
// register new LoginResponse
$this->app->singleton(
\Laravel\Fortify\Contracts\LoginResponse::class,
\App\Http\Responses\LoginResponse::class
);
}
Hope it saves you time.

I understand your frustration with the documentation (or lack thereof). I had a similar problem and this is how I managed to do it:
if ($user && in_array($user->status_id, [1,2,3])) {
if (Hash::check($request->password, $user->password)) {
return $user;
}
}
else {
throw ValidationException::withMessages([
Fortify::username() => "Username not found or account is inactive. Please check your username.",
]);
}
https://github.com/laravel/fortify/issues/94#issuecomment-700777994

For SPA applications that only want a custom JSON response rather than the default {two_factor: false}.
Create a custom response that implements LoginResponse contract of Fortify (in this case I'm returning the user object):
<?php
declare(strict_types=1);
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
use Symfony\Component\HttpFoundation\Response;
class LoginResponse implements LoginResponseContract
{
public function toResponse($request): Response
{
return response()->json(auth()->user());
}
}
Add this line to the boot method of FortifyServiceProvider:
$this->app->singleton(LoginResponseContract::class, LoginResponse::class);
Making sure you've imported the namespaces correctly:
use App\Http\Responses\LoginResponse;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
If you are still not getting a JSON from the server, make sure your request has the right headers. It should be an XHR request that accepts application/json.
Inspired by this article from Laravel News.

for example - custom redirect after login/logout/register. By request parameter - alias
In FortifyServiceProvider class
Add.
use Laravel\Fortify\Contracts\LoginResponse;
use Laravel\Fortify\Contracts\LogoutResponse;
use Laravel\Fortify\Contracts\RegisterResponse;
And then in register method.
$this->app->instance(LogoutResponse::class, new class implements LogoutResponse {
public function toResponse($request)
{
return redirect('/'.$request->alias);
}
});
$this->app->instance(LoginResponse::class, new class implements LoginResponse {
public function toResponse($request)
{
return redirect()->intended('/'.$request->alias.'/admin/dashboard');
}
});
$this->app->instance(RegisterResponse::class, new class implements RegisterResponse {
public function toResponse($request)
{
return redirect('/'.$request->alias.'/admin/dashboard');
}
});

goto \config\fortify.php at line 66, change home value to any path you want
'home' => '/dashboard',

Currently, Fortify doesn't have a way to customize redirects.
There is an open issue requesting this bahavior here: https://github.com/laravel/fortify/issues/77
There is a good chance this will be added soon!

Related

Why making additive checks in app/Http/Middleware/HandleInertiaRequests.php on users.status I got error?

In laravel 9/Inertiajs 3/vuejs 3 app users table has status field and I need to make additive checks for its value
and in app/Http/Middleware/HandleInertiaRequests.php I make :
use Laravel\Fortify\Fortify;
// use Illuminate\Support\Facades\Auth;
use Auth;
class HandleInertiaRequests extends Middleware
{
protected $rootView = 'app';
public function share(Request $request): array
{
if (auth()->user()->status !== 'A') {
Auth::logout(); // I GOT “Method Illuminate\Auth\RequestGuard::logout does not exist.” error on this line
// $this->guard()->logout(); // uncommented this way does not work
// auth()->logout();
throw ValidationException::withMessages([
Fortify::username() => "Account is not active",
]);
return redirect('/login');
}
Looks like that is invalid way...
How can I do it ?
Thanks in advance!

Redirect Laravel 8 Fortify

I need some help with redirects after login with Laravel 8 Fortify. I know how to do it in a login controller but with Fortify there is a LoginResponse that I am not sure how to do this. I know in RouteService Provider I can change it to whereever but I have roles that I want to redirect to different dashboards based on role.
In the old Login Controller I would do the following. How would I change this to the LoginResponse for Fortiy?
public function redirectTo()
{
if(Auth::user()->hasRole('admin')){
$this->redirectTo = route('admin.dashboard');
return $this->redirectTo;
}
if(Auth::user()->hasRole('manager')){
$this->redirectTo = route('manager.dashboard');
return $this->redirectTo;
}
if(Auth::user()->hasRole('employee')){
$this->redirectTo = route('employee.dashboard');
return $this->redirectTo;
}
}
You can Customize Redirects with Fortify by binding your own implementation of the LoginResponse. You could add the following to the register method of your FortifyServiceProvider:
use Laravel\Fortify\Contracts\LoginResponse;
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->instance(LoginResponse::class, new class implements LoginResponse {
public function toResponse($request)
{
if($request->user()->hasRole('admin')){
return redirect()->route('admin.dashboard');
}
if($request->user()->hasRole('manager')){
return redirect()->route('manager.dashboard');
}
if($request->user()->hasRole('employee')){
return redirect()->route('employee.dashboard');
}
}
});
}
If you'd prefer you can always create the class in an actual file instead of using an anonymous class.
Here is a Laravel News post that goes into it in a little more details.
Since you seem to have a custom dashboard for each role, if it were me instead of changing the under the hood as Rwd suggested I would make my default redirect page which is in /config/fortify.php as the home directory to be the page that handles the redirect. So your route('dashbord') will decide which dashboard the user needs:
Route::middleware([...])->group(function () {
Route::get('/dashboard', function () {
if(Auth::user()->hasRole('admin')){
$redirectTo = route('admin.dashboard');
}
if(Auth::user()->hasRole('manager')){
$redirectTo = route('manager.dashboard');
}
if(Auth::user()->hasRole('employee')){
$redirectTo = route('employee.dashboard');
}
return redirect()->to($redirectTo);
})->name('dashboard');
});

Laravel 8 jetstream stack inertia. Redirect to Home after Login, instead of users choice

What happens to me is that if the user puts the url: project.test/med, the system redirects to Login but then it does not redirect to dashboard but to med. In RedirectIfAuthenticated the expression Auth::guard($guard)->check() is not evaluated or at least in the Log it is not shown, so I am not able to identify what is happening.
/** RedirectIfAuthenticated.php */
public function handle($request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
   Log::info($request);
   Log::info(Auth::user());
foreach ($guards as $guard) {
     Log::info(Auth::guard($guard)->check());
     Log::info(Auth::check());
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
      }
   }
  Log::info(Auth::check());
   Log::info('end');
return $next($request);
 }
/** web.php */
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return Inertia\Inertia::render('Dashboard');
})->name('dashboard');
Route::middleware(['auth:sanctum','verified'])->get('/med', function (Request $request) {
return Inertia\Inertia::render('Med');
})->name('med');
Go to config/fortify.php and modify this line:
'home' => RouteServiceProvider::HOME,
to:
'home' => function(){
//if you want to go to a specific route
return route('dashboard');
//or if you have a bunch of redirection options
if (Auth::user()->hasRole('admin')) {
return route('admin.dashboard');
}
else{
return route('guest.dashboard');
}
}
EDIT: Fortify now ships with a simpler way of doing this. See this answer for simplified way.
The RedirectIfAuthenticated middleware does not do what you think it does. This middleware checks to see if a user is authenticated when trying to access a route with the guest middleware attached to it. So after you login to the application and you try to access /login route, this middleware will intercept the request and redirect the user to the RouteServiceProvider::HOME path.
With that being said, at the time of this writing, Laravel Fortify does not offer the ability to easily modify where the user gets redirected after registering or logging in to the application. See issue for more info
To get this to work you will have to go through a few steps.
Step 1 - Create new LoginResponse class
Anywhere in your application create a LoginResponse class and implement Laravel\Fortify\Contracts\LoginResponse.
<?php
namespace App\Http\Responses;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class LoginResponse implements LoginResponseContract
{
/**
* #inheritDoc
*/
public function toResponse($request)
{
return $request->wantsJson()
? response()->json(['two_factor' => false])
: redirect()->intended(config('fortify.home')); // This is the line you want to modify so the application behaves the way you want.
}
}
Step 2 - Override the default Fortify Login Response
In this step you have to tell Laravel to use your login response instead of the default Fortify response.
<?php
namespace App\Providers;
use App\Http\Responses\LoginResponse;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(
LoginResponseContract::class, // Contract is required in order to make this step work.
LoginResponse::class,
);
}
//...
}
Once you're done making the changes the user will be redirected to wherever you set it to be.
There is a simpler solution to this.
If you look in config/fortiy.php you will see the home variable is set to:
'home' => RouteServiceProvider::HOME,
This refers to a public const variable in app/Providers/RouteServiceProvider.php
So to redirect users to the home page after they login you can simply change the home variable:
public const HOME = '/';
Doing it this way is slightly less complex and saves you having to create a whole new login response class.
If the changes don't take effect straight away try running:
php artisan optimize
Just open fortify in the config folder
Search for 'home' => RouteServiceProvider::HOME
and replace it but your code logic and don't forget to add Auth facades
use Illuminate\Support\Facades\Auth;
like is :
'home' => function () {
$is_active = Auth::user()->is_active;
$role = Auth::user()->role;
if ($is_active == 1) {
if ($role== 1) {
return '/admin/dashboard';
} else {
return '/';
}
} else {
return '/login';
}
},

Implicit route model binding 401 unauthorized

I have a super simple learning app. My Laravel version is 5.5.13. A User can create a Pet. I am implicitly throwing 404 but I need to also implicitly throw 401 is this possible?
Details on setup:
Pet model:
class Pet extends Model
{
protected $fillable = ['name', 'user_id'];
public function user()
{
return $this->belongsTo('App\User');
}
}
And User model giving the relationship hasMany:
class User extends Authenticatable
{
use Notifiable;
// ... some stuff hidden for brevity
public function pets()
{
return $this->hasMany('App\Pet');
}
}
I used implicit route model binding to throw 404 status when the id is not found like this:
Route::group(['middleware' => 'auth:api'], function() {
Route::get('pets', 'PetController#index');
Route::get('pets/{pet}', 'PetController#show');
Route::post('pets', 'PetController#store');
Route::put('pets/{pet}', 'PetController#update');
Route::delete('pets/{pet}', 'PetController#delete');
});
Notice the {pet} instead of {id}.
However I also want to throw 401 unauthorized status if the $pet->user_id does not equal Auth::guard('api')->user()->id. Is this implicitly possible?
If not possible, may you please show me how to explicitly do this in the controller? I was doing this, but I don't think it's the recommended way is it?
public function show(Pet $pet)
{
if ($pet->user_id != Auth::guard('api')->user()->id) {
return response()->json(['message'=>'Not authenticated to view this pet'], 401);
}
return $pet;
}
The more Laravel centric way todo this is using policies.
Then for each action you want to authorize you register them inside your policy. Your show method would then become:
public function show(Pet $pet)
{
$this->authorize('show', $pet);
return $pet;
}
So your steps would be:
Create a new Policy for Pets
Add the actions you want to authorize
Register the policy in the AuthServiceProvider
Use the authorize
call inside your Controller action

How to create Policies without parameters in Laravel 5.2?

I am trying to create a project to add edit contacts.
To restrict the user can add/edit their own contacts, So added policy ContactPolicy as below
<?php
namespace App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use App\User;
use App\Contact;
class ContactPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function before($user, $ability)
{
if ($user->isAdmin == 1) {
return true;
}
}
public function add_contact(User $user)
{
return $user->id;
}
public function update_contact(User $user, Contact $contact)
{
return $user->id === $contact->user_id;
}
}
And registered in AuthServiceProvider as below
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Contact::class => ContactPolicy::class,
];
To restrict adding of contact by current user I added Gate in my controller function as below without passing parameters
if (Gate::denies('add_contact')) {
return response('Unauthorized.', 401);
}
Even if current user tries to add contact, it shows Unauthorized message.
How will I solve this problem?
Policies are intended to have all authorization logic related to a certain class of resource in one place.
So, you define Contact::class => ContactPolicy::class meaning the ContactPolicy has all policies regarding Contacts. When you write Gate::denies('add_contact'), how could the framework know which policy to search? You have to pass an object of type Contact as second parameter in order to access the ContactPolicy.
Anyway, there is in fact a place to write authorization logic which is not particular to any class of resource. In the method boot of AuthServiceProvider you could add
$gate->define('add_contact', function ($user) {
return $user->id;
});
By the way, what's the intention with returning the user id? I think you just need to return a boolean.
Also, if you are checking the permission within a controller, you should just call $this->authorize('add_contact') and the controller itself will check and return a Forbidden response (for which the proper code is 403, not 401) if it fails, no need to return it yourself.

Resources