After we released API Generator with Auth provided by Laravel Passport, some of our customers were asking how to remove it, for some public API endpoints, which don’t require Auth. Here’s the article for you.

Warning: be very careful with this approach. This article will show you how to make API public, which means ANYONE can access it from anywhere. Please make sure you really want to do that.


By default, here’s our routes/api.php:

Route::group([
    'prefix' => 'v1', 
    'as' => 'api.', 
    'namespace' => 'Api\V1\Admin', 
    'middleware' => ['auth:api']
], function () {
    Route::apiResource('permissions', 'PermissionsApiController');
    Route::apiResource('roles', 'RolesApiController');
    Route::apiResource('users', 'UsersApiController');

    // ... more API endpoints
});

The most important part here is auth:api middleware, which is defined in config/auth.php as API driver ‘passport’.

And if you try to GET /api/v1/users without any authentication, you will get error “Key path …/storage/oauth-public.key does not exist or is not readable”:

If you want to remove that Auth from some of your routes, just create a separate Route group which doesn’t have that middleware. Let’s say we want to make Users list public. The routes/api.php from above could look like this:

Route::group([
    'prefix' => 'v1',
    'as' => 'api.',
    'namespace' => 'Api\V1\Admin',
], function () {

    // These routes will still be secured by Laravel Passport
    Route::group([
        'middleware' => ['auth:api']
    ], function() {
        Route::apiResource('permissions', 'PermissionsApiController');
        Route::apiResource('roles', 'RolesApiController');

        // ... Other secured API endpoints
    });

    // This route will be public
    Route::apiResource('users', 'UsersApiController');

    // ... Other public API endpoints
});

We create Route group within a group. So all API endpoints will have same prefix and namespace, but only one sub-group will have auth:api middleware.

So, we’ve made our /api/v1/users accessible without a login. But we will still get a different error:

“403 Forbidden”

Laravel API 403 Forbidden

And that’s because our request is authenticated but not authorized for that particular method.


Authentication (401) and Authorization (403)

There’s a difference:

  • Authentication means whether someone is logged in or not for the whole system (in case of failure, it should return HTTP Status code 401 Unauthorized);
  • Authorization means whether logged in user has permissions for specific area of the system (in case of failure, it should return HTTP Status code 403 Forbidden).

Now, 403 Forbidden happens because in our Controllers we check for permissions, and we expect user to be logged in. For example, here’s app/Http/Controllers/Api/V1/Admin/UsersApiController:

class UsersApiController extends Controller
{
    public function index()
    {
        abort_if(Gate::denies('user_access'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        return new UserResource(User::with(['roles'])->get());
    }

    // ... other methods

See the abort_if() statement? Here’s where we check the permissions.
Now, if you want to skip that check, just delete that line in the Controller.

And then… you will successfully get your users list, without any authentication or authorization. In public:

Laravel API 200 Status


Restricting POST/PUT Methods

So far, I’ve shown you how to make GET request public. But for the POST or PUT requests we generate a little different code. We put the Auth check inside of FormRequests classes instead of the Controller.

So, if you want to create a new User, here’s your method in UsersApiController.php:

public function store(StoreUserRequest $request)
{
    $user = User::create($request->all());
    $user->roles()->sync($request->input('roles', []));

    return (new UserResource($user))
        ->response()
        ->setStatusCode(Response::HTTP_CREATED);
}

Seems like there’s no check for permission, right? But it’s actually inside of that app/Http/Requests/StoreUserRequest.php:

class StoreUserRequest extends FormRequest
{
    public function authorize()
    {
        abort_if(Gate::denies('user_create'), Response::HTTP_FORBIDDEN, '403 Forbidden');

        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required',
            // ... other validation rules
        ];
    }
}

So method authorize() returns true or false, depending on the permissions of a user.
Read more about Laravel Form Requests in the official documentation.

If you wanna make that POST public (I can’t imagine why, but still), just remove that abort_if() from here.

Notice: this FormRequest class is re-used for both web Controller and API Controller, so if you want to change something, it will be affected in both cases.

So, that’s it. That’s how you can remove auth:api and Laravel Gates to make your API endpoints public. But, again, be careful with that!