Security Tip: Test for Missing Authorisation

[Tip#48] We write tests for everything else, so why not write tests for authorisation as well?

Security Tip: 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 file, but it can be easily missed in there too, so I’m talking to everyone here!

💡
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.

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 cases:

  • Guest request
  • Not allowed user request
  • Allowed user request
💡
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.

I typically use one of these assertions in my tests (here is the full list):

$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.

Authorisation Resources

To learn more about Authorisation in Laravel, you can find all of the past Tips at: https://securinglaravel.com/t/authorization.

Also check out these articles:

In Depth: Policy Objects
[InDepth#8] Policy Objects are incredibly powerful. Use them.
In Depth: Signed URLs
[InDepth#9] One of the many awesome and completely underrated Laravel security features.
In Depth: Insecure Direct Object References (IDOR)
[InDepth#11] Also known as hide-and-seek, and security through obscurity! Challenge yourself with the new IDOR challenges in our intentionally vulnerable webapp!
Security Tip: Don’t Forget About Policy Filters!
[Tip#2] Policy Filters let you implement shared authorisation checks across your entire policy without repeating code in every method.

If you've made it this far, please share this Security Tip and Securing Laravel with your Laravel friends, colleagues, and enemies! The more folks we have learning about security, the more secure code will be written, and the safer the whole community and our users will be. Also, if you tag me I'll give it a retweet, and you can find all my socials on Pinkary.