Laravel 5 redirect()->back() doesn't work as expected

10,989

Solution 1

If no HTTP_REFERER is set, Laravel tries to get the last request from the session. This URL is stored in Illuminate\Session\Middleware\StartSession:

protected function storeCurrentUrl(Request $request, $session)
{
    if ($request->method() === 'GET' && $request->route() && ! $request->ajax())
    {
        $session->setPreviousUrl($request->fullUrl());
    }
}

As you can see there are a few conditions, which unfortunately your JS requests fulfill:

  1. GET method
  2. Handled by a route
  3. Not an ajax request

Solution

As I see it, you have two options.

Disable the middleware

Warning: I haven't tested this and there could be side-effects

Go to app/Http/Kernel.php and remove 'Illuminate\Session\Middleware\StartSession', from the $middleware. Now if no HTTP_REFERER is set, Laravel will simply redirect to the root of your application (/)

Extend the middleware and add additional constraints

You could extend the middleware and add some additional checking:

class CustomStartSession extends \Illuminate\Session\Middleware\StartSession {
    public function storeCurrentUrl(Request $request, $session){
        if(!ends_with($request->url(), '.js')){
            parent::storeCurrentUrl($request, $session);
        }
    }
}

Then enable it by replacing the original StartSession in app/Http/Kernel.php with yours and any request with an URL ending in .js will not be saved as "previous url"

Update

Apparently the middleware needs to be registered as a singleton to be fully functional. You can do that by adding this in the register method of one of your service providers (e.g. AppServiceProvider)

$this->app->singleton('App\Http\Middleware\CustomStartSession');

Solution 2

My current work-around is to extend the BaseController like this:

<?php namespace App\Http\Controllers;

use Illuminate\Foundation\Bus\DispatchesCommands;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;

abstract class Controller extends BaseController
{
    use DispatchesCommands, ValidatesRequests;

    /**
     * The fallback url for redirects...
     * Prevent faulty redirect if 'HTTP_REFERER' not set.
     *
     * @var string
     */
    private static $back_url = '';

    /**
     * @param string $back_url The fallback url for redirects...
     */
    public static function setBackUrl($back_url)
    {
        self::$back_url = $back_url;
    }

    /**
     * @return string
     */
    protected static function getBackUrl()
    {
        return self::$back_url;
    }

    /**
     * @return \Illuminate\Http\RedirectResponse
     */
    public static function redirect_back()
    {
        $back = self::getBackUrl();
        if (!empty($_SERVER['HTTP_REFERER']))
        {
            return redirect()->back();

        } else if (!empty($back)) {
            return redirect()->to($back);

        } else {
            return redirect()->to('/');
        }
    }
}

And then call it like this:

<?php namespace App\Http\Controllers;

use App\Http\Requests;
use App\Http\Controllers\Controller;

/**
 * Class MyController
 *
 * @package App\Http\Controllers
 */
class MyController extends Controller
{
    /**
     * Check permission for specified '$id'.
     *
     * @param int|string $id The datarow id
     * @return boolean
     */
    public function checkPermission($id)
    {...}

    /**
     * Display a listing of the resource.
     *
     * @param int|string $id The datarow id
     * @return Response
     */
    public function index($id)
    {
        if (!$this->checkPermission($id)) {
            self::setBackUrl(\URL::route('my_prefered_fallback_route'));
            return self::redirect_back()
                ->withErrors(["message"]);
        }

        return 'Hello, API';
    }
}

Solution 3

I know this is uggly but you can avoid storeCurrentUrl() by changing request method to POST in your Controller.

$request->setMethod('POST');
Share:
10,989
algorhythm
Author by

algorhythm

Updated on June 04, 2022

Comments

  • algorhythm
    algorhythm almost 2 years

    I've got a problem with redirect()->back() when there is no HTTP_REFERER set.

    Normally when I open my URL with an ID as parameter to show a specific datarow: http://my-domain/module/ID

    If the specified ID is not existent or the current loggedin user has no permission to open it, I make

    if (!$this->checkPermission($id)) {
        return redirect()->back()->withError('message');
    }
    // do my stuff to show the datarow
    

    But when I change the browsers address field and load an non-permitted ID then there is no HTTP_REFERER set and redirect()->back() redirects me to my last loaded JavaScript for_this_view.js (and shows it) added in the view as following:

    app.blade.php

    <!DOCTYPE html>
    <html lang="de">
    <head>
        @section('head')
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    
        {!! HTML::style('assets/jquery-ui/jquery-ui.min.css?ver=1.11.4') !!}
        <!-- some more scripts -->
        @show
    </head>
    
    <body>
        @yield('content')
    </body>
    </html>
    

    show.blade.php

    @extends('app')
    
    @section('head')
        @parent
        {!! HTML::script('for_this_view.js') !!}
    @stop
    
    @section('content')
        <!-- display datarow -->
    @stop
    

    What is redirect()->back() doing here? If there is no URL to redirect back laravel is guessing one? And why? Maybe to ever have a way to redirect.

    That make me not able to use this function.

    UPDATE

    Ah, maybe I found my problem...

    I've tried to dynamically load JavaScript's if they exist in a specific folder. I do that, because I want my JavaScript's be parsed like a view to get Blade Syntax in it. I added a specific folder to the /config/view.php and made files like for_this_view_js.blade.php. They are dynamically added as for_this_view.js in the routes.php.

    And therefore I made something like:

    /* load view javascripts */
    /** @var \SplFileInfo $path */
    foreach (new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator(public_path())
        ) as $path)
    {
        if ($path->isFile()
            && ends_with($path->getFilename(), 'js.blade.php'))
        {
            Route::get('url_to_javascript.js', function() {
                $contents = View::make('for_this_view_js(.blade.php)');
                $response = Response::make($contents);
                $response->header('Content-Type', 'application/javascript');
                return $response;
            });
        }
    }