I have invoked a class through $app->post() from routes.php
And the script runs well without errors
public function __invoke(Request $request, Response $response): Response {
// your code
// to access items in the container... $this->container->get('');
return $response->withHeader('Location', '/')
->withStatus(201);
}
And i see on Console that the Location and Status is actually correctly set, but the browser wont redirect me. Why is this?
The Location response header indicates the URL to redirect a page to. It only provides a meaning when served with a 3xx (redirection) or 201 (created) status response.
This is from Mozilla-dev, do i misunderstand this one? Shouldnt it redirect if i send a 201 created? (Location - HTTP | MDN)
If you return a location on a 201, thatâs telling the client where they can find the resource. It doesnât redirect. The only way to redirect is to use a 3xx status. Theyâre literally defined by the W3C as:
Exact same struggle here. 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:
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);
}
}
}
Very good point. Unfortunately, it still fails to redirect. It falls thru to the originally called route, completely ignoring the redirect header. If I initiate a new Response, as suggested by FvsJson, it hangs on a white screen.
Okay, I figured it out. Basically, Iâm an idiot. My code was stuck in a perpetual redirect loop. I had to use the $_SERVER[âREQUEST_URIâ] variable to break out of the loop, like so:
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 way to accomplish this, or is the $_SERVER method the best way?
Ugh, I spoke too soon. Basically, I got so frustrated trying to make Slim 4âs redirect work in a middleware scenario that I went dabbling in FatFreeFramework, and found I had the same problem. That clued me in that maybe it was something I was doing that was the culprit. Long story short, in my FFF testing, I was definitely putting my test app in an infinite redirect loop, so I assumed it was the same issue here. But sadly it is not.
In my Slim 4 app, my âloginâ method is in a completely different class, and it obviously is NOT behind authentication. And sadly, the redirect still falls thru to the original route. Anybody have an idea on this? Has anybody else made redirect work in middleware, and if so, can you post a snippet showing how?
Make sure that The AuthMiddleware will be only invoked for routes that needs the AuthMiddleware. In Slim, you could create a special Route group for all âprotectedâ routes and create another route group for all login/logout-related routes, but without the AuthMiddleware.
If you try this concept, the AuthMiddleware only have to check for the logged in user. I would also call the handle method only for valid users. Here is an example.
Routes
use Slim\App;
use Slim\Routing\RouteCollectorProxy;
return static function (App $app) {
// Routes without authentication check
$app->group('/users', function (RouteCollectorProxy $group) {
$group->post('/login', \App\Action\UserLoginSubmitAction::class);
$group->get('/login', \App\Action\UserLoginIndexAction::class)->setName('login');
$group->get('/logout', \App\Action\UserLogoutAction::class);
})->add(SessionMiddleware::class);
// Routes with authentication
$app->group('', static function (RouteCollectorProxy $group): void {
// Default page
$group->get('/', \App\Action\HomeIndexAction::class)->setName('root');
// add more routes
// ...
})->add(AuthMiddleware::class)
->add(SessionMiddleware::class);
};
The AuthMiddleware
Pseudo example:
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Routing\RouteContext;
/**
* Auth Middleware.
*/
final class AuthMiddleware implements MiddlewareInterface
{
/**
* @var ResponseFactoryInterface
*/
private $responseFactory;
/**
* Constructor.
*
* @param ResponseFactoryInterface $responseFactory The response factory
*/
public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->responseFactory = $responseFactory;
}
/**
* Invoke middleware.
*
* @param ServerRequestInterface $request The request
* @param RequestHandlerInterface $handler The handler
*
* @return ResponseInterface The response
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$isLoggedIn = !empty($_SESSION['user_id']); // check user login / session here
if ($isLoggedIn) {
return $handler->handle($request);
}
// Redirect to login route
$routeParser = RouteContext::fromRequest($request)->getRouteParser();
$url = $routeParser->urlFor('login');
return $this->responseFactory->createResponse()->withHeader('Location', $url)->withStatus(302);
}
}
I think Odan nailed it. Your auth middleware is currently set to global so it hits a loop. you need to only assign the auth middleware to the route that needs to be authenticated.
// Routes with authentication
$app->group('', static function (RouteCollectorProxy $group): void {
// Default page
$group->get('/', \App\Action\HomeIndexAction::class)->setName('root');
// add more routes
// ...
})->add(AuthMiddleware::class)
->add(SessionMiddleware::class);
Yes, in my testing code, it was definitely a redirect loop, but in the app Iâm actually considering upgrading from Slim 3, the login method is not behind authentication, and the redirect still fails.
However, examining Odanâs code, I see heâs used the ResponseFactory to create a fresh response for the redirect, which is something I did not do and will have to try. Life has gotten in the way the past few days, so I havenât had a chance to try it yetâŚ