Repository navigation
[9.x] Allow authorization responses to specify HTTP status codes - #43097
Conversation
7b2fe96 to
8dba56d
Compare
8dba56d to
a8a75cd
Compare
|
Note to self:
|
|
Would it be possible to disable this feature in development mode (maybe the same way we did with |
|
@timacdonald This is an awesome addition! 😍 A couple of notes:
Is it a business decision to not support a default per policy? It seems to me that supporting a per policy default would be easier than a global default. The policy is inheriting a trait, so you can define a parameter on the trait and have the trait use it when building the Response. The
Maybe it would be better for the debug mode 404 screen to expand on the 404 context. |
|
@valorin I've just updated the original description with reasoning around why I removed the default status from the PR. See: "Not supporting default status per policy". @fgaroby @valorin I'm still thinking on if I think the framework should offer a switch for debug mode on this. You already have the ability to handle this in your own app of course. // in the exception handler...
protected function prepareException(Throwable $e)
{
if (config('app.debug') && $e instanceof AuthorizationException) {
return parent::prepareException($e->withStatus(null));
}
return parent::prepareException($e);
}Gonna ponder on this today a bit more. Happy to hear any more feedback though! |
|
This seems like a no-brainer to me. Great work! 👍🏻 |
|
Thanks for all the feedback on this folks. If anyone has ideas on how this could be improved now the base functionality it in place, please feel free to circle back and suggest further additions. I think a default can still be achieved as well as a debug thing. |
Ah, very good points as to why. Thanks. 🙂 I think this is an awesome addition, nice work. 👍 |
|
Thanks for reviewing it. When it comes to security related things, you are my go to community member, so appreciate you taking the time! |
|
I'm always happy to review anything security related. 😁 |
|
@timacdonald can you send in docs for this? |
|
@timacdonald really helpful! One thing I have picked up on that I'd appreciate your thoughts on... the AuthorizationException defaults the message to |
|
@timacdonald should the instead of |
|
Good catch @johanmolen. Updated for future reference. Thanks! |
|
Adding the ability to set a global deny status: #43902 |
return match (true) {
$e instanceof AuthorizationException && ! $e->hasStatus() => new NotFoundHttpException($e->getMessage(), $e),
default => parent::prepareException($e)
};i would not use |
…avel#43097) * allow authorize response to specify http status * code style * add 404 helpers
This PR introduces the ability to set the status code for a denied authorization action in policies, gates, and wherever
Auth\Access\ResponseorAuthorizationExceptionare handled by the framework.Let's take the following example policy where only the authenticated user can view themselves...
Given the above scenario, any user is now able to determine how many users the system currently has if the system is using auto-incrementing IDS...
This PR drastically improves (but does not totally solve**) this scenario by allowing developers to specify the HTTP response code that should be returned when a "forbidden" action is attempted.
This is what I would consider a security "best practice" in many, but not all, scenarios. You can see it all across the web. GitHub, for example, will show a
404for repositories that exist or not when you are not allowed to access them, for example...https://github.com/timacdonald/config-differ
The above repository exists...
but because it is private, everyone else except for myself will see a
404response, thus keeping its existence secret.As you can see from my initial example and then this GitHub example, this feature is not only useful for incrementing IDs, but generally for hiding the existence of any resource**.
Full API
Using
Illuminate\Auth\Access\HandlesAuthorizatione.g. Policies...Returning
Illuminate\Auth\Access\Responsee.g. Gates...Directly throwing the exception....
The exception is translated in the exception handler to send the appropriate HTTP status response.
Why not just use
abort()I'm glad you asked! There is an argument to just utilise the
aborthelper here...However this has a few downfalls.
AuthorizationExceptionbubble up to the exception handler allows you to categorize and appropriately handle that type of exception as needed by your application.This allows developers to handle the exception type while logging an exception message, exception code, and translating to an appropriate HTTP status code for security reasons.
Form Requests
Another place to perform authorization is within Form Requests. You can set a response status in the form request by using the new helpers on the exception.
Not supporting default status per policy
In my initial implementation, I supported a default deny status per policy. This status was then shared to all the deny responses on the policy. This worked by having the policy implement an "implicit property contract" e.g.
This felt really nice and I felt smart 🤓 The implementation for this was housed on the
HandlesAuthorizationtrait. In my opinion, this starts to break down. Take the following policy...Should these all get the default 404 response status? I feel like developers might feel like they should and I was worried it might create some confusion. As it stands, only the
createwould get the404, as the implementation was handled in theHandlesAuthorizationtrait.I could look at pushing this functionality up into the Gate, where it could apply the default status if it is denied with
false,Response::deny(), or$this->deny(), but then I'm wondering if it would have to be an actual interface as I believe our implicit property contracts are usually internal and thusprotected. We could make it that the property needs to bepublic, but that feels flakey and like it could also cause issues as people might declare it as protected.So then you'd need to implement an interface to support the default functionality...and that feels like more work than just being explicit in the policy.
Converting to 404 globally and just handling other cases
It might be the case that developers actually want all authorization exceptions to be rendered as 404, and instead be explicit about what denied actions should show a
403instead. This would be achievable by doing the following in the exception handler...This now converts all
AuthorizationExceptionthat do not have an explicit status set to a404response. Developers are then able to opt back into a403response with the following...Notes