laravel / telescope

An elegant debug assistant for the Laravel framework.
https://laravel.com/docs/telescope
MIT License
4.84k stars 577 forks source link

How to make the gate work without session based authentication #337

Closed denjaland closed 5 years ago

denjaland commented 5 years ago

Hi,

Our SPA does not have session based authentication. In stead, we have our app login using passport, and use token based authentication for all subsequent requests.

Our app stores the token in session storage, but obviously since the vuejs component won't pick it automatically to send the Bearer token along, I van login at mydomain.com but as soon as I go to mydomain.com/telescope, telescope won't have the authentication set up.

How do you suggest working around this?

I must say I absolutely LOVE all the tooling Laravel creates, paid or unpaid (in fact just bought some of them even though I don't use them, but to support you guys ;)), but would like more of these tools to have a documented API, so we don't need to use the front developed / designed by you, but can just incorporate it into our dashboard directly using the API calls you document.

I know I can use the API's myself now, but I've always learned not to use an undocumented API, as it is bound to be changed without notificatoin :-)

Any thoughts?

deleugpn commented 5 years ago

I'm not sure if Telescope APIs will ever be intended for public consumption. If you think about it, it's just a simple SELECT statement on a telescope_entries table. If you want to use your own dashboard, you can easily build your own API around this table and rely on Telescope just to feed the table for you.

paras-malhotra commented 5 years ago

I think it would be very easy to do. Since Laravel Telescope does use loadViewsFrom, you can override the package's view by adding a customized view in your resources/views/vendor/telescope directory.

In this custom view, just include all of Laravel Telescope's layout.blade.php and add in your javascript code to have axios globally add any headers that you'd like for your authentication!

In case you want to send additional data in all your responses, you can do that too by creating an After Middleware and adding it to your config/telescope.php in the middleware key.

This way you can use Telescope's existing UI / frontend with a simple tweak.

denjaland commented 5 years ago

@deleugpn depending on an undocumented table poses the same risks as depending on the undocumented api... I don’t like relying on stuff that might change without any notifications in release notes, which will definitely happen since it’s undocumented ;-)

denjaland commented 5 years ago

@paras-malhotra I’ll look into it, but I believe telescope will already return an access denied for loading that very first view, before I’m even able to set up an axios interceptor...

We never rely on the sessions as laravel session handling turned out to be unreliable as soon as you have more than one simultaneous request on the same setting. So we have user authenticate and then treat everything as being stateless, which is better for scaling as well anyway...

deleugpn commented 5 years ago

Table changes require a new migration to be executed, which requires your explicit involvement. Besides that, it's much more likely that a migration will either demand a major release (semver) or be compatible. The internal API, on the other hand, presents no need to do a major release as it's an internal API. In conclusion, I honestly think that relying on the table structure is much safer in terms of breaking change and disagree with your reasoning that it presents the same risk.

denjaland commented 5 years ago

I see what you mean; @deleugpn , but the database is just part of the "internal structure" of satellite as well. Since migrations can be registered in the service provider, there is no way that I can be sure that there won't be any new migration, even on a minor version.

I agree that it's probably less likely to happen (even though I'm not even to sure about that), but the issue remains that as long as something's undocumented, you should consider it to only be used by the package itself internally. I never rely on internals of a package; any minor change in there can completely destroy what you built based on that.

paras-malhotra commented 5 years ago

@denjaland noone will secretly place additional migrations in the service provider on a minor release. At least you can be assured of that for Laravel official repos. I think it's the same probability as someone throwing an Exception in the boot method of the ServiceProvider :)

denjaland commented 5 years ago

I'm not saying the probability is there. I'm just saying that I like all the (official or not) packages; I use satellite, I use horizon, but I would feel a lot more comfortable if the API was documented, so I know the probablility of changes are less likely or at at least documented with new releases.

I personally hate the statement "it's very unlikely to happen", as these are most of the times the things that DO happen. I've seen entire projects go under because developers "thing something is not going to happen". In the end, it occurs, and they are left with two choices: make changes to their code, or not upgrade the package they depend on, and be left with old versions.

Again - laravel is great, the packages are great, but when tooling like this is released, I honestly miss a "bare api-based" part; the UI is nice to have, but I'd prefer to have the documented API. That's all.

themsaid commented 5 years ago

You can define your own middleware applied to Telescope routes in telescope.middleware config value then use Telescope::auth to define if a certain user should access telescope or not.

denjaland commented 5 years ago

Hiya @themsaid,

How does that help? Still not getting the token in like that...

denjaland commented 5 years ago

FYI, I ended up writing a route that sets a temporary cookie, and then added middleware for the telescope routes to check that cookie to authenticate a user. Not that happy with the solution, but it doesn't seem to be high on the list here to have a more elegant solution ;-)

AustinW commented 5 years ago

I was having similar trouble as we have a Vue SPA using Passport and needed authentication for telescope. Our solution was the following:

In our login process, we added: Auth::guard('web')->login($user);

Next, in our logout process, we added Augh::guard('web')->logout();

In our config/telescope.php, we changed: 'middleware' => ['web', 'auth:web', Authorize::class],

And finally, our TelescopeServiceProvider:

/**
 * Configure the Telescope authorization services.
 *
 * @return void
 */
protected function authorization()
{
    $this->gate();

    Telescope::auth(function (Request $request) {
        $user = $request->user();

        $isLocal = App::environment('local');
        $gateCheck = Gate::check('viewTelescope', $user);

        return $user !== null && ($isLocal || $gateCheck);
    });
}

/**
 * Register the Telescope gate.
 *
 * This gate determines who can access Telescope in non-local environments.
 *
 * @return void
 */
protected function gate()
{
    Gate::define('viewTelescope', function (User $user) {
        return $user->hasPermissionTo('view telescope');
    });
}

Uses: https://github.com/spatie/laravel-permission

The real trick for us, as @themsaid pointed out, was configuring the middlewares properly. Hope that helps anyone.

vrkansagara commented 4 years ago

Best way to use Telescope with SPA as bellow middleware.

<?php

namespace App\Http\Middleware;

use App\Models\User;
use Closure;
use Illuminate\Support\Facades\Cookie;
use Tymon\JWTAuth\Facades\JWTAuth;

class Authorize
{
    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $telescopeUrl = config('telescope.path');
        $currentUrl = $request->url();
        $matches = \Illuminate\Support\Str::is("*$telescopeUrl*", $currentUrl);
        $adminToken = Cookie::get('Admin-Token');
        if (
            isset($adminToken)
            && !empty($adminToken)
            && $matches
        ) {
            $request->headers->add(['Authorization' => 'Bearer ' . $adminToken]);
            $jwtObject = JWTAuth::parseToken($adminToken);
            if (null != $jwtObject->user() && $jwtObject->user() instanceof User) {
                $user = $jwtObject->user();
                dd($user);
            }
            $request->headers->remove('Authorization');
        }
        return $next($request);

    }
}