ueberdosis / hocuspocus-laravel

Integrates Hocuspocus into Laravel with a few clicks
MIT License
27 stars 8 forks source link

Using Laravel's built-in session authentication instead of collaborators and tokens #4

Open fredpedersen opened 1 year ago

fredpedersen commented 1 year ago

I wondered if there was a reason not to use the built-in session for authentication of requests rather than custom tokens. The session id could be sent with the request the same way the access token currently is, however it would provide a number of advantages over the current access token system:

JanMisker commented 1 year ago

The session won't work because there is no client/websocket connection to the laravel server but only to the hocuspocus server.

The hocuspocus server, via the webhook extension, is in charge of actually updating the document (database table) and this is also authenticated via the same token.

Note: I am not the creator or maintainer of this package, but we do use it in a 'close-to-production' setting.

fredpedersen commented 1 year ago

My understanding is that the onconnected hook on the hocus-pocus server is passed via the websocket extension to the laravel server, where you can authenticate or return 401. If 401 is returned by the laravel server the user is also blocked from the hocus-pocus server.

If the session key was included in the payload the same way the access token currently is, you could use the session to authenticate the user?

On Mon, 23 Jan 2023, 14:54 Jan Misker, @.***> wrote:

The session won't work because there is no client/websocket connection to the laravel server but only to the hocuspocus server.

The hocuspocus server, via the webhook extension, is in charge of actually updating the document (database table) and this is also authenticated via the same token.

Note: I am not the creator or maintainer of this package, but we do use it in a 'close-to-production' setting.

— Reply to this email directly, view it on GitHub https://github.com/ueberdosis/hocuspocus-laravel/issues/4#issuecomment-1400292844, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6COEJFUGLD2EGYHYOVXILWTZ5Q7ANCNFSM6AAAAAAUDU6UHQ . You are receiving this because you authored the thread.Message ID: @.***>

JanMisker commented 1 year ago

Indeed, basically there are 2 points of authorization: 1) onConnected, and 2) onChange (with a delay) to store the document back to the database. Both via callbacks to this extension.

I guess the tricky part with using a session token is what happens when the token expires. How to provide a new session token? But as said, I didn't create this package, I hope the authors also respond.

fredpedersen commented 1 year ago

Exactly. When it expires the server would return unauthorized and the app should just redirect to the laravel login page. A new login would equal a new session token which would be passed back to the app via window.session just like the access token is currently passed.

Two different methods of Auth means double the potential for bugs and security risks. For example if an account was compromised the attacker could log in and get the access token and access to the account forever. The user would likely reset their password and then consider the account secure however this would not effect access tokens. I suspect the double system introduces more such vulnerabilities.

On Mon, 23 Jan 2023, 15:32 Jan Misker, @.***> wrote:

Indeed, basically there are 2 points of authorization: 1) onConnected, and 2) onChange (with a delay) to store the document back to the database. Both via callbacks to this extension.

I guess the tricky part with using a session token is what happens when the token expires. How to provide a new session token? But as said, I didn't create this package, I hope the authors also respond.

— Reply to this email directly, view it on GitHub https://github.com/ueberdosis/hocuspocus-laravel/issues/4#issuecomment-1400349591, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB6COEMLQMNTRK3SWT642FDWT2CATANCNFSM6AAAAAAUDU6UHQ . You are receiving this because you authored the thread.Message ID: @.***>

fredpedersen commented 1 year ago

I've managed to get this approach working for anyone interested. I believe it's significantly better than the access token system currently used because:

It does require a csrf token to be passed in place of the accessToken however, as websockets are vunerable to csrf attacks.

My method is to add a middleware that simply moves the session cookie, and csrf token to the request headers before the request is seen by the other built-in middleware. The built-in middleware then handles the authentication, verification of csrf, and makes the authenticated user available via $request->user()

Kernel.php

protected $routeMiddleware = [
        'hocus' => \App\Http\Middleware\ParseHocusRequest::class,
.....
];
protected $middlewarePriority = [
        \App\Http\Middleware\ParseHocusRequest::class
];

ParseHocusRequest::class

class ParseHocusRequest
{
    public function handle($request, Closure $next)
    {
        $json = json_decode($request->getContent() ?: '{}', true);

        if (!isset($json['payload']['requestParameters']) ||
            !isset($json['payload']['requestHeaders']['cookie'])) {
            throw new BadRequestException('Invalid payload');
        }

        $cookies = collect(explode('; ', $json['payload']['requestHeaders']['cookie']))->map(function ($item) {
            return explode('=', $item);
        })->mapWithKeys(function ($item) {
            return [$item[0] => urldecode($item[1])];
        })->toArray();

        $request->cookies->add($cookies);
        $request->headers->set('X-CSRF-TOKEN', $json['payload']['requestParameters']['csrf_token']);

        return $next($request);
    }
}

routes/api.php

Route::middleware(['hocus', 'web', 'auth', 'verified'])->post(config('hocuspocus-laravel.route'), [HocuspocusLaravel::class, 'handleWebhook']);

You also need to pass a csrf token to the hocuspocus provider, for example:

In <script>

    window.csrfToken = '{{ csrf_token() }}';
const provider = new HocuspocusProvider({
                url:  'ws://localhost:1234',
                name: this.documentName,
                document: ydoc,
                parameters: {
                    csrf_token: window.csrfToken,
                },
            })
fredpedersen commented 1 year ago

Update

Auth with Laravel is better achieved using the Hocuspocus server's built-in onAuthenticate hook. No custom middleware is needed:

 onAuthenticate(data) {
        return new Promise((resolve, reject) => {
            const headers = data.requestHeaders;
            headers["X-CSRF-TOKEN"] = data.token;

            axios.get(
                process.env.APP_URL + '/api/hocus',
                { headers: headers },
            ).then(function (response) {
                if (response.status === 200)
                    resolve()
                else
                    reject()
            }).catch(function (error) {
                reject()
            })
        })
    },

Plus add the csrf token in Hocuspocus provider:

const provider = new HocuspocusProvider({
    token: window.csrfToken,