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

jdillon's avatar

Possible to register a policy without a model?

I'm working on a project that leverages policies for authorization, and this works well when I'm authorizing an entity that has a corresponding model. However, not every entity I want to authorize has a corresponding model.

Is there a way to register a policy without a model (or is that fundamentally incorrect?), or should I take a different approach?

0 likes
12 replies
martinbean's avatar

@jdillon Policies are meant to dictate access to models. How can you have a policy without a corresponding model? What is a policy for if not a model?

jdillon's avatar

@keizah Thank you for the reply! I understand that it's possible to call an authorization method without passing in a model.

What I'm actually asking is if it's possible to register a Policy in the $policies array in AuthServiceProvider somehow without registering a model along with it. After having another look at the documentation, I think I need to use A Gate

1 like
jdillon's avatar

@martinbean Being relatively new to Laravel, my initial understanding was that policies were for Authorization in general, but my understanding is incomplete. As I mentioned in another reply, it seems like I need to leverage Gatesto meet my need. Thoughts on this approach?

1 like
martinbean's avatar

@jdillon You’ve not actually said what it is you’re trying to do, so can’t really advise.

If you explain what it is you’re trying to achieve, I’d be able to point you in the right direction.

jdillon's avatar

@martinbean Ultimately I want to grant authorization to entities within an application. Some entities have corresponding models, but there are also certain entities, more like actions, I guess, that I want to authorize, and in that case there isn't a model I can register with a policy.

My initial question was if it was possible to register a policy in the $policies array somehow without a model associated with it (or even if it made sense to do so), so that along with entities that have models, I could also authorize certain application actions.

Your response above essentially answered my question, and prompted me to re-read the authorization docs where I saw this approach: Gate::define('update-post', 'App\Policies\PostPolicy@update');. I think this meets my need as I can define various Gates in AuthServiceProvider that call the various methods of a Policy class and then call $this->authorize in controller methods. So in essence, I am able to use a Policy class, without necessarily providing a model.

Again, I'm new to laravel, so any thoughts on this approach are welcome!

martinbean's avatar

@jdillon I still don’t really follow if I’m honest. Policies dictate if an entity can perform an action. They’re not used to determine if an “action” can perform… an action? As you see, that makes no sense.

Think of policies like this: “can X do Y?” If you let us know what “X” is in your application, then we can probably point you in the direction of the most appropriate authorisation strategy.

jdillon's avatar

@martinbean I apologize if I'm not being clear here, and I appreciate your time. X would be a user, but Y could be a CRUD operation, or something else that requires business logic to determine if a user has authorization to perform a certain action.

1 like
jlrdw's avatar

Use a gate or work out the logic:

pseudocode

public function makeInvoice()
    {
        if (a required role of bkeep is not true here) {   // bkeep = bookkeeper
            return redirect('somewhere); // whereever you redirect to if not authorized
        }
        // Rest of method here is accomplished if 
        // the logged in used has the required role of 'bkeep'.
    }

A spatie example I saw a while back:

public function update(Request $request, Post $post) {
    if ($post->author !== auth()->user()->id || auth()->user()->cannot('edit posts'))
        abort(404);// or redirect, or whatever action 
    }
    //rest of method if all okay
}

But you still also have to have code to make sure someone does not change something in the url. Meaning the Auth::user()->id matches an FK.

And you need to use a scope to ensure user is seeing only their data and admin can see all or more.

RBAC involves several steps.

1 like
J5Dev's avatar
J5Dev
Best Answer
Level 11

Stumbled upon this looking for something else, and wanted to add some views and suggestions, which may help the OP @jdillon and any others coming her in the future.

I think what @martinbean was trying to articulate , is that the Policy class itself (Laravels implementation) is designed specifically to be tied to a Model class, but that doesn't mean you cannot still use the authentication system (Gates) within Laravel if you want something without a model.

To provide some context we have a huge number of both of these approaches within our applications, as both have a part to play... take the following examples;

Policies

  • Verifying a user can Create, Update, Delete etc a blog post
  • Verifying that a user can comment on a blog post

Simple example, but these could be solved using the Policy approach, tied to a model, and having functions related to the desired action. Then inside the methods use a combination of checking the users permissions, they created the post, and even system settings such as comments are enabled for posts (individually or at all)... you get the idea.

This is what the core examples pertain to, and is fine if the logic is predominantly tied to a model and its associated Crud activities.

Pretty much every object in our system has policy, with various methods associated with each, predominantly CRUD related, but not always, though usually always related to the changing of the state of the model.

Non-Model Policies (Basic Gates)

Take the following examples;

  • Verify a user can clear the system caches from within and admin panel
  • Verify a user has the ability to put the system into a form of maintenance mode
  • Verify a user can force log other users out, or force refresh tokens

These are more aligned to what I believe @jdillon was referring to, and obviously they do not relate to a model, and it would be wrong to 'shoehorn' them into a Policy class bound to a model. So where do action oriented checks like this go... Gates, just plain old gates.

While it has already been mentioned that Gates can be used for this, and are ideal for doing so, I wanted to clarify some confusion with regards to the Class approach to using a gate... It doesn't have to be a Policy, it can be any old class and method, essentially the second parameter to define on the Gate facade just wants a callable that it can feed a user... either inline or as a reference.

Take this example from one of our apps:

Gate::define('system.admin.cache.clear', 'App\Modules\Auth\SystemAdminAbilities@clearCache');

The above is one of our defined 'abilities', which we manage using the Gate system, but it in no way uses the model policies. It is literally a class with callable methods, within which we check a whole bunch of things depending on what we want to check the user can do.

Summary

As always, there is always more than one way to do something, some suit some situations better than others, and some are preferable for other reasons. The key aspect is to not get hung up on something being the 'correct way', and instead reach for the way which suits your needs the most without being afraid to mix and match.

16 likes
jdillon's avatar

I know it's been a while, but this is basically what I was looking for at the time, thank you for taking the time to explain this @j5dev

2 likes
loureirorg's avatar

In addition to J5Dev answer, if you want to keep your rules as a Policy, you can do this:

// App\Providers\AuthServiceProvider.php
public function boot()
{
    $this->registerPolicies();

    Gate::define('clearCache', [CachePolicy::class, 'clearCache']);
}

In this example, CachePolicy has no model.

Then, call it in your controller:

public function clearCache() {
  if (! Gate::allows('clearCache')) {
    abort(403, "Not allowed");
  }
  /* ... */
}

I like this structure because it keeps all your authorization rules in one place: the App/Policies folder.

2 likes

Please or to participate in this conversation.