Level 2
please show your client side code, i think that's where the problem comes from
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
I'm using laravel and vue 3 and I'm trying to get sanctum to work.
The issue I'm having is that after logging in and I do something then I get an Unauthenticated error
I don't know if I missed something when it comes to setting up sanctum.
My api.php
Route::middleware('auth:sanctum')->group(function() {
Route::post('/files/upload', [ FileController::class, 'uploadFile']);
});
My config/auth.php
<?php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'sanctum',
'provider' => 'users',
'hash' => false
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];
My config/sanctum.php
<?php
use Laravel\Sanctum\Sanctum;
return [
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort()
))),
'guard' => ['web'],
'expiration' => null,
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],
];
and for the env('SANCTUM_STATEFUL_DOMAINS' I have this in my env SANCTUM_STATEFUL_DOMAINS=localhost:6010
please show your client side code, i think that's where the problem comes from
@Thunderson sure, here it is bootstrap.js
import 'bootstrap';
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
app.js
import './bootstrap';
import { createApp } from 'vue';
const app = createApp({});
import ExampleComponent from './components/ExampleComponent.vue';
app.component('example-component', ExampleComponent);
app.mount('#app');
I also have work.js
import { createApp } from 'vue';
import '../bootstrap';
import Index from "./Index.vue";
const app = createApp({});
app.component('index', Index);
app.mount('#work-app');
Index.vue
<template>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-12">
<input type="file" ref="fileInput" @change="handleFileUpload" style="display: none" />
<button class="btn btn-outline-primary mb-3" @click="selectFile">
<i class="fas fa-upload"></i> Upload File
</button>
<div class="card">
<div class="card-header">
<div class="float-left">
Files
</div>
<div class="float-right">
<nav aria-label="Page navigation example">
<ul class="pagination pagination-sm">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
<div class="card-body">
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th scope="col">File Name</th>
<th scope="col">Status</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<a href="">
File name 1
</a>
</td>
<td>
<span class="badge text-bg-primary">In Progress</span>
</td>
<td>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn dropdown-toggle btn btn-outline-primary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-cogs"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#">
<i class="fas fa-edit"></i> Edit
</a>
</li>
<li>
<a class="dropdown-item text-danger" href="#">
<i class="fas fa-trash"></i> Delete
</a>
</li>
</ul>
</div>
</td>
</tr>
<tr>
<td>
<a href="">
File name 2
</a>
</td>
<td>
<span class="badge text-bg-success">Completed</span>
</td>
<td>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn dropdown-toggle btn btn-outline-primary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-cogs"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#">
<i class="fas fa-edit"></i> Edit
</a>
</li>
<li>
<a class="dropdown-item text-danger" href="#">
<i class="fas fa-trash"></i> Delete
</a>
</li>
</ul>
</div>
</td>
</tr>
<tr>
<td>
<a href="">
File name 3
</a>
</td>
<td>
<span class="badge text-bg-secondary">To Do</span>
</td>
<td>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn dropdown-toggle btn btn-outline-primary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-cogs"></i>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#">
<i class="fas fa-edit"></i> Edit
</a>
</li>
<li>
<a class="dropdown-item text-danger" href="#">
<i class="fas fa-trash"></i> Delete
</a>
</li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: [],
components: {},
data(){
return {
newFiles: []
}
},
computed: {
},
methods: {
selectFile() {
this.$refs.fileInput.click();
},
handleFileUpload(event) {
this.selectedFile = event.target.files[0];
this.uploadFile();
},
async uploadFile() {
const formData = new FormData();
formData.append('file', this.selectedFile);
try {
const response = await axios.post('/api/files/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
console.log(response.data);
} catch (error) {
console.error('Error uploading file:', error);
}
},
},
mounted() {
console.log('Component mounted.')
}
}
</script>
my master.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
{{-- Base Meta Tags --}}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
{{-- Custom Meta Tags --}}
@yield('meta_tags')
{{-- Title --}}
<title>
@yield('title_prefix', config('adminlte.title_prefix', ''))
@yield('title', config('adminlte.title', 'AdminLTE 3'))
@yield('title_postfix', config('adminlte.title_postfix', ''))
</title>
{{-- Custom stylesheets (pre AdminLTE) --}}
@yield('adminlte_css_pre')
{{-- Base Stylesheets --}}
@if(!config('adminlte.enabled_laravel_mix'))
<link rel="stylesheet" href="{{ asset('vendor/fontawesome-free/css/all.min.css') }}">
<link rel="stylesheet" href="{{ asset('vendor/overlayScrollbars/css/OverlayScrollbars.min.css') }}">
<link rel="stylesheet" href="{{ asset('vendor/adminlte/dist/css/adminlte.min.css') }}">
@if(config('adminlte.google_fonts.allowed', true))
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
@endif
@else
<link rel="stylesheet" href="{{ mix(config('adminlte.laravel_mix_css_path', 'css/app.css')) }}">
@endif
{{-- Extra Configured Plugins Stylesheets --}}
@include('adminlte::plugins', ['type' => 'css'])
{{-- Livewire Styles --}}
@if(config('adminlte.livewire'))
@if(intval(app()->version()) >= 7)
@livewireStyles
@else
<livewire:styles />
@endif
@endif
{{-- Custom Stylesheets (post AdminLTE) --}}
@yield('adminlte_css')
{{-- Favicon --}}
@if(config('adminlte.use_ico_only'))
<link rel="shortcut icon" href="{{ asset('favicons/favicon.ico') }}" />
@elseif(config('adminlte.use_full_favicon'))
<link rel="shortcut icon" href="{{ asset('favicons/favicon.ico') }}" />
<link rel="apple-touch-icon" sizes="57x57" href="{{ asset('favicons/apple-icon-57x57.png') }}">
<link rel="apple-touch-icon" sizes="60x60" href="{{ asset('favicons/apple-icon-60x60.png') }}">
<link rel="apple-touch-icon" sizes="72x72" href="{{ asset('favicons/apple-icon-72x72.png') }}">
<link rel="apple-touch-icon" sizes="76x76" href="{{ asset('favicons/apple-icon-76x76.png') }}">
<link rel="apple-touch-icon" sizes="114x114" href="{{ asset('favicons/apple-icon-114x114.png') }}">
<link rel="apple-touch-icon" sizes="120x120" href="{{ asset('favicons/apple-icon-120x120.png') }}">
<link rel="apple-touch-icon" sizes="144x144" href="{{ asset('favicons/apple-icon-144x144.png') }}">
<link rel="apple-touch-icon" sizes="152x152" href="{{ asset('favicons/apple-icon-152x152.png') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('favicons/apple-icon-180x180.png') }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicons/favicon-16x16.png') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicons/favicon-32x32.png') }}">
<link rel="icon" type="image/png" sizes="96x96" href="{{ asset('favicons/favicon-96x96.png') }}">
<link rel="icon" type="image/png" sizes="192x192" href="{{ asset('favicons/android-icon-192x192.png') }}">
<link rel="manifest" crossorigin="use-credentials" href="{{ asset('favicons/manifest.json') }}">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="{{ asset('favicon/ms-icon-144x144.png') }}">
@endif
</head>
<body class="@yield('classes_body')" @yield('body_data')>
{{-- Body Content --}}
@yield('body')
{{-- Base Scripts --}}
@if(!config('adminlte.enabled_laravel_mix'))
<script src="{{ asset('vendor/jquery/jquery.min.js') }}"></script>
<script src="{{ asset('vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ asset('vendor/overlayScrollbars/js/jquery.overlayScrollbars.min.js') }}"></script>
<script src="{{ asset('vendor/adminlte/dist/js/adminlte.min.js') }}"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
@vite([
'resources/js/app.js',
'resources/js/work/work.js',
])
@else
<script src="{{ mix(config('adminlte.laravel_mix_js_path', 'js/app.js')) }}"></script>
@endif
{{-- Extra Configured Plugins Scripts --}}
@include('adminlte::plugins', ['type' => 'js'])
{{-- Livewire Script --}}
@if(config('adminlte.livewire'))
@if(intval(app()->version()) >= 7)
@livewireScripts
@else
<livewire:scripts />
@endif
@endif
{{-- Custom Scripts --}}
@yield('adminlte_js')
</body>
</html>
page.blade.php
@extends('adminlte::master')
@inject('layoutHelper', 'JeroenNoten\LaravelAdminLte\Helpers\LayoutHelper')
@inject('preloaderHelper', 'JeroenNoten\LaravelAdminLte\Helpers\PreloaderHelper')
@section('adminlte_css')
@stack('css')
@yield('css')
@stop
@section('classes_body', $layoutHelper->makeBodyClasses())
@section('body_data', $layoutHelper->makeBodyData())
@section('body')
<div class="wrapper">
{{-- Preloader Animation (fullscreen mode) --}}
@if($preloaderHelper->isPreloaderEnabled())
@include('adminlte::partials.common.preloader')
@endif
{{-- Top Navbar --}}
@if($layoutHelper->isLayoutTopnavEnabled())
@include('adminlte::partials.navbar.navbar-layout-topnav')
@else
@include('adminlte::partials.navbar.navbar')
@endif
{{-- Left Main Sidebar --}}
@if(!$layoutHelper->isLayoutTopnavEnabled())
@include('adminlte::partials.sidebar.left-sidebar')
@endif
{{-- Content Wrapper --}}
@empty($iFrameEnabled)
@include('adminlte::partials.cwrapper.cwrapper-default')
@else
@include('adminlte::partials.cwrapper.cwrapper-iframe')
@endempty
{{-- Footer --}}
@hasSection('footer')
@include('adminlte::partials.footer.footer')
@endif
{{-- Right Control Sidebar --}}
@if(config('adminlte.right_sidebar'))
@include('adminlte::partials.sidebar.right-sidebar')
@endif
</div>
@stop
@section('adminlte_js')
@stack('js')
@yield('js')
@stop
files.blade.php
@extends('adminlte::page')
@section('content')
<div id="work-app">
<index></index>
</div>
@endsection
Please or to participate in this conversation.