Kritim Yantra
Jan 08, 2026
“Okay… but how do real users log in and stay logged in?”
You’re asking the right question.
Authentication is where most beginners get stuck. Tokens, cookies, CSRF, sessions—it can feel like a foreign language. I remember staring at a 401 Unauthorized error for hours, wondering what I broke.
In this blog, we’ll build authentication the correct, modern way in 2026, using:
No shortcuts. No skipped steps. Just a clean, understandable flow.
By the end of this tutorial, you will have:
Laravel = office security system
Vue = employee
Sanctum = ID card
Once logged in, the employee can move freely until they log out.
Laravel Sanctum handles authentication in two ways:
In short: less headache, more productivity.
From your Laravel project root:
composer require laravel/sanctum
Publish config:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
Run migrations:
php artisan migrate
Sanctum is now installed.
Open:
config/sanctum.php
Make sure stateful domains include your app:
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,127.0.0.1'
)),
.envSESSION_DRIVER=cookie
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost
Warning: If this step is wrong, login will work but every request will return 401.
I learned this the hard way.
Open:
bootstrap/app.php
Ensure this middleware exists:
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
$middleware->append([
EnsureFrontendRequestsAreStateful::class,
]);
This allows Vue (SPA) to authenticate using cookies.
Create controller:
php artisan make:controller Api/AuthController
AuthController.phpuse Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
class AuthController extends Controller
{
public function register(Request $request)
{
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
]);
Auth::login($user);
return response()->json($user);
}
public function login(Request $request)
{
if (!Auth::attempt($request->only('email', 'password'))) {
return response()->json(['message' => 'Invalid credentials'], 401);
}
return response()->json(Auth::user());
}
public function user(Request $request)
{
return $request->user();
}
public function logout()
{
Auth::logout();
return response()->noContent();
}
}
Open:
routes/api.php
use App\Http\Controllers\Api\AuthController;
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', [AuthController::class, 'user']);
Route::post('/logout', [AuthController::class, 'logout']);
});
This means:
/user only works when logged in/logout only works when logged inOpen:
resources/js/app.js
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.defaults.baseURL = 'http://localhost:8000';
Before login/register, Sanctum needs CSRF:
await axios.get('/sanctum/csrf-cookie');
This is mandatory.
Auth.vue<template>
<div>
<h2>Register</h2>
<input v-model="register.name" placeholder="Name" />
<input v-model="register.email" placeholder="Email" />
<input v-model="register.password" type="password" placeholder="Password" />
<button @click="registerUser">Register</button>
<hr>
<h2>Login</h2>
<input v-model="login.email" placeholder="Email" />
<input v-model="login.password" type="password" placeholder="Password" />
<button @click="loginUser">Login</button>
<hr>
<button @click="getUser">Get Auth User</button>
<button @click="logout">Logout</button>
<pre>{{ user }}</pre>
</div>
</template>
<script setup>
import axios from 'axios'
import { ref } from 'vue'
const register = ref({ name:'', email:'', password:'' })
const login = ref({ email:'', password:'' })
const user = ref(null)
const registerUser = async () => {
await axios.get('/sanctum/csrf-cookie')
user.value = (await axios.post('/api/register', register.value)).data
}
const loginUser = async () => {
await axios.get('/sanctum/csrf-cookie')
user.value = (await axios.post('/api/login', login.value)).data
}
const getUser = async () => {
user.value = (await axios.get('/api/user')).data
}
const logout = async () => {
await axios.post('/api/logout')
user.value = null
}
</script>
Route::middleware('auth:sanctum')->get('/posts', fn () => Post::all());
if (!user.value) {
// redirect to login
}
/sanctum/csrf-cookiewithCredentialsIf auth returns 401, check Sanctum config first.
For SPAs, cookie-based auth is:
Only use token auth if:
auth:sanctumOnce you understand this flow, authentication stops being scary.
For SPAs, yes. Less complexity, better security.
Yes. Add a role column and check it with middleware.
Yes. Just update domain settings and run npm run build.
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google
Kritim Yantra