Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
fredpedersen opened this issue Jan 23, 2023 · 6 comments

Comments

@fredpedersen
Copy link

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:

  • Built in expiry - access tokens currently don't expire which is a security risk
  • Automatic integration with Laravel's auth system without needing extra tables, classes
  • Considerably less code to maintain in the plugin
@JanMisker
Copy link

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
Copy link
Author

fredpedersen commented Jan 23, 2023 via email

@JanMisker
Copy link

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
Copy link
Author

fredpedersen commented Jan 23, 2023 via email

@fredpedersen
Copy link
Author

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:

  • sessions are temporary, if an attacker managed to somehow get hold of a session cookie it would expire soon anyway
  • access is automatically cut off to the websocket when the user logs out - this is what the user would expect to happen
  • protecting routes using session cookies passed in headers is well tested
  • this approach uses Laravels built in authentication system that is well tested
  • it vastly reduces the code/tables required by this library ultimately reducing bugs and vulnerabilities

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 fredpedersen changed the title Using session instead of collaborators and tokens Using Laravel's built-in session authentication instead of collaborators and tokens Jan 29, 2023
@fredpedersen
Copy link
Author

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,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants