-
Notifications
You must be signed in to change notification settings - Fork 11.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[9.x] Allow authorization responses to specify HTTP status codes #43097
[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 |
This PR introduces the ability to set the status code for a denied authorization action in policies, gates, and wherever
Auth\Access\Response
orAuthorizationException
are 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
404
for 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
404
response, 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\HandlesAuthorization
e.g. Policies...Returning
Illuminate\Auth\Access\Response
e.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
abort
helper here...However this has a few downfalls.
AuthorizationException
bubble 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
HandlesAuthorization
trait. 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
create
would get the404
, as the implementation was handled in theHandlesAuthorization
trait.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
403
instead. This would be achievable by doing the following in the exception handler...This now converts all
AuthorizationException
that do not have an explicit status set to a404
response. Developers are then able to opt back into a403
response with the following...Notes