
We use a Laravel-powered dashboard app at Highland to monitor the health of the company. The app updates all its data using scheduled tasks, so aside from consuming that data, we donât interact with the app. Weâve no need for user accounts except to keep the info private, but creating accounts and logging in feels excessive, so we decided to lock things down another way.
Our first solution was to set up a firewall, but when team members started building Slack apps and Alexa skills that query the dashboardâs API, we knew we needed to manage whitelisted IPs at a higher-level to more easily account for volatile IPs. We opted for a fairly simple solution powered by middleware and Trusted Proxies (a package Laravel includes by default) for the front end and, of course, Passport for the API.
Implementing the solution
If you clicked the above link to Laravelâs docs, youâll notice they tell you to just drop your proxies in app/Http/Middleware/TrustProxies.php
 all willy nilly, but that doesnât always play out so well across different environments. Inspecting the extended Fideloper\Proxy\TrustProxies
 class for an unintrusive alternative, I discovered it pulls from the config in its method setTrustedProxyIpAddresses()
 which the oh-so-familiar handle()
 method (or, dare I say, gentleman-caller) calls for us.
I could just drop a new config variable in config/app.php
 and use it in app/Http/Middleware/TrustProxies.php
, but Trusted Proxies already has one, so it feels a little more out-of-the-box to just use it.
I only need to run php artisan vendor:publish â-provider="Fideloper\Proxy\TrustedProxyServiceProvider"
 so I can drop an environment variable in there.
Side note: I name my environment variables after the related file and config key, e.g. TRUSTEDPROXY_PROXIES
 for config/trustedproxy.php
's proxies
 key. This standard prevents naming collisions and keeps teams from getting frustrated over Jimmyâs HORRIBLE NAMING CHOICES, which in turn saves time during retros. #protip
I ran into some fun⸎ issues and discovered that specifying my proxies as an array works on the server, but using *
 (WHITELIST ALL THE IPS! _o/) in an array was problematic when running tests on Travis. That explains the unsightly string vs. array war at the top of my config/trustedproxy.php
.
Middlesware
Trusted Proxies doesnât actually block IPs. It just happens to overlap cleanly enough for my taste to repurpose things a bit. To block any IPs not whitelisted as trusted proxies, we need some simple middleware. So simple I wonât even explain it. Here it is:
When registering the middleware, I didnât want to wrap everything in routes/web.php
 in a single Route::group()
, so I just added it to the web middleware group in app/Http/Kernel.php
 to keep from muddying things up.
Potentially relevant to some⌠our dashboardâs front end is an SPA that uses API-querying Vue components, and worrying about bearer tokens when dogfooding an API on the same server is unnecessarily tedious nonsense. So I haz another middleware for you to point your lookyballs at. It suffices for our uses, but definitely not all, so donât use it blindly.
This checks if the request is from one of the whitelisted IPs and injects our bearer token if so. When getting the bearer token, it pulls from the cache if already set, otherwise it deletes the existing token(s) (just to keep the database a bit more manageable) then creates and caches a new one. Of course, I also need to assign the middleware a key in app/Http/Kernel.php
âs $routeMiddleware
. I choose you, Pika-injectbearertoken
-chu!
I also need to set the middleware priority to ensure the app calls the InjectBearerTokenForLocalApiRequests
 middleware early enough that the app doesnât reject the requests. The App\Http\Kernel
 class extends Illuminate\Foundation\Http\Kernel
 which already has the $middlewarePriority
 property defined. I copied that over to app/Http/Kernel.php
 and dropped my middleware in there as late in priority as practical.
Since not all of the dashboardâs API endpoints are relevant, I opted to Route::group()
 the relevant jabronis in routes/api.php
.
With this in place:
we can remove the firewall,
I can continue querying the internal API from the front end without touching any of the JavaScript,
my team members can query the API (with Passport securing that side), and
Bobâs your uncle (assuming you have an Uncle Bob⌠I do not).
Let me know if you have any interest in seeing some tests or would benefit from a writeup on working with Projectorâs API, which, for the uninitiated, is a bit less straightforward than we typically expect from modern apps.