Skip to content
Home » Coding » Laravel » Laravel Passport, allow specific routes to guests only

Laravel Passport, allow specific routes to guests only

    Securing your Laravel endpoints using Passport to make sure only authenticated users can access them is easy. In fact, Laravel provides an existing Middleware to do so, and it is explained in the official documentation.

    What is not as easy, and is more obscure, is how to make sure only un-authenticated users (or, “guests”) can access an endpoint. Actually, even a Google search doesn’t yield great results in that regard, even thought it feels like almost every website would need it at some point.

    But why would you need it?

    The answer might seem obvious to some, but people with less experience might think something along the lines of “Sure, I don’t want logged-in users to access the signup page, but my frontend can handle that!”.

    Never trust user input.

    Usually we see this rule when talking about forms and actual “inputs”, but in the end, making a request to an API is also a user input, and nothing guarantees you that an authenticated user won’t be able to call a page reserved to guests.

    This is true for every website, but on top of that you could have other reasons to do so, like exposing a public API, delegating the authorization layer entirely to your backend, etc.

    A solution

    First, I said it’s not as easy as doing the contrary, but if you really want there is the very simple solution of validating that the user isn’t authenticated directly in your controller method:

    use Illuminate\Support\Facades\Auth;
    
    if (!Auth::check()) {
        // The user is a guest
    }
    

    It works, but for multiple reasons it is a bad solution:

    1. Your controller is not specific to authenticated users / guests, and you’ll likely have an if / else which makes it less readable.
    2. It’s easier to make mistakes and forget to include the condition in one of your controllers, potentially creating a security issue.
    3. Authentication is checked after authorization which might go as far as throwing a 500 (if your authorization layer checks for user properties for example), polluting your logs and getting a negative user experience.

    The solution!

    Laravel provides a system called Middlewares to handle authentication and authorization, they basically intercept the request made by the user and sit before the request reaches your controller.

    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Support\Facades\Auth;
    
    class DenyIfAuthenticated
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @param  string|null  $guard
         * @return mixed
         */
        public function handle($request, Closure $next, $guard = null)
        {
            if (Auth::guard($guard)->check()) {
                abort(401);
            }
    
            return $next($request);
        }
    }
    

    This file should sit in your app/Http/Middleware folder.

    The code itself is very simple, we just get the user and check() if he is authenticated, in which case we abort the request with a HTTP 401 Unauthorized error code. Otherwise, we just pass the request to the next Middleware (or to the controller if it’s the last one).

    Only two steps remain, one of which is to register this Middleware in the application. This is simply done by adding your middleware to the $routeMiddleware variable in the App/Http/Kernel.php file, and giving it the identifier that you want:

    protected $routeMiddleware = [
        ...
        ‘auth.deny’ => \App\Http\Middleware\DenyIfAuthenticated::class
    ];
    

    This allows you to proceed to the next step, by informing Laravel that “auth.deny” is an existing Middleware.

    This last step is to head to your routes file and use the middleware like you would with any other, just like the “auth” middleware.

    Route::group([‘middleware’ => [‘auth.deny:api’]], function () {
        Route::resource(‘users’, ‘UserController’, [‘only’ => [‘create’, ‘store’]]);
    });
    

    This will secure the create and store methods for your UserController, and throw a 401 error in case the user is already authenticated!