Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

CSRF token mismatch. #428

Closed
driesvints opened this issue Aug 6, 2019 · 18 comments
Closed

CSRF token mismatch. #428

driesvints opened this issue Aug 6, 2019 · 18 comments

Comments

@driesvints
Copy link

driesvints commented Aug 6, 2019

I get the above error when I run laravel-echo-server start. None of the issues I've searched in the issue tracker have helped so far. The event is also not being broadcasted. Queue and Broadcast drivers have been set to Redis. Broadcast service provider was correctly registered in app.php.

I'm just using a default laravel app installation btw.

Full log: https://paste.laravel.io/bdcbf334-b7ce-46d5-b0a8-0b49703cc546#1,21

JS code:

import Echo from 'laravel-echo';

window.io = require('socket.io-client');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
    authEndpoint: "/broadcasting/auth",
});

window.Echo.private(`test`)
    .listen('.user.registered', function (e) {
        console.log(e);
    });

Channels:

Broadcast::channel('App.User.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Broadcast::channel('test', function ($user) {
    return true;
});

Event:

<?php

namespace App\Events;

use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class UserRegistered implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('test');
    }

    public function broadcastAs()
    {logger('broadcast as');
        return 'user.registered';
    }

    public function broadcastWith()
    {
        return ['id' => $this->user->id];
    }
}

I call the event with:

event(new \App\Events\UserRegistered(Auth::user()));

Really at a loss here. No idea why I get the error after starting the web server or why I can't broadcast events.

@driesvints
Copy link
Author

I managed to get it to work eventually. The CSRF issue suddenly disappeared. I have no idea why.

@tlaverdure
Copy link
Owner

Hey there!

If a meta tag with the csrf-token as the value is found, it will be automatically passed by echo as the header: X-CSRF-TOKEN.

    protected setOptions(options: any): any {
        this.options = Object.assign(this._defaultOptions, options);


        if (this.csrfToken()) {
            this.options.auth.headers['X-CSRF-TOKEN'] = this.csrfToken();
        }


        return options;
    }

Inspired by:
https://laravel.com/docs/5.8/csrf#csrf-x-csrf

You could do it manually too:

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001',
    authEndpoint: "/broadcasting/auth",
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

@driesvints
Copy link
Author

@tlaverdure I was just using a fresh Laravel 5.8 install. Everything should be good to go out of the box really. Thanks for helping out.

@driesvints
Copy link
Author

I just discovered that this happens when I run in dev mode. In production mode it's fine.

@2p4b
Copy link

2p4b commented Jan 30, 2020

This issue still plagues me because everything seams fine but token isn't being sent by echo

@2p4b
Copy link

2p4b commented Jan 30, 2020

@driesvints please what did you do to resolve this? other than running in production mode

@driesvints
Copy link
Author

No sorry. It's been too long ago.

@willsmanley
Copy link

@mfoncho check the meta tags in your view. Do you see the CSRF token? If it is probably missing

@2p4b
Copy link

2p4b commented Feb 1, 2020

@willsmanley i have everything working including passport just websockets!

@2p4b
Copy link

2p4b commented Feb 1, 2020

The problem is cookies. The cookies aren't sent with the channel subscription payload to verify to token against. I did a reverse proxy so the websocket and host are on the same primary host domain but i'm getting some other errors.
Going against laravel framework default (pusher) is brutal uphill battle

@willsmanley
Copy link

Echo sends the CSRF token in the request header automatically if it detects the CSRF meta tag in the DOM.

Otherwise you’ll have to pass it manually as shown above by @tlaverdure

@willsmanley
Copy link

You should just go to chrome's network tab and inspect the request sent by echo. See if it has the right cookies there.

@2p4b
Copy link

2p4b commented Feb 1, 2020

@willsmanley You not getting it I have that setup already..! CSRF on its own mean nothing without cookies CSRF tokens are not JWT's. say a site loads on localhost:8000 and websockets load on port 6001 if you have strict cookie policies all requests to port 6001 will not get the path cookies on port 8000. Yes laravel validates subscriptions with Echo but only in the case of Pusher as far as i know. what i see here with socket.io is. the request is made by the echo server to laravel to authenticate the subscription.

@2p4b
Copy link

2p4b commented Feb 1, 2020

Thats why every subscription with request payload is sent with the CSRF token over websockets to the socket.io server then the server tries to authenticate the subscription. So instead of the browsers Echo client sending the request to authenticate a channel the http request params are sent over the websocket connection so the server can do that for the Echo client and authenticate all in one go.

@2p4b
Copy link

2p4b commented Feb 1, 2020

Sending a JWT token header to be sent to the socket.io server and changing the Broadcast routes guard to passport's api works fine. no errors. But sending JWT tokens for someother service to Act As is kinda mehhh.

@willsmanley
Copy link

Oh right, well still inspect to see if the echo request has the session cookie attached. If it does, then it’s a problem with routing on port 6001.

You can also inject a Log::debug() into the route for /broadcasting/auth route to inspect the request to see what cookies it has once it’s received by your app.

@marcnewton
Copy link

marcnewton commented Mar 9, 2020

I finally got pragmatic headers to work, Since I built a full Same Domain SPA the CSRF token is pragmatically stored in a state that is set via an axios interceptor looking for a token change so I get the token a bit later and it will update when the user session state changes or the token is invalidated and renewed.

None of the solutions above worked for setting the token AFTER the instance is crated, here is a solution that does:

import io from 'socket.io-client'

window.Echo = new LaravelEcho({
  broadcaster: 'socket.io',
  host: { path: '/socket.io' },
  client: io
})

// A FEW MOMENTS LATER...
// axios call to login a user, a new token is created and needs to be set.
window.Echo.connector.options.auth.headers['X-CSRF-TOKEN'] = token

The problem is if you login a user via ajax and do not reload the page then the token from the meta tag is not going to match anymore since Laravel changes the token value when the user session state changes.
An example of session state change is when a Guest Session becomes a User session (Login) or User Session becomes Guest session (Logout).

My use case looks like this:

vm.$on('user:authenticated', authenticated => {
  if (authenticated && vm.user.id != null) {
    vm.$Echo.connector.options.auth.headers = vm.$http.defaults.headers.common

    vm.$Echo.private(`App.User.${vm.user.id}`).notification(response => {
      console.info('Private Notification', response)
    })
  }
})

@jsphpl
Copy link

jsphpl commented Aug 15, 2020

For me the issue was that i had the echo server running on a different subdomain than the main application. App accessible under app.example.com, echo server under sockets.example.com. Solution was to extend the session cookie domain by setting SESSION_DOMAIN=.example.com (mind the leading dot)

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

No branches or pull requests

6 participants