Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ahoi's avatar
Level 5

Trying to understand authentication with laravel-echo

Hello everyone,

I want to implement Laravel-Echo to my Nuxt3 Application. As Laravel is my backend, I am using Sanctum as auth provider.

I am using nuxt-auth-sanctum, which works nice for my purpose (so far). This is the nuxt.conf.ts:

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  modules: [
    '@nuxt/content',
    '@vueuse/nuxt',
    'nuxt-auth-sanctum'
  ],
  runtimeConfig: {
    backendUrl: process.env.NUXT_BACKEND_URL,
    public: {
      pusherKey: process.env.NUXT_PUBLIC_PUSHER_KEY,
      pusherHost: process.env.NUXT_PUBLIC_PUSHER_HOST,
      pusherWsPort: process.env.NUXT_PUBLIC_PUSHER_WS_PORT,
      pusherWssPort: process.env.NUXT_PUBLIC_PUSHER_WSS_PORT,
      pusherScheme: process.env.NUXT_PUBLIC_PUSHER_SCHEME,
      pusherCluster: process.env.NUXT_PUBLIC_PUSHER_CLUSTER
    }
  },
  sanctum: {
    baseUrl: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/api/${process.env.NUXT_API_VERSION}`,
    origin: process.env.NUXT_PUBLIC_SITE_URL,
    endpoints: {
      csrf: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/csrf`,
      login: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/auth/login`,
      logout: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/auth/logout`,
      user: '/auth/me'
    },
    logLevel: 4,
    redirect: {
      keepRequestedRoute: true,
      onLogin: '/dashboard',
      onLogout: '/',
      onAuthOnly: '/login',
      onGuestOnly: '/dashboard'
    }
  },
  routeRules: {
    '/backend/api/v1/**': {
      proxy: {
        to: `${process.env.NUXT_BACKEND_URL}/api/${process.env.NUXT_API_VERSION}/**`,
        headers: { accept: 'application/json' }
      }
    },
    '/backend/web/**': {
      proxy: {
        to: `${process.env.NUXT_BACKEND_URL}/**`,
        headers: { accept: 'application/json' }
      }
    },
    '/backend/auth/**': {
      proxy: {
        to: `${process.env.NUXT_BACKEND_URL}/auth/**`,
        headers: { accept: 'application/json' }
      }
    },
    '/backend/csrf': {
      proxy: `${process.env.NUXT_BACKEND_URL}/sanctum/csrf-cookie`,
      headers: { accept: 'application/json' }
    },
    '/broadcasting/auth': {
      proxy: `${process.env.NUXT_BACKEND_URL}/api/${process.env.NUXT_API_VERSION}/broadcasting/auth`,
      headers: { accept: 'application/json' }
    },
    '/api/search.json': { prerender: true },
    '/docs': { redirect: '/docs/getting-started', prerender: false }
  },
  devtools: {
    enabled: true
  },
})

This is the echo plugin file called echo.client.ts:

import Echo from 'laravel-echo'
import Pusher from 'pusher-js'
import {
  useCookie
} from '#app'

declare global {
  interface Window {
    pusher: any
  }
}

export default defineNuxtPlugin(() => {
  window.pusher = Pusher
  const config = useRuntimeConfig().public
  const token = useCookie('XSRF-TOKEN')

  const echo = new Echo({
    broadcaster: 'pusher',
    key: config.pusherKey,
    wsHost: config.pusherHost,
    wsPort: config.pusherWsPort,
    wssPort: config.pusherWssPort,
    forceTLS: (config.pusherScheme ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
    cluster: config.pusherCluster,
    auth: {
      headers: {
        'X-XSRF-TOKEN': token.value
      }
    }
  })

  return {
    provide: {
      echo: echo
    }
  }
})

It is actually possible to login and subscribe to a channel like this:

onMounted(() => {
  $echo.private(`Order.${props.order.uuid}`).listen('.OrderShipped', () => {
    refresh()
  })
})

This makes this call:

fetch("http://web.example.test/broadcasting/auth", {
  "headers": {
    "accept": "*/*",
    "accept-language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
    "content-type": "application/x-www-form-urlencoded",
    "x-xsrf-token": "...="
  },
  "referrer": "http://web.example.test/orders",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": "socket_id=6189903872.1771283099&channel_name=private-Order.9c571400-9c2e-4d8a-bdbb-088ea138b0d5",
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});

Now the strange thing happens here:

If I subscribe to another channel like this:

onMounted(() => {
  currentProject.value = data.value.data[selectedProject.value]
  $echo.private(`User.${user.value.uuid}`).listen('.OrderCreated', () => {
    refresh()
  })
})

the subscription process works out fine, too. But if I refresh the page, I am getting logged out. If I remove one $echo.private()-statement, this does not happen. If I just use $echo.channel() (not private), this is not happening, too.

What I did figure out: There's a new session being created in Laravel's sessions-table, if I try to join two channels. If I try to join one channel only, this does not happen.

The first session that is being created before I log in got the useragent node. After logging in, the same session gets the useragent Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36.

Additional session after entering two channels

Now, if I enter multiple channels, the behaviour changes.

I am going to outline the steps:

  1. Entering the page: Session is being created:
id user_id ip_address user_agent payload last_activity SCDpN7Qac64PZFsUIVd9Hi5kxGiVMqKqR45kTdH7 127.0.0.1 node YTozOntzOjY6Il90b2tlbiI7czo0MDoiblNWYTJySFB0OXczQzhQS3RIOElVRmc4bER3WnQyd2JDWU80Q09jVyI7czo5OiJfcHJldmlvdXMiO2E6MTp7czozOiJ1cmwiO3M6MzY6Imh0dHA6Ly9hcGkuaG9zdHIudGVzdC9hcGkvdjEvYXV0aC9tZSI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fX0= 1719216079
  1. After logging in:
id user_id ip_address user_agent payload last_activity I09Iv2sAdubRWUxYUvZj03xZeu9885bPONZstz9u 9c571400-9c2e-4d8a-bdbb-088ea138b0d5 127.0.0.1 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 YTo1OntzOjY6Il90b2tlbiI7czo0MDoiRFlLdnFVVjNYM1hQZ2duYmVRZkVMdElGTWlCQnFFTEpyR1NLQ1dWRCI7czo5OiJfcHJldmlvdXMiO2E6MTp7czozOiJ1cmwiO3M6ODA6Imh0dHA6Ly9hcGkuaG9zdHIudGVzdC9hcGkvdjEvcHJvamVjdC8wMTkwM2IxNC03Yzc0LTcxMWEtOGE2Yy0xNzdhMmUzOTFlYmIvc2VydmVyIjt9czo2OiJfZmxhc2giO2E6Mjp7czozOiJvbGQiO2E6MDp7fXM6MzoibmV3IjthOjA6e319czo1MDoibG9naW5fd2ViXzU5YmEzNmFkZGMyYjJmOTQwMTU4MGYwMTRjN2Y1OGVhNGUzMDk4OWQiO3M6MzY6IjljNTcxNDAwLTljMmUtNGQ4YS1iZGJiLTA4OGVhMTM4YjBkNSI7czoxNzoicGFzc3dvcmRfaGFzaF93ZWIiO3M6NjA6IiQyeSQxMiRyRlNieEZpWTZIVVR5ZExBU1A2cGRlMjZFLy5LY0VSRVlLRzBGNGFKa2Y1L1pHMWx5V0tFTyI7fQ== 1719216200
  1. After refreshing the page
id user_id ip_address user_agent payload last_activity I09Iv2sAdubRWUxYUvZj03xZeu9885bPONZstz9u 9c571400-9c2e-4d8a-bdbb-088ea138b0d5 127.0.0.1 node YTo1OntzOjY6Il90b2tlbiI7czo0MDoiRFlLdnFVVjNYM1hQZ2duYmVRZkVMdElGTWlCQnFFTEpyR1NLQ1dWRCI7czo5OiJfcHJldmlvdXMiO2E6MTp7czozOiJ1cmwiO3M6MzY6Imh0dHA6Ly9hcGkuaG9zdHIudGVzdC9hcGkvdjEvcHJvamVjdCI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fXM6NTA6ImxvZ2luX3dlYl81OWJhMzZhZGRjMmIyZjk0MDE1ODBmMDE0YzdmNThlYTRlMzA5ODlkIjtzOjM2OiI5YzU3MTQwMC05YzJlLTRkOGEtYmRiYi0wODhlYTEzOGIwZDUiO3M6MTc6InBhc3N3b3JkX2hhc2hfd2ViIjtzOjYwOiIkMnkkMTIkckZTYnhGaVk2SFVUeWRMQVNQNnBkZTI2RS8uS2NFUkVZS0cwRjRhSmtmNS9aRzFseVdLRU8iO30= 1719216228 q5YXbbteK7znhilMDKeIBrhvOeEcRUw4Sj8ITGSR 127.0.0.1 node YTozOntzOjY6Il90b2tlbiI7czo0MDoiWFlSdTB5NFcwSk1SSnIzNFBKT0ZhcW5qNWZsa21FVHE4VWFtRXl4NCI7czo5OiJfcHJldmlvdXMiO2E6MTp7czozOiJ1cmwiO3M6MzY6Imh0dHA6Ly9hcGkuaG9zdHIudGVzdC9hcGkvdjEvYXV0aC9tZSI7fXM6NjoiX2ZsYXNoIjthOjI6e3M6Mzoib2xkIjthOjA6e31zOjM6Im5ldyI7YTowOnt9fX0= 1719216229

So I can see that there's a second session being created if I join multiple channels. But the second one does not have an user_id assigned, so the user is logged out.

0 likes
1 reply
ahoi's avatar
Level 5

Too bad, the tables are not shown correctly.

Long story short:

When joining multiple channels, another session is being created with no user_id.

Please or to participate in this conversation.