Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

pr0f3ss0r's avatar

Laravel 11 - Api unauthorized requests redirects to login page

Hi all,

I'm having issue on

withMiddleware method of ApplicationBuilder class adding below code to middleware.

$middleware = (new Middleware) ->redirectGuestsTo(fn () => route('login'));

I tried to customize it in bootstrap/app.php withMiddleware part but i couldnt do it and cant find any resource for it.

Can you please help me how can I return json unauthorized response instead redirection for auth:api middleware? Also I would like to modify route('login') redirection to another route again I couldnt do that also.

0 likes
20 replies
Snapey's avatar

send accept json with your request so that the framework knows a json response is required

3 likes
pr0f3ss0r's avatar

@Snapey Im using different domain for panel (login) and api. And i dont want to show my panel domain to api users. Thats why i would like to handle this instead laravel do it for me.

Also i asked another question; how can i change login route name to another route for guests

pr0f3ss0r's avatar

@Snapey api routes using different domain than normal web and auth routes (set by Route::domain()) if api user doesnt send Accept: application/json header in his request. he is redirected to my login page and my panel domain exposed.

Can you help me how can i manage that in laravel 11?

Snapey's avatar

@pr0f3ss0r isnt your domain exposed in the url of the api call?

Simple answer is, if you don't want to be redirected you must send accept header. You can avoid this if you don't have any web routes and create middleware that forces json response on API routes?

kokoshneta's avatar

@Snapey The API domain is exposed (e.g., api.mydomain.com), but the panel domain isn’t (e.g., dashboard.mydomain.com).

You can also create a middleware that adds the accept header within your application and then apply that middleware to your API group – then the application will always see it as set, even if the client doesn’t specify it. This article shows an example of how to do it.

pr0f3ss0r's avatar

@Snapey I'm struggling to understand why you're having difficulty. If you can't provide an answer to my question, why do you come and ask me questions? My question is quite clear. I'm giving you answers again

isnt your domain exposed in the url of the api call? NO because api domain is DIFFERENT than web routes. i hope you understood 3 times i explained that.

Simple answer is, if you don't want to be redirected you must send accept header. You can avoid this if you don't have any web routes and create middleware that forces json response on API routes? Its not SIMPLE that much. because its redirected before coming to middleware.

kokoshneta's avatar

@pr0f3ss0r The behaviour of redirectGuestsTo() can be changed in your bootstrap file. My guess would be that in this case you simply want to ignore that, since you don’t want to redirect guests at all in an API call.

But the underlying issue here would seem to be that API routes shouldn’t be checking for authentication that way at all. What middleware are you using for auth:api?

pr0f3ss0r's avatar

I fixed the issue by changing middleware append to prepend. now requests coming to middleware before than redirect. Thanks @kokoshneta for your advice

JimNayzium's avatar

@pr0f3ss0r Would you mind explaining in more detail exactly what you did that solved your issue? After reading a lot on this I think my use case, while different, has a need for the similar solution, i.e. Was it as simple as in bootstrap/app.php just changing the append word to prepend? I would like to see what you actually applied to get the api routes to automatically add the accept: application/json as well. THANKS!

puklipo's avatar
puklipo
Best Answer
Level 9

Change response

// bootstrap/app.php
use Illuminate\Http\Request;
use Illuminate\Auth\AuthenticationException;
 
->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (AuthenticationException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => $e->getMessage(),
            ], 401);
        }
    });
})

Change redirect

// bootstrap/app.php
use Illuminate\Http\Request;

    ->withMiddleware(function (Middleware $middleware) {
        $middleware->redirectGuestsTo(function (Request $request) {
            if ($request->is('api/*')) {
                return route('...');
            } else {
                return route('...');
            }
        });
    })
3 likes
JimNayzium's avatar

@puklipo Is the implication in the redirectGuestTo function that if the route is 'api/*' we are able to customize it fully using this method, and therefore not return a route at all, but rather a json response? I am confused why both sides of the logic would return the same thing? Or am I just over coaching the answer and I can then just customize the api side of the logic to be whatever I want? THANKS!

LaryAI's avatar
Level 58

To customize the behavior of unauthorized requests in your Laravel API, you can create a custom middleware that checks if the request expects a JSON response and, if so, returns a JSON response instead of redirecting to the login page.

Here's how you can create and register your custom middleware:

  1. Create a new middleware using the Artisan command:
php artisan make:middleware ApiAuthenticate
  1. Open the newly created middleware file located in app/Http/Middleware/ApiAuthenticate.php and modify the handle method to return a JSON response for unauthorized requests:
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class ApiAuthenticate
{
    public function handle(Request $request, Closure $next)
    {
        if (Auth::guard('api')->check()) {
            return $next($request);
        }

        if ($request->expectsJson()) {
            return response()->json(['message' => 'Unauthorized'], 401);
        }

        // Modify this to redirect to a different route if necessary
        return redirect()->route('your_custom_route');
    }
}
  1. Register your custom middleware in app/Http/Kernel.php. Add it to the $routeMiddleware array:
protected $routeMiddleware = [
    // ...
    'api.auth' => \App\Http\Middleware\ApiAuthenticate::class,
    // ...
];
  1. Now, you can use your custom middleware in your routes. For example, in routes/api.php, you can apply it to a group of routes:
Route::middleware('api.auth')->group(function () {
    // Your protected routes go here
});

This way, any unauthorized requests to your API routes will receive a JSON response with a 401 status code, and you can customize the redirection for non-API routes by changing the redirect()->route('your_custom_route') line in your middleware.

Remember to replace 'your_custom_route' with the actual route name you want to redirect unauthorized non-API requests to.

JimNayzium's avatar

Okay, for anyone searching for this later who is like me and couldn't quite make Laravel 11 make this work, I solved the issue for Laravel 11+Sanctum and the api routes needing to have the Accepts:application/json added automatically.

For me, it was a matter of not wanting to manually have to change hundreds of fetch requests in the finished SPA that is NOW going to use the Laravel 11 API system.

Per the Laravel 11 Middleware Groups documentation, and reading various places including this thread here is what I did.

  1. Created a Middleware file called ApiForceJsonResponse
<?php  
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ApiForceJsonResponse
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next) : Response
    {
        $request->headers->set('Accept', 'application/json');
        return $next($request);
    }
}

That was the easy part, but knowing where to put it in Laravel 11 was the trickier part for me. In the end it was super simple, but took forever to find.

It's in the documentation very clearly, but for some reason I had to read it a dozen times before I realized it was so simple.

So here is my bootstrap/app.php file now with the class referenced in the api group. It's important as noted by @pr0f3ss0r that we use PREPEND in the grouping so it all happens before the route triggers.

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\RestrictIpAddressMiddleware;

use Illuminate\Http\Request;
use Illuminate\Auth\AuthenticationException;


return Application::configure(basePath: dirname(__DIR__))

    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        commands: __DIR__ . '/../routes/console.php',
        health: '/up',

        //. custom v1 routes per laracast master class tutorial
        then: function () {
            // Route::middleware(['api', 'auth:sanctum'])
            Route::middleware(['api'])
                ->prefix('api/v1') 
                ->name('api_v1.')
                ->group(base_path('routes/api_v1.php'));
        },
    )

    ->withMiddleware(function (Middleware $middleware) {
        
		// here is the prepend of the forceJsonResponse class!
        $middleware->api(prepend: [
            \App\Http\Middleware\ApiForceJsonResponse::class,
        ]);

        $middleware->redirectGuestsTo(function (Request $request) {
            if ($request->is('api/*')) {
                return response()->json([
                    'ok' => false,
                    'message' => "In withMiddleware: Custom Unauthenticated message because no accept header!",
                    'msg' => "In withMiddleware: Custom Unauthenticated message because no accept header!",
                ], 401);
            } else {
                return route('/');
            }
        });
    })


// helps with the error messages and redirect to /login with unauthenticated api requests.
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->render(function (AuthenticationException $e, Request $request) {
            if ($request->is('api/*')) {
                return response()->json([
                    'ok' => false,
                    'msg' => $e->getMessage(),
                    'message' => $e->getMessage(),
                ], 401);
            }
        });
    })->create();

Hope this helps someone out who spent hours trying to figure this out!! I am new to Laravel and did a crash course recently on version 8, then to write my API installed version 11 figuring "it can't be that different, right?" And it's not, except where it is!!!

JimNayzium's avatar

A slight improvment on the handle function in order to account for possibility that an accept header is already present and not to alter it other than to add the accept application/json if not present.

    public function handle(Request $request, Closure $next) : Response
    {
        // Retrieve the current 'Accept' header
        $acceptHeader = $request->header('Accept');

        // Check if 'application/json' is already included
        if (strpos($acceptHeader, 'application/json') === false) {
            if (!empty($acceptHeader)) {
                // If other values exist, append 'application/json'
                $request->headers->set('Accept', $acceptHeader . ', application/json');
            } else {
                // If no 'Accept' header exists, set it to 'application/json'
                $request->headers->set('Accept', 'application/json');
            }
        }
        
        return $next($request);
    }
nfonandrew's avatar

I was wondering if there is a configuration in Laravel 11 to handle authentication exceptions gracefully (particularly for api), rather than attempting to redirect to route('login'), which results in a RouteNotFoundException. I’ve noticed in some blog posts that a RouteNotFoundException is returned instead of an Unauthorized or Unauthenticated 401 error.

I tried implementing a workaround, but it doesn’t seem ideal. I used the following code to inform the frontend that the error was related to authentication:

if (str_contains($e->getMessage(), 'login')) {
    return response()->json(
        [
            'success' => false,
            'message' => 'Unauthorized'
        ],
        401
    );
}

Here’s how my exception handling is configured:

Khoxle's avatar

This thread helped me so I figured I should post my solution for people that ahve the same needs as I do. Basically, I use laravel 11 for API only, so I totally removed web.php and resources folder. I only return responses in JSON and handled HTTP exceptions in bootstrap/app.php.

Here is my code: I setup the redirectGuestsTo middleware to avoid redirecting to login route.

->withMiddleware(function (Middleware $middleware) {
        $middleware->redirectGuestsTo(function (Request $request) {
            return response()->json(['status' => 'error', 'hint' => 'not_authenticated'], 403);
        });
    })

shouldRenderJsonWhen always return true, so that API always return JSON no matter the Accept set in header of the request. I redefine how each exception is handled because I want a custom layout/message in it, with debug data handled.

If you think this can be cleaner, please let me know.

martinbean's avatar

If you think this can be cleaner, please let me know.

@Khoxle Sure. If you just want JSON responses for your API, then just do that, instead of all that code you have.

$exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
    return $request->expectsJson() || $request->is('api/*');
});

Please or to participate in this conversation.