Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auth filter redirecting back to original POST request in Laravel

It seems that Redirect::guest('login') will only work for GET requests. Ie. it will redirect an authenticated user to the original intended URL (GET).

In a situation where there is a POST request, is there a way for the auth filter to continue on to POST to a URL after the user has successfully logged on?

A simple example: I want to show a form available to anyone to view. Upon hitting the submit button, the auth filter kicks in which will bring a guest to the login page. After successful authentication, I would like the submit request (ie. POST request) to continue onwards.

like image 677
mistermat Avatar asked Sep 19 '13 16:09

mistermat


People also ask

How do I redirect back to original URL after successful login in Laravel?

You may use Redirect::intended function. It will redirect the user to the URL they were trying to access before being caught by the authenticaton filter.

What is Auth :: Routes () in Laravel?

Auth::routes() is just a helper class that helps you generate all the routes required for user authentication. You can browse the code here https://github.com/laravel/framework/blob/5.8/src/Illuminate/Routing/Router.php instead.

What is Guard () in Laravel?

Guards define how users are authenticated for each request. For example, Laravel ships with a session guard which maintains state using session storage and cookies. Providers define how users are retrieved from your persistent storage.

How does Laravel Auth attempt work?

The attempt method accepts an array of key / value pairs as its first argument. The password value will be hashed. The other values in the array will be used to find the user in your database table. So, in the example above, the user will be retrieved by the value of the email column.


2 Answers

I had the same desire to redirect back to a POST request with the original input. I could not find an existing way to do this in Laravel except for redirecting to to the intended URL via GET.

Laravel 5

I first solved this in Laravel 4 following the outline below but found the exact same setup not to work in Laravel 5. Follow the outline for Laravel 4 but instead of the creating the IntendedUrlServiceProvider create a Middleware.

  1. The problem is that in Laravel 5 the session seems to be started with the StartSession which runs after all of the ServiceProviders.

/app/Http/Middleware/IntendedUrl.php

<?php namespace App\Http\Middleware;

use Closure;
use Request;
use Session;

class IntendedUrl {

    /**
     * This loads saved POST input data and changes the method to POST if a visitor tried to access a page
     * but was blocked via an auth filter. Auth filter saves data via the Redirect::guest() and after
     * login it needs to be repopulated to simulate a POST.
     *
     * GET requests also may pass through here. I am less certain if it is required for them but shouldn't hurt
     * and may help load any input data.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        // Check to see if we were redirected to this page with the Redirect::intended().
        //      We extended the class to track when the redirect occurs so we know to reload additional request data
        if (Session::has('intended.load')) {
            // intended.load could be set without these being set if we were redirected to the default page
            //      if either exists, both should exist but checking separately to be safe
            if (Session::has('intended.method')) {
                Request::setMethod(Session::get('intended.method'));
            }
            if (Session::has('intended.input')) {
                Request::replace(Session::get('intended.input'));
            }
            // Erase all session keys created to track the intended request
            Session::forget('intended');

            // Laravel 5.2+ uses separate global and route middlewares. Dispatch altered request as the route type changed. *Credit to Munsio in answer below
            return \Route::dispatch($request);
        }

        return $next($request);
    }

}
  1. Then instead of adding the IntendedUrlServiceProvider as in step 4 below add the new middleware after the StartSession in the $middleware array of /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\IntendedUrl',
];
  • Also of note, just for organization I moved my customer service providers to the new standard /App/Providers and changed their namespace.

Laravel 4

I decided to extend the framework to add this feature. It will be hard to detail my complete solution, but here is an outline. To do this you will need to be pretty familiar with the framework and read up on how to extend it. http://laravel.com/docs/extending#ioc-based-extension

I also referenced Taylor's book "Laravel from Apprentice to Artisan"

  1. Extend the Redirector class to record additional info on the intended request.

    <?php namespace GQ\Routing;
    class Redirector extends \Illuminate\Routing\Redirector {
        /**
         * ** Extended to add functionality for restoring POST input and the POST method after a login
         */
        public function guest($path, $status = 302, $headers = array(), $secure = null)
        {
            // Recording the method and input for the request so that it can be reloaded after being redirected back to the intended page
            $this->session->put('intended.method', $this->generator->getRequest()->getMethod());
            $this->session->put('intended.input', $this->generator->getRequest()->all());
    
            return parent::guest($path, $status, $headers, $secure);
        }
    
        /**
         * ** Extended to record in the session when we redirect to an intended page so method and input can be loaded on the next page
         */
        public function intended($default = '/', $status = 302, $headers = array(), $secure = null)
        {
            $redirect_response = parent::intended($default, $status, $headers, $secure);
    
            // Set the intended.load session variable so we know we returned to the intended page and can load the additional method and input
            return $redirect_response->with('intended.load', true);
        }
    }
    ?>
    
  2. Create a new Service Provider that writes over "redirect" in the IOC container. I originally tried extending the RoutingServiceProvider but had trouble with that working.

    <?php namespace App\Providers;
    
    use GQ\Routing\Redirector;
    use Illuminate\Support\ServiceProvider;
    class RedirectServiceProvider extends ServiceProvider {
    
        protected $defer = true;
    
        /**
         * Register the Redirector service.
         *
         * ** Copy of class registerRedirector from RoutingServiceProvider,
         * using a different "use" statement at the top to use the extended Redirector class
         * Extending the RoutingServiceProvider was more of a pain to do right since it is loaded as a base provider in the Application
         *
         * @return void
         */
        public function register()
        {
            $this->app['redirect'] = $this->app->share(function($app)
            {
                $redirector = new Redirector($app['url']);
    
                // If the session is set on the application instance, we'll inject it into
                // the redirector instance. This allows the redirect responses to allow
                // for the quite convenient "with" methods that flash to the session.
                if (isset($app['session.store']))
                {
                    $redirector->setSession($app['session.store']);
                }
    
                return $redirector;
            });
        }
        public function provides() {
            return array('redirect');
        }
    }
    
  3. Create a new service provider which will set the intended method and input after the redirect.

    <?php
    
    namespace GQ\Providers;
    
    use Illuminate\Support\ServiceProvider;
    
    class IntendedUrlServiceProvider extends ServiceProvider {
        /**
         * Bootstrap the application events.
         *
         * @return void
         */
        public function boot() {
            // Check to see if we were redirected to this page with the Redirect::intended().
            //        We extended the class to track when the redirect occurs so we know to reload additional request data
            if (\Session::has('intended.load')) {
                // intended.load could be set without these being set if we were redirected to the default page
                //        if either exists, both should exist but checking separately to be safe
                if (\Session::has('intended.method')) {
                    \Request::setMethod(\Session::get('intended.method'));
                }
                if (\Session::has('intended.input')) {
                    \Request::replace(\Session::get('intended.input'));
                }
                // Erase all session keys created to track the intended request
                \Session::forget('intended');
            }
        }
    
        public function register() {
        }
    }
    
  4. Finally add your 2 new service providers to your providers array in app/config/app.php

    'GQ\Providers\RedirectServiceProvider',
    'GQ\Providers\IntendedUrlServiceProvider',
    

Hopefully this steers you in a good direction. This has worked for me but I have not tested it extensively. Maybe if it continues to work well we could build a composer package or get the ability included in Laravel.

like image 57
Zack Huston Avatar answered Sep 20 '22 00:09

Zack Huston


In Laravel 5.2 they implemented the middleware groups and for new projects they apply the default "web" group to the whole routes.php file.

The Problem:
The group middleware's are called after the route was determined so by simply changing the method of the current request has no effect.

There are two different approaches to bring it back to work (I suggest nr. 2)


Solution 1:
Put the session and intended url middleware back to the global middleware array in the Kernel.php file - That's easy and it would work but sometimes you have beside your project some REST-API routes and imho an session has nothing to do there.


Solution 2:
Put the intended url class after the ShareErrorsFromSession class in the web group and adopt the file as shown below:

// Erase all session keys created to track the intended request
Session::forget('intended');

$response = Route::dispatch($request);
return $response;

By dispatching the modified requests we break the current lifecycle and call a new one, so the correct route is used and it work as expected. The second approach gives us also the possibility to define the intened url functionality only to selected routes if you want that.

like image 43
Munsio Avatar answered Sep 21 '22 00:09

Munsio