Questions about Authentication and Authorization plugin usage / best practices


#1

I’m in the process of porting all my authentication and authorization code to the new middleware-based plugins. It’s a pretty sizable application, with lots of complexity in terms of who is allowed to do what. So far, I’m very much liking what it’s doing for the code structure, eliminating a lot of duplication, which should make things more maintainable for the long term. (Especially if/when new roles are introduced, changes should be restricted to the policies.)

I have, however, run into a few scenarios where I’m not sure what the best solution would be. I’ve posed a couple of question on the Slack channel, but they tend to disappear quickly in the flow of discussion there. I think they’re fairly opinion-based, so not appropriate for Stack Overflow. And while I think there may be enhancements to be made to the plugins, I’m not convinced that what I’m running into are truly “issues” to be posted on Github. So, here I am.

The first area where I’m struggling is with hierarchical data, where some people have access to edit lower levels but not higher levels, but where the higher levels may control aspects of what’s available. Say that A hasMany B, and X has authority over a particular B, but not the A that it belongsTo. In order to perform operation O, I need to authorize that against resource B, but it also needs to check whether that feature is entirely disabled for A. Options as I see them:

  1. Call $this->Authorize->authorize for both A and B. Seems like unnecessary duplication, plus I have to remember to do it everywhere that I’m checking permission for that operation. Having to remember to add code like this is a sure source of bugs.
  2. Call $this->Authorize->authorize only for B, and have it check A. But that means that either I have to have A as a property of B (sometimes this makes sense, but sometimes I’ve loaded all the Bs into A and am foreaching over them) or else have the policy load the A record (which would be extra queries when it’s probably already been loaded somewhere).
  3. Stop using policies to check whether features are enabled. But that means that I have to duplicate the feature check everywhere that I check authorization, which isn’t DRY. Admittedly, checking whether a feature is enabled has nothing to do with who is logged in, so an argument can be made against this technique, but then again the plugins have split authentication apart from authorization, and I feel like disabling features is a way to block anyone from being authorized to perform those actions.

Leaning towards option 2 here, unless people have good reasons not to, or suggestions for other ways to go about it.

Second thing is that I’ve run into a few situations where it seems very useful to be able to pass context data into an authorization check. Some of these I’ve thought more carefully about and realized that passing context would be a lazy solution, while a perfectly good no-context solution also exists. But in other cases I can’t for the life of me figure out how to handle a particular authorization without context. For example, when links are emailed to people with hash codes embedded in them, permitting the recipient to take that one action without logging in. In order to authorize that action, the policy needs the resource and either an identity or a code. I could set the code as a property on the resource, but that seems very hackish. Having an optional context array that could be passed into the authorize function would solve this. (And could be used to provide the A data in my option 2 above.)

So the question here is whether or not adding an optional context parameter to the API would be considered a good thing, or if it was already considered and rejected for some reason.

Last thing (at least for now…) is that I’ve worked up a simple “AuthorizationHelper” that my views can use, to do things like if ($this->Authorize->can('edit', $record)) { // echo edit link }. Is this something that others would find useful? If so, I could submit it to the plugin for the community to benefit from.

I know that’s a lot to digest. Thanks for making it to the end. I’d give you a leftover Halloween candy if you were here. :slight_smile:


#2

Just remembered another thing that I’m not clear on. The old Auth component had configuration for setting what flash message would be shown in case of authentication failures. It’s not at all clear to me how this would work in the new system. For now, I have made my own “unauthorized handler” that does this, but I’m at a bit of a loss about how to handle “unauthenticated” errors.

Is this all something that should be added into the existing plugins, or intended to be done externally?


#3

Leaning towards option 2 here, unless people have good reasons not to, or suggestions for other ways to go about it.

That is how I would go about it too. Checking authorization for B and as part of that process check the parent item as well. You could also check your feature flags in your policies as well. Either using inheritance or a custom policy resolver that injects the required feature checker in would let you check features in your policies.

For example, when links are emailed to people with hash codes embedded in them, permitting the recipient to take that one action without logging in. In order to authorize that action, the policy needs the resource and either an identity or a code. I could set the code as a property on the resource, but that seems very hackish. Having an optional context array that could be passed into the authorize function would solve this. (And could be used to provide the A data in my option 2 above.)

I personally would either create a new ‘resource’ for that operation or add the additional context onto the current resource you have.


#4

Thanks for the feedback! Seems I’m generally on the right track.

I’m going to think about building some sort of contextual resolver, which could take an array of objects, locating the policy based on the first one. Is that the sort of thing you meant by creating a new “resource”?

Any thoughts on the general usefulness of my helper, or how to set a flash message for unauthenticated errors? Maybe the latter is a tiny bit of middleware that catches such exceptions, adds the flash message, and re-throws? Seems to me that it’s likely to be a common enough requirement to build support directly into the current component. Happy to do a PR if that’s something that would be welcomed, but don’t want to put in the time if there are reasons to keep it separate.


#5

You can build a DTO object, inject your A, B and some context into it and authorize the DTO.

$dto = ABAuthorization($a, $b, ['foo' => 'bar']);
$this->Authorization->authorize($dto);

This would of course require you to map policies manually or create a custom resolver. I’d use that approach only for most complex scenarios.


#6

I’ve built a generic context object, which takes the main resource and a context array, and updated my custom resolver code to deal with this. So far, so good…