Table of Contents
The problem #
Imagine you’re running a headless Laravel application which has bunch of APIs with only token based authentication. Well, in an API only application, authenticating Telescope and Horizon is not that straightforward. But you need both of these dashboards in your production environment to monitor your application’s performance and queue system. Leaving it wide open for everyone is not ideal scenario, as they may contain sensitive or confidential information.
So, what to do now? #
In my company, we faced this several times. Fortunately, like other parts of Laravel, you can customize the authentication system of these packages as well and it’s pretty simple. We planned to make use of that and to do some shenanigans to go our way. The idea is basically to set a secret hashed key for authentication. We can put this in
.env and retrieve when we need. Then while visiting the dashboard we can append the unhashed password to the URL and match it against our secret key. We would go one step further so that we don’t have to append this key every time we go to a new link, e.g.
Let’s dive in… #
First pick a secure password and put the hash of it in
.env. We will call it
INSPECTION_SECRET='$2y$10$L/4eZD6mYq/xiORTOhOY9OtDZlKnjrFJZViOx.LpZakEuiQhggt4O' # P@55W0RD (pretty secure, huh?)
Since it is not a good idea to access environment variables via
env() in production environment, we should keep it in one of our config files. Let’s put it in
'inspection_secret' => env('INSPECTION_SECRET'),
Great! Now we can get the hash using
Okay, how to use this key? 🗝️ #
Now head over to the
TelescopeServiceProvider class. There you will see a
gate() method, but it requires a
$user object which we don’t have. So it is not useful to us, instead, we would override the
authorization() method of the
Notice that it calls the
gate() method, we will get rid of it. And inside the
auth() method, we will pass our own closure. In
TelescopeServiceProvider class, override the
authorization() method with the following body:
Now, let’s fill up the body of the closure.
Alright, now to test, change your
production and hit telescope route, e.g. http://localhost:8000/telescope. Voila! You will see a 403 Forbidden page from Laravel. To open the gate, append
?password=P@55W0RD to the URL, e.g. http://localhost:8000/telescope?password=P@55W0RD and you will land in the dashboard.
But wait… #
Yes, it keeps loading forever, right? Open the network tab in browser developer tools and check. You would see swarm of forbidden requests. Why? Because we’ve locked the way and these requests do not have the key. To resolve this issue, we should save the key somewhere. How about session? Change your
authorization() method like following.
Few things we’ve added. First, we are checking if the session has any previous password stored and matching against it. Second, if the request has a password, we are saving it for future requests in this session. So when we land in the dashboard our password is already saved in the session and all background requests will be authenticated using the stored key. Finally, we are deleting the key from session if password is changed in
.env, so it is effective immediately.
We are done #
Now our telescope dashboard is secured. But what about Horizon? You could do exactly the same for Horizon in
HorizonServiceProvider class. However, instead of writing same code again, I would suggest to create a service/support class and use that on both places for the authentication. Again, there might be plenty of room for improvement as there is no one true way to achieve the same goal. If you do find some improvement or a better way please share with the world. 🤝