ConleeC
ConleeC

Reputation: 347

How to do a redirect from a Slim 4 middleware?

I've been testing the new Slim 4 framework and redirects work fine for me in normal classes, but I cannot seem to get them working in middleware, where a response is dynamically generated (apparently?) by the Request Handler. When I try to redirect with a Location header, it simply fails to redirect, and my route continues to the original location.

Here’s a basic version of my authentication middleware for testing:

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class AuthMiddleware extends Middleware {

    public function __invoke(Request $request, RequestHandler $handler): Response {
        $response = $handler->handle($request);
        $loggedInTest = false;
        if ($loggedInTest) {
            echo "User authorized.";
            return $response;
        } else {
            echo "User NOT authorized.";
            return $response->withHeader('Location', '/users/login')->withStatus(302);
        }
    }
}

Has anybody got this to work? And if so, how did you accomplish it? Thanks in advance.

Upvotes: 4

Views: 7716

Answers (4)

fammi farendra
fammi farendra

Reputation: 17

Use 2 response

namespace App\middleware;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response as Response7;
use Psr\Http\Message\ResponseInterface as Response;

final class OtorisasiAdmin {
     public function __invoke(Request $request, RequestHandler $handler): Response {
        $session = new \Classes\session();
        $session->start();
        $isAdmin=($session->has("login","admin"))?true:false;
        if(!$isAdmin){
            $response = new Response7();
            $error = file_get_contents(__dir__."/../../src/error/404.html");    
            $response->getBody()->write($error);
            return $response->withStatus(404);
        }
        $response=$handler->handle($request);
        return $response;
    }
}

Upvotes: 0

Anastas Dolushanov
Anastas Dolushanov

Reputation: 73

eimajenthat is right, except that you cannot create an instance of interface.

Try this instead:

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;

class AuthMiddleware extends Middleware {

public function __invoke(Request $request, RequestHandler $handler): Response {            
    global $app; // Assuming $app is your global object

    $loggedInTest = false;
    if ($loggedInTest) {
        $response = $handler->handle($request);
        echo "User authorized.";
        return $response;
    } else {
        $response = $app->getResponseFactory()->createResponse();
        // echo "User NOT authorized.";
        return $response->withHeader('Location', '/users/login')->withStatus(302);
    }
}

}

Upvotes: 3

ConleeC
ConleeC

Reputation: 347

I was growing so frustrated by Slim 4 and redirect issues that I took a look at FatFreeFramework and had the exact same problem. So I knew it was something I was doing. My code was putting the app into a never-ending redirect loop. I can make it work by validating the redirect URL like so in FatFreeFramework:

class Controller {

    protected $f3;

    public function __construct() {
        $isLoggedIn = false;
        $this->f3 = Base::instance();
        if ($isLoggedIn == false && $_SERVER['REQUEST_URI'] != '/login') {
            $this->f3->reroute('/login');
            exit();
        }
    }
}

Therefore, although I haven't actually taken the time to test it, I'm assuming I could fix it in Slim 4 by doing something like:

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class AuthMiddleware extends Middleware {

    public function __invoke(Request $request, RequestHandler $handler): Response {
        $response = $handler->handle($request);
        $loggedInTest = false;
        if (!$loggedInTest && $_SERVER['REQUEST_URI'] != '/user/login') {
            return return $response->withHeader('Location', '/users/login')->withStatus(302);
        } else {
            return $response;
        }
    }
}

Does anybody have another idea for how to break a continuous redirect loop? Or is the $_SERVER variable the best option?

Thanks in advance.

Upvotes: 0

eimajenthat
eimajenthat

Reputation: 1338

I think I see the problem with this code.

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;

class AuthMiddleware extends Middleware {

    public function __invoke(Request $request, RequestHandler $handler): Response {
        $response = $handler->handle($request);
        $loggedInTest = false;
        if ($loggedInTest) {
            echo "User authorized.";
            return $response;
        } else {
            echo "User NOT authorized.";
            return $response->withHeader('Location', '/users/login')->withStatus(302);
        }
    }
}

When you call $handler->handle($request), that processes the request normally and calls whatever closure is supposed to handle the route. The response hasn't been completed yet, you can still append stuff to it, but the headers are already set, so you can't do a redirect, because the headers are done.

Maybe try this:

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Response;

class AuthMiddleware extends Middleware {

    public function __invoke(Request $request, RequestHandler $handler): ResponseInterface {            
        $loggedInTest = false;
        if ($loggedInTest) {
            $response = $handler->handle($request);
            echo "User authorized.";
            return $response;
        } else {
            $response = new Response();
            // echo "User NOT authorized.";
            return $response->withHeader('Location', '/users/login')->withStatus(302);
        }
    }
}

If the login test fails, we never call $handler->handle(), so the normal response doesn't get generated. Meanwhile, we create a new response.

Note that the ResponseInterface and Response can't both be called Response in the same file, so I had to remove that alias, and just call the ResponseInterface by its true name. You could give it a different alias, but I think that would only create more confusion.

Also, I commented out the echo before the redirect. I think this echo will force headers to be sent automatically, which will break the redirect. Unless Slim 4 is doing output buffering, in which case you're still not going to see it, because the redirect will immediately send you to a different page. Anyway, I commented it out to give the code the best chance of working but left it in place for reference.

Anyway, I think if you make that little change, everything will work. Of course, this post is almost a year old, so you've probably solved this on your own, switched to F3, or abandoned the project by now. But hopefully, this will be helpful to someone else. That's the whole point of StackOverflow, right?

Upvotes: 7

Related Questions