Mircha Emanuel D'Angelo

Customizing User Login Redirects in Laravel with Jetstream

Mircha Emanuel D'Angelo

When working with Laravel and Jetstream, you might want to tailor the post-login redirection based on the user's role. Here's a neat way to handle it:

First, let's create our custom LoginResponse class that implements the Laravel\Fortify\Contracts\LoginResponse in Http\Responses directory.

To keep things tidy, we'll import it with an alias:

use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;

Next, we'll implement LoginResponseContract method to our class:

use App\Roles;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Laravel\Fortify\Contracts\LoginResponse as LoginResponseContract;
use Symfony\Component\HttpFoundation\Response;

class LoginResponse implements LoginResponseContract
{

    public function toResponse($request): JsonResponse|RedirectResponse|Response
    {
        return $request->wantsJson()
                    ? response()->json(['two_factor' => false])
                    : redirect()->intended($this->redirectTo());
    }

    private function redirectTo(): string
    {
        if (auth()->user()->hasRole(Roles::Administrator)) {
            return route('admin.dashboard');
        }

        return route('pages.logout');
    }
}

Now, let's make sure our LoginResponse is returned by registering it in the JetstreamServiceProvider. In the boot method, add:

public function boot(): void
{
    $this->configurePermissions();

    Jetstream::deleteUsersUsing(DeleteUser::class);


    // register new LoginResponse
    $this->app->singleton(
        \Laravel\Fortify\Contracts\LoginResponse::class,
        App\Http\Responses\LoginResponse::class
    );

    $this->app->singleton(
       \Laravel\Fortify\Contracts\TwoFactorLoginResponse::class,
       App\Http\Responses\LoginResponse::class
    );
}

To verify everything works as expected, we can write a feature test (here, I'm using Pest):

test('Fortify LoginResponse is instance of custom LoginResponse', function() {
    // Act & Assert
    expect(app(\Laravel\Fortify\Contracts\LoginResponse::class))
        ->toBeInstanceOf(App\Http\Responses\LoginResponse::class);

 });

test('Fortify TwoFactorLoginResponse is instance of custom LoginResponse', function () {
    // Act & Assert
    expect(app(\Laravel\Fortify\Contracts\TwoFactorLoginResponse::class))
        ->toBeInstanceOf(App\Http\Responses\LoginResponse::class);
});

Additionally, we can test the actual redirect after login. With Jetstream, the process would look something like this:

it('redirects to admin.dashboard if user has `administrator` role', function() {
    // Arrange
    // create administrator role
    Role::findOrCreate(Roles::Administrator->value);

    $user = User::factory()->create();
    $user->assignRole(Roles::Administrator->value);

    // Act & Assert
    $response = $this->post('/login', [
        'email' => $user->email,
        'password' => 'password',
    ]);

    $this->assertAuthenticated();
    $response->assertRedirect(route('admin.dashboard'));
});

And there you have it – a custom redirection based on user roles in Laravel with Jetstream. Implementing this ensures that your users land on the right dashboard and enhances the overall user experience.

Tailoring the redirection logic can extend beyond basic role checks. By customizing LoginResponse, you can integrate more complex workflows, such as redirecting users to a page that they were attempting to access before login or to a welcome page on their first login.