Discover more from Securing Laravel
Security Tip: Test for Missing Authorisation
[Tip#48] We write tests for everything else, so why not write tests for authorisation as well?
Greetings friends! The second most common security issue I discover when doing security audits on Laravel apps is… Missing Authorisation! I was honestly surprised to see it so high - Laravel has many robust and well-known authorisation tools, but the numbers didn’t lie. As I dug into it, I realised it most commonly occurs on a single route, due to a single
`authorize()` call being missed in a controller action. I’ve covered many of Laravel’s authorisation features before, but for Today’s tip we’re focusing on a very specific idea that will help you avoid this exact problem.
Here’s our current Top 10:
#10 → Insufficient Input Validation
#9 → Missing Subresource Integrity (SRI)
#8 → Insufficient Rate Limiting
#7 → Cross-Site Scripting (XSS)
#6 → Outdated & Vulnerable Dependencies
#5 → Insecure Function Use
#4 → Missing Security Headers
#3 → Missing Content Security Policy (CSP)
#2 → Missing Authorisation
🛡️ When was your last annual security audit?
🕵️ Book a security audit now!
Securing Laravel is 100% reader-supported. Please consider becoming a free or paid subscribe to receive new posts and support my security work in the Laravel community!
Test for Missing Authorisation
In my experience auditing Laravel apps, the most common cause for missing authorisation is due to the developer forgetting to include an
`authorize()` call within a controller action. It’s far less common to forget when authorisation is handled within the routes file1, but it can be easily missed in there too, so I’m talking to everyone here!
Forgetting authorisation is super easy to do - you’re adding your route, writing your controller action, thinking about the logic, where the inputs go, the validation rules you need, which jobs and events are dispatched, and what is returned to the browser. But somewhere in there you’ve forgotten to check if the user has permissions to actually do any of it…
So how do we avoid this trap? Write tests that specifically check for permissions, alongside your feature and unit tests!
If you think about it, it makes perfect sense. We write tests that cover the success and failure states of our features - ensuring everything works as expected, and nothing is broken accidently in the future. So we really should be writing tests that check we’ve implemented authorisation correctly, and that it doesn’t break in the future.
When I’m writing tests, I try to hit each of these use cases2:
Not allowed user request
Allowed user request
I typically use one of these assertions in my tests3:
$this->assertStatus($status); $this->assertUnauthorized(); // 401 $this->assertForbidden(); // 403 $this->assertNotFound(); // 404 $this->assertRedirect($url);
Once you get in the habit of writing these tests, you’ll start automatically writing them when adding new routes, making it harder to forget authorisation. You’ll also find you consider authorisation and permissions more, potentially avoiding assigning the wrong permissions or leaving access too open on sensitive routes.
To learn more about Authorisation in Laravel, you can find all of the past Tips at: https://securinglaravel.com/t/authorization.
This is why I always recommend doing authentication and authorisation within your routes file. You’re far more likely to notice missing auth* when you’re looking at all of your routes in a single place than you are when you’re looking at your controller logic across multiple methods and files.
Always ensure you’ve created all of the models you need for a successful request, otherwise a 404 due to route model binding may provide a false negative in your lacking authorisation tests.
The full list can be found at: https://laravel.com/docs/10.x/http-tests#available-assertions