According to this answer I need to override the default behaviour for my api calls and prevent the redirections regardless the header using a middleware:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class ApiMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return Response
*/
public function handle(Request $request, Closure $next): Response
{
Log::info(__FUNCTION__);
$response = $next($request);
if ($response->getStatusCode() === 302 || $response->getStatusCode() === 301) {
return new JsonResponse(['msg'=>"Unauthorized"], 401);
}
return $response;
}
}
And upon routes/api.php
using the following aproach:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Middleware\ApiMiddleware;
Route::middleware([ApiMiddleware::class])->group(function (){
// Generating the token
Route::put('/token',[\App\Http\Controllers\API\SaasUserController::class,'login'])
->name('api.login');
Route::middleware('auth:sanctum')->group(function (){
Route::get('/user', function (Request $request) {
return $request->user();
});
// Misc routes that need authentication
});
});
But by doing a plain GET to /api/user
I get redirection according to insomnia log:
> GET /api/user HTTP/1.1
> Host: 172.161.5.2
> User-Agent: insomnia/2022.6.0
> Cookie: my_session=eyJpdiI6IjBxWVJkMlJobTREYVFuMk9wZ2xnV0E9PSIsInZhbHVlIjoiRmlRbnlwSlhBbE55a0FJS21hWE9reCs0Q1FPOW03bU1vcy9zOFhrZG1QTHlDYmgwV1VqUVgyNFJpUXE3cmxrOWI4d2lFcktDVFMydmxjZ0VmcFF3WGQvOWYvM0V4dXhQb2xya1ppNEVBNnZDMXBQNUJsaDJmV3NBMTdjdmpYREEiLCJtYWMiOiI2MGQ4MDA1ZTU1MTczYjM4NDdkNjFjOTE1ZDg3ZGU4YTUwOTlkOGRhY2EyZTJlZDU2ZTFhMWVlMWIyMjAyYWZiIiwidGFnIjoiIn0%3D; XSRF-TOKEN=xxxx
> Accept: */*
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Server: nginx
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/8.2.17
< Cache-Control: no-cache, private
< Date: Thu, 28 Mar 2024 10:29:17 GMT
< Location: https://172.161.5.2/login
< Access-Control-Allow-Origin: *
* Ignoring the response-body
* Received 358 B chunk
* Connection #6 to host 172.161.5.2 left intact
* Issue another request to this URL: 'https://172.161.5.2/login'
* Found bundle for host 172.161.5.2: 0x3f5c038f58c0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#6) with host 172.161.5.2
* Connected to 172.161.5.2 (172.161.5.2) port 443 (#6)
> GET /login HTTP/1.1
> Host: 172.161.5.2
> User-Agent: insomnia/2022.6.0
> Cookie: my_session=eyJpdiI6IjBxWVJkMlJobTREYVFuMk9wZ2xnV0E9PSIsInZhbHVlIjoiRmlRbnlwSlhBbE55a0FJS21hWE9reCs0Q1FPOW03bU1vcy9zOFhrZG1QTHlDYmgwV1VqUVgyNFJpUXE3cmxrOWI4d2lFcktDVFMydmxjZ0VmcFF3WGQvOWYvM0V4dXhQb2xya1ppNEVBNnZDMXBQNUJsaDJmV3NBMTdjdmpYREEiLCJtYWMiOiI2MGQ4MDA1ZTU1MTczYjM4NDdkNjFjOTE1ZDg3ZGU4YTUwOTlkOGRhY2EyZTJlZDU2ZTFhMWVlMWIyMjAyYWZiIiwidGFnIjoiIn0%3D; XSRF-TOKEN=XXXXXX
> Accept: */*
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/8.2.17
< Cache-Control: no-cache, private
< Date: Thu, 28 Mar 2024 10:29:17 GMT
* Replaced cookie XSRF-TOKEN="XXXXXX" for domain 172.161.5.2, path /, expire 1711628957
< Set-Cookie: XSRF-TOKEN=XXXXXX; expires=Thu, 28 Mar 2024 12:29:17 GMT; Max-Age=7200; path=/; secure; samesite=lax
* Replaced cookie my_session="eyJpdiI6InBXcHQrVnc1QVVVbVBpckd1eE5VS0E9PSIsInZhbHVlIjoidEF5RGlHRTIvVFdGNnBodVdMWC9UYWYydUVzanJFOTRBcjN3WTZuYUgvSHpFempNWUZiZjVHSGJCandHdUZFakNxRFpwbGo1WGxYVWpnSjA3VlF1ZnZKZENtUWFJUENwMW9EMyt6UmpidjZWVzZTdkIrekk4d24xK0R4Wi9IeloiLCJtYWMiOiI3MzE3YmU3OGY1MTc0NTJmYjVlZTZkMDNiOWE1YTkwNGFiNGMyNmNiOWUwMThmNTFkNDg3ZWVkNGMyNGIyOGNmIiwidGFnIjoiIn0%3D" for domain 172.161.5.2, path /, expire 1711628957
< Set-Cookie: my_session=eyJpdiI6InBXcHQrVnc1QVVVbVBpckd1eE5VS0E9PSIsInZhbHVlIjoidEF5RGlHRTIvVFdGNnBodVdMWC9UYWYydUVzanJFOTRBcjN3WTZuYUgvSHpFempNWUZiZjVHSGJCandHdUZFakNxRFpwbGo1WGxYVWpnSjA3VlF1ZnZKZENtUWFJUENwMW9EMyt6UmpidjZWVzZTdkIrekk4d24xK0R4Wi9IeloiLCJtYWMiOiI3MzE3YmU3OGY1MTc0NTJmYjVlZTZkMDNiOWE1YTkwNGFiNGMyNmNiOWUwMThmNTFkNDg3ZWVkNGMyNGIyOGNmIiwidGFnIjoiIn0%3D; expires=Thu, 28 Mar 2024 12:29:17 GMT; Max-Age=7200; path=/; httponly; samesite=lax
Is there a way to override this without needing to provide accept header?
In order to achieve this you need to apply the middleware globally at bootstrap/app.php
like this:
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->api(prepend: [
\App\Http\Middleware\ApiMiddleware::class
]);
// Rest of middleware bootstrapping goes here
})
->withExceptions(function (Exceptions $exceptions) {
// Code ommited here if any
})->create();
Then you can modify the middleware using these options as api stategies:
Keep the middleware as you have
Send a 401 response in a format that the server accepts as well:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class ApiMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return Response
*/
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
if ($response->getStatusCode() === 302 || $response->getStatusCode() === 301) {
$accept=$request->header('Accept')??"";
$accept=trim(preg_replace(";.*","",$accept));
if(!empty($accept) && $accept!='application/json'){
$content="Unautorized";
switch ($accept){
case "application/html":
case "text/html":
$content="<!DOCTYPE html><html><head><tilte>Unautorized</tilte></head><body>Unautorized</body></html>";
break;
case 'application/xml':
case 'text/xml':
$content="<xml><msg>Unautorized</msg></xml>";
break;
default:
$accept="text/plain";
}
return new Response($content,401,['Content-Type',$accept]);
}
return new JsonResponse(['msg'=>"Unauthorized"], 401);
}
return $response;
}
}
application/json
class ApiMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return Response
*/
public function handle(Request $request, Closure $next): Response
{
if(!$request->wantsJson()){
new Response("",400);
}
$response = $next($request);
return $response;
}
}
As mentioned fill the necessary header as shown in https://laraveldaily.com/tip/force-json-response-for-api-requests
That enforces that we only api accepts only Json.
class ApiMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @return Response
*/
public function handle(Request $request, Closure $next): Response
{
if(!$request->wantsJson()){
new JsonResponse(['msg'=>"Invalid provided Accept Type"],400);
}
$request->headers->set('Accept', 'application/json');
$response = $next($request);
return $response;
}
}