dmytroshved liked a comment+100 XP
3mos ago
@dmytro_shved Furthermore with Sanctum you don't have to store the token, it's done automatically via Laravel internal mechanisms.
dmytroshved liked a comment+100 XP
4mos ago
dmytroshved wrote a reply+100 XP
4mos ago
Here is the summary of changes after all refactors and researches
Appreciate for help: @shivamyadav, @shahriar_shaon and StackOverflow
Key points to pay attention to
-
there's no reason to keep setUser and getCurrentUser separate from the store, a store is not just a global state but can contain related business logic.
-
there's supposed to be a global auth guard instead of per route beforeEnter, this is where logic takes place:
router.beforeEach(async (to, from) => {
if (from === START_LOCATION) {
const userStore = useUserStore();
await userStore.getUser();
}
})
-
any initialization logic can be run like that. It's convenient to declaratively mark routes that need auth with meta: { auth: true } with this guard and run auth logic only if to.meta.auth === true.
-
it needs to be taken into account that initial navigation starts before an application is mounted. In order to avoid possible race conditions, it can be:
await router.isReady()
app.mount('#app')
Code
main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import './style.css';
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
await router.isReady()
app.mount('#app')
router/index.js
import { createRouter, createWebHistory, START_LOCATION } from 'vue-router'
import LoginView from '@/views/auth/LoginView.vue'
import RegisterView from '@/views/auth/RegisterView.vue'
import ToursView from '@/views/ToursView.vue'
import { useUserStore } from '@/store/user.js'
const routes = [
{
name: 'Login',
path: '/login',
component: LoginView,
meta: { auth: false }, // for Unauthenticated users
},
{
name: 'Register',
path: '/register',
component: RegisterView,
meta: { auth: false }, // for Unauthenticated users
},
{
name: 'Tours',
path: '/tours',
component: ToursView,
meta: { auth: true }, // for Authenticated users
},
// ...
];
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore();
if (from === START_LOCATION) {
await userStore.getUser();
}
if ((to.name === 'Login' || to.name === 'Register') && userStore.isAuthorized) {
return next('/')
}
if (to.meta.auth && !userStore.isAuthorized) {
return next('/login')
}
next()
})
export async function routerPush(name, params) {
if (params === undefined) {
return await router.push({ name })
} else {
return await router.push({ name, params })
}
}
export default router
store/user.js
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
import { getCurrentUser } from '@/api/user.js'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isAuthorized = computed(() => !!user.value)
async function getUser() {
try {
const userData = await getCurrentUser()
userData ? user.value = userData : user.value = null
console.log('Logged in: ', user.value?.name)
} catch (error) {
console.log('Error: ', error)
return error
}
}
function updateUser(userData) {
userData ? user.value = userData : user.value = null
}
return {
user,
isAuthorized,
getUser,
updateUser
}
})
Usage
For example here is how my LoginView.vue script looks like
LoginView.vue
<script setup>
import GuestLayout from '@/layouts/GuestLayout.vue'
import { reactive, ref } from 'vue'
import { login } from '@/api/auth.js'
import { routerPush } from '@/router/index.js'
import { useUserStore } from '@/store/user.js'
const form = reactive({
email: '',
password: '',
})
const { updateUser } = useUserStore()
const errorMessage = ref('')
async function onLogin(form) {
errorMessage.value = ''
try {
const userData = await login(form)
updateUser(userData)
await routerPush('Dashboard')
}
catch (error) {
errorMessage.value =
error.response?.data?.message || 'Something went wrong'
}
}
</script>
dmytroshved wrote a reply+100 XP
4mos ago
dmytroshved liked a comment+100 XP
4mos ago
The issue happens because the router is creating a new Pinia store instance when you call useUserStore() inside the router guard. This latest instance does not contain the authenticated user, so isAuthorized always shows false.
router/index.js
import { useUserStore } from '@/store/user'
import pinia from '@/store'
router.beforeEach((to, from, next) => {
const userStore = useUserStore(pinia)
if ((to.name === 'login' || to.name === 'register') && userStore.isAuthorized) {
return next('/')
}
next()
})
dmytroshved liked a comment+100 XP
4mos ago
Try this you are mounting the router earlier.
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import setUser from '@/plugins/set-user.js'
async function bootstrap() {
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
// Wait until user is loaded BEFORE router runs guards
await setUser(pinia)
app.use(router)
app.mount('#app')
}
dmytroshved wrote a reply+100 XP
4mos ago
It works only if I have authenticated user, if his session burns out I see only blank page and message that GET /user returns 401 Atuh error from backend, which means setUser failed
I added try..catch so application can mount even if user didn't found
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import './style.css';
import setUser from '@/plugins/set-user.js'
async function bootstrap() {
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
try{
// Wait until user is loaded BEFORE router runs guards
await setUser(pinia)
} catch (error){
console.log('Unauthorized')
}
app.use(router)
app.mount('#app')
}
bootstrap()
What you think?
dmytroshved started a new conversation+100 XP
4mos ago
Hi
I need to restrict access for authenticated users to the /login and /register routes
My Pinia Storage user.js contains a computed property isAuthorized. However when I try to use this variable in my router/index.js I see it always has a false instead of true.
Also F12 -> Vue -> Pinia shows isAuthorized = true, but in code it shows as false
I also have a plugin set-user that takes the current authenticated user from backend and saves him to the Pinia Store user.js
store/user.js
import { computed, ref } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isAuthorized = computed(() => !!user.value)
function updateUser(userData) {
userData ? user.value = userData : user.value = null
}
return {
user,
isAuthorized,
updateUser
}
})
router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import LoginView from '@/views/auth/LoginView.vue'
import RegisterView from '@/views/auth/RegisterView.vue'
import { useUserStore } from '@/store/user.js'
const routes = [
{
name: 'Login',
path: '/login',
component: LoginView,
beforeEnter: (to) => {
const store = useUserStore()
if (store.isAuthorized) {
return '/'
}
},
},
{
name: 'Register',
path: '/register',
component: RegisterView,
beforeEnter: (to, from, next) => {
const user = useUserStore()
user.isAuthorized.value ? next('/') : next()
},
},
// ...
];
const router = createRouter({
history: createWebHistory(),
routes
})
export async function routerPush(name, params) {
if (params === undefined) {
return await router.push({ name })
} else {
return await router.push({ name, params })
}
}
export default router
plugins/set-user.js
import { getCurrentUser } from '@/api/user.js'
import { useUserStore } from '@/store/user.js'
export default async function setUser(pinia) {
const { updateUser } = useUserStore(pinia)
const userData = await getCurrentUser()
userData ? updateUser(userData) : updateUser(null)
}
main.js
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import './style.css';
import setUser from '@/plugins/set-user.js'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(router)
setUser(pinia)
app.mount('#app')
Would be grateful for your help
Best regards
dmytroshved liked a comment+100 XP
4mos ago
@dmytro_shved Doing authorisation checks on the client is never secure. This is the entire reason Sanctum and its cookie-based authentication was created.
dmytroshved started a new conversation+100 XP
4mos ago
Hey everyone
I am analyzing the code of RealWorld Example (Vue 3 + Pinia) and I'm trying to understand the author's logic for storing the user and token.
Below is a summary of what I observed.
1. User storage in localStorage
The app uses a custom Storage class to store the user object (from what I understood including the token) in localStorage:
// src/utils/storage.ts
export const userStorage = new Storage<User>('user')
2. Global access via Pinia
The user is exposed through a Pinia store:
// src/store/user.ts
const user = ref(userStorage.get())
const isAuthorized = computed(() => !!user.value)
function updateUser(userData?: User | null) {
if (userData) {
userStorage.set(userData)
api.setSecurityData(userData.token)
user.value = userData
} else {
userStorage.remove()
api.setSecurityData(null)
user.value = null
}
}
Notes:
-
user.valueprovides reactive access to the user data. -
isAuthorizedis a computed property based on whether a user exists (boolean true/false). -
updateUserupdates both Pinia andlocalStoragewith edited user data.
3. Token storage in JavaScript memory
The HttpClient class, used for API requests, has a private securedData property that holds the token.
// src/services/api.ts
export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = "https://api.realworld.show/api";
private securityData: SecurityDataType | null = null; // this line
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
private abortControllers = new Map<CancelToken, AbortController>();
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
fetch(...fetchParams);
//...
public setSecurityData = (data: SecurityDataType | null) => {
this.securityData = data;
};
Source code of api.ts (the file is pretty big (800+ lines of code), so you can find it by: Ctrl + F and typing HttpClient.)
During login, the token is stored both in localStorage (via userStorage) and in JavaScript memory (securityData):
// src/store/user.ts
updateUser(result.data.user) // sets token in both places
4. Token recovery after reload
On application start, a plugin (set-authorization-token.ts) reads the token from localStorage and writes it to HttpClient.securityData
// src/plugins/set-authorization-token.ts
import { api } from 'src/services'
import { userStorage } from 'src/store/user'
export default function setAuthorizationToken(): void {
const token = userStorage.get()?.token
if (token !== undefined)
api.setSecurityData(token)
}
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import registerGlobalComponents from './plugins/global-components'
import setAuthorizationToken from './plugins/set-authorization-token'
import { router } from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
setAuthorizationToken()
registerGlobalComponents(app)
app.mount('#app')
Authentication flow:
-
The user visits
Login.vueand logs in viaapi.users.login, which returns a user object (with token?). -
updateUser(user)saves user inuser.tsanduserStorage, and copies token intoHttpClient.securityData. -
On page reload,
setAuthorizationToken()reads the token fromuserStorageand restores it toHttpClient.securityData. -
API requests use
HttpClient.securityDatain the Authorization header:
// src/services/index.ts
import { CONFIG } from 'src/config'
import type { GenericErrorModel, HttpResponse } from 'src/services/api'
import { Api, ContentType } from 'src/services/api'
export const limit = 10
export const api = new Api({
baseUrl: `${CONFIG.API_HOST}/api`,
securityWorker: token => token ? { headers: { Authorization: `Token ${String(token)}` } } : {},
baseApiParams: {
headers: {
'content-type': ContentType.Json,
},
format: 'json',
},
})
// ...
My questions
- If the token is already in
userStorage, why does the author also store it in JavaScript memory (securityData)? - Is this approach secure? I understand that storing tokens in
userStorageis vulnerable to XSS attacks, but since this repo has many stars I still don't understand the logic. - How is the token actually protected here, given that it persists in
userStorageand only copied into JavaScript memory on initialization?
I'm trying to understand the rationale and security implications of this design. Is this a common pattern? Are there better practices for SPA token management?
Would be grateful for your help
dmytroshved was awarded Best Answer+1000 XP
5mos ago
I finally noticed where I had a mistake
My session table didn't have an user_id because I was using UUID for my users, and session table wasn't properly configured for such UUID
Fixed session migration (notice that session table included in the create_users_table migration in laravel 12)
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignUuid('user_id')->nullable()->index(); // fixed line to use UUID
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
EDIT
I also wanna to leave the important configuration places you need to check if you're getting errors
Config settings to check:
backend
1. Check your SPA (e.g. VueJS app) and API (e.g. Laravel) are on the same top level domain
For example:
api - localhost:8000
spa - localhost:5173
(notice: localhost = 127.0.0.1:8000 is a top-level domain)
2. Ensure to properly configure .env
APP_URL=http://localhost:8000
FRONTEND_URL=http://localhost:5173
SANCTUM_STATEFUL_DOMAINS=localhost:5173
SESSION_DOMAIN=localhost
3. In your bootstrap/app.php in the withMiddleware
$middleware->statefulApi();
4. Ensure config/cors.php is properly configured
Notice: If you don't have this file run: php artisan config:publish cors
'paths' => ['*'], // optionally config this line (see usual config below)
'allowed_methods' => ['*'], // EXAMINE THIS LINE
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], // v
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true, // AND EXAMINE THIS LINE (throws CORS error if false)
usually you can see this kind of config for paths in config/cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie', '/login', '/logout'],
5. Session and SESSION_DRIVER
In your .env file check SESSION_DRIVER=database (it's a default value and you don't need to touch it)
Check user_id is set after registration/login in your sessions table
Reminder:
-
You don't need to call the /sanctum/csrf-cookie in logout, because after yout logged in or registered the frontend should already have the token
-
Authentication routes (login, register, logout) should be in routes/api.php not routes/web.php
frontend
1. Check your axiosClient (if you're using one, or chec your axios are using proper settings)
Example of my src/axios.js
import axios from 'axios'
import router from '@/router/index.js'
const axiosClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // check how to configure this line below
withCredentials: true, // THIS LINE IS IMPORTANT
withXSRFToken: true, // THIS LINE IS ALSO IMPORTANT
})
axiosClient.interceptors.response.use( (response) =>{
return response;
}, error => {
if (error.response && error.response.status === 401){
router.push({name: 'Login'})
}
throw error;
})
export default axiosClient
in your .env (notice: its a SPA's .env file, not an API's) check you have your API base URL, for example
VITE_API_BASE_URL=http://localhost:8000
2. Check your routes are sending the GET /sanctum/csrf-cookie request before POST, PUT, PATCH, DELETE
example: login request
const form = reactive({
email: '',
password: '',
})
const errorMessage = ref('')
function submit() {
// THIS LINE IS IMPORTANT
axiosClient.get('/sanctum/csrf-cookie').then(response => {
axiosClient.post('/login', form)
.then(response => {
router.push({name: 'Home'})
})
.catch(error => {
errorMessage.value = error.response.data.message;
})
});
}
Reminder:
- You shouldn't call GET /sanctum/csrf-cookie before EVERY POST, PUT, PATCH, DELETE requests
Remember this: You only need to call the endpoint once at the start of the session.
Hope it'll provide clear understanding where you can find your error
Really appreciate the support I got from:
dmytroshved wrote a reply+100 XP
5mos ago
I finally noticed where I had a mistake
My session table didn't have an user_id because I was using UUID for my users, and session table wasn't properly configured for such UUID
Fixed session migration (notice that session table included in the create_users_table migration in laravel 12)
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignUuid('user_id')->nullable()->index(); // fixed line to use UUID
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
EDIT
I also wanna to leave the important configuration places you need to check if you're getting errors
Config settings to check:
backend
1. Check your SPA (e.g. VueJS app) and API (e.g. Laravel) are on the same top level domain
For example:
api - localhost:8000
spa - localhost:5173
(notice: localhost = 127.0.0.1:8000 is a top-level domain)
2. Ensure to properly configure .env
APP_URL=http://localhost:8000
FRONTEND_URL=http://localhost:5173
SANCTUM_STATEFUL_DOMAINS=localhost:5173
SESSION_DOMAIN=localhost
3. In your bootstrap/app.php in the withMiddleware
$middleware->statefulApi();
4. Ensure config/cors.php is properly configured
Notice: If you don't have this file run: php artisan config:publish cors
'paths' => ['*'], // optionally config this line (see usual config below)
'allowed_methods' => ['*'], // EXAMINE THIS LINE
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], // v
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true, // AND EXAMINE THIS LINE (throws CORS error if false)
usually you can see this kind of config for paths in config/cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie', '/login', '/logout'],
5. Session and SESSION_DRIVER
In your .env file check SESSION_DRIVER=database (it's a default value and you don't need to touch it)
Check user_id is set after registration/login in your sessions table
Reminder:
-
You don't need to call the /sanctum/csrf-cookie in logout, because after yout logged in or registered the frontend should already have the token
-
Authentication routes (login, register, logout) should be in routes/api.php not routes/web.php
frontend
1. Check your axiosClient (if you're using one, or chec your axios are using proper settings)
Example of my src/axios.js
import axios from 'axios'
import router from '@/router/index.js'
const axiosClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // check how to configure this line below
withCredentials: true, // THIS LINE IS IMPORTANT
withXSRFToken: true, // THIS LINE IS ALSO IMPORTANT
})
axiosClient.interceptors.response.use( (response) =>{
return response;
}, error => {
if (error.response && error.response.status === 401){
router.push({name: 'Login'})
}
throw error;
})
export default axiosClient
in your .env (notice: its a SPA's .env file, not an API's) check you have your API base URL, for example
VITE_API_BASE_URL=http://localhost:8000
2. Check your routes are sending the GET /sanctum/csrf-cookie request before POST, PUT, PATCH, DELETE
example: login request
const form = reactive({
email: '',
password: '',
})
const errorMessage = ref('')
function submit() {
// THIS LINE IS IMPORTANT
axiosClient.get('/sanctum/csrf-cookie').then(response => {
axiosClient.post('/login', form)
.then(response => {
router.push({name: 'Home'})
})
.catch(error => {
errorMessage.value = error.response.data.message;
})
});
}
Reminder:
- You shouldn't call GET /sanctum/csrf-cookie before EVERY POST, PUT, PATCH, DELETE requests
Remember this: You only need to call the endpoint once at the start of the session.
Hope it'll provide clear understanding where you can find your error
Really appreciate the support I got from:
dmytroshved liked a comment+100 XP
5mos ago
I'm not sure I get it right, so should I remove /sanctum/csrf-token call in the logout?
Yes. It shouldn't be needed, because the front end should already have the token: you only need to call the endpoint once at the start of the session. If it doesn't work, something's wrong.
Am I making a mistake placing my auth routes in routes/web.php instead of routes/api.php?
Yes. When you're using Laravel as a stateful API, those routes should be under api.php. Also, make sure to apply the auth:sanctum middleware to protected routes, e.g. the logout route.
https://laravel.com/docs/12.x/sanctum#protecting-mobile-api-routes
dmytroshved wrote a reply+100 XP
5mos ago
Okay I have an interesting question
Should I configure my SESSION_DRIVER to cookie instead of default DATABASE?
Because when I changed this thing my spa is not throwing 419 and everything working well!
Example
+ SESSION_DRIVER=cookie
- SESSION_DRIVER=database
EDIT
I also got a new idea how to fix it while keeping my SESSION_DRIVER=database
In my project it was necessary to use UUID for users (I know about their disadvantage, but it wasn't mine idea)
what if in my session table there was no user_id and thus I got an 419 because application didn't see users id in auth session?
dmytroshved wrote a reply+100 XP
5mos ago
dmytroshved wrote a reply+100 XP
5mos ago
I'm not sure I get it right, so should I remove /sanctum/csrf-token call in the logout?
Are you doing anything unusual related to origins, like redirecting to a different subdomain or port after logging in, and then attempting to log out from there?
No, after login I am redirecting to the Home page, nothing fancy here
function submit() {
axiosClient.get('/sanctum/csrf-cookie').then(response => {
axiosClient.post('/login', data.value)
.then(response => {
router.push({name: 'Home'})
})
.catch(error => {
console.log(error.response)
errorMessage.value = error.response.data.message;
})
});
}
EDIT
Am I making a mistake placing my auth routes in routes/web.php instead of routes/api.php?
Route::post('login', LoginController::class);
Route::post('register', RegisterController::class);
Route::post('logout', LogoutController::class);
dmytroshved liked a comment+100 XP
5mos ago
POST endpoints require a CSRF token, of course. The token is included in the XSRF-TOKEN cookie you receive in responses, and Axios automatically includes it as a request header.
The real question is why the token stops working after register or login. You shouldn't need to call the /sanctum/csrf-cookie endpoint before every POST request. Once should be enough. Even if the token was rotated during login (which it is not), the new token cookie would be included in the login response.
There must be something wrong with your setup.
Edit. Are you doing anything unusual related to origins, like redirecting to a different subdomain or port after logging in, and then attempting to log out from there?
dmytroshved wrote a reply+100 XP
5mos ago
Some good guy helped me with that, so I am leaving the answer here
You would still need to call the /sanctum/csrf-cookie as your /logout endpoint is a POST request.
All of laravel's POST,PUT, PATCH, DELETE requests require you to have the csrf cookie as mentioned here in the docs.
Fixed logout function
function logout() {
axiosClient.get('/sanctum/csrf-cookie').then(response => {
axiosClient.post('/logout')
});
}
dmytroshved wrote a reply+100 XP
5mos ago
The error happens in the request to /logout, I mean when I try to logout, I hit the button, it emits the logout function and then I see 419 in the browser, however preflight request was successfull
Are you regenerating the CSRF token at the end of a login / register, like you're doing in logout?
Im not sure, I am regenerating the session in the login, but at the register I don't
RegisterController
public function __invoke(RegisterRequest $request)
{
$user = User::create($request->validated());
Auth::login($user);
return (new UserResource($user));
}
LoginController
public function __invoke(LoginRequest $request)
{
if (Auth::attempt($request->validated())){
$request->session()->regenerate();
return (new UserResource(Auth::user()));
}else{
return response()->json([
'message' => 'The provided credentials are incorrect'
], 422);
}
}
dmytroshved liked a comment+100 XP
5mos ago
When is the 419 thrown? Is it on the logout request itself, or some other action after that? Check the network tab in the browser's dev tools.
Are you regenerating the CSRF token at the end of a login / register, like you're doing in logout?
Regardless, Laravel should include a fresh XSRF-TOKEN cookie in the login or register response, and Axios should pick that up. If that's not working, further debugging is needed.
dmytroshved started a new conversation+100 XP
5mos ago
Hey everyone
I am struggling with annoying 419 error trying to logout. The login and register are working fine. My api and spa are on the same top-level domain, but different ports
api - localhost:8000 spa (vue) - localhost:5173
After hours of debugging and changing the different settings I still get the 419
backend:
env
APP_URL=http://localhost:8000
FRONTEND_URL=http://localhost:5173
SANCTUM_STATEFUL_DOMAINS=localhost:5173
SESSION_DOMAIN=localhost
bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
apiPrefix: 'api/v1',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'role' => RoleMiddleware::class,
]);
$middleware->api(prepend: [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
]);
$middleware->statefulApi();
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();
cors
'paths' => ['*'],
'allowed_methods' => ['*'],
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
sanctum
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,localhost:5173,127.0.0.1,127.0.0.1:8000,::1',
Sanctum::currentApplicationUrlWithPort(),
// Sanctum::currentRequestHost(),
))),
authentication routes in routes/web.php
Route::post('login', LoginController::class);
Route::post('register', RegisterController::class);
Route::post('logout', LogoutController::class);
LogoutController.php
public function __invoke(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return response()->json([
'message' => 'Logged out'
]);
}
frontend:
axios.js
import axios from 'axios'
import router from '@/router/index.js'
const axiosClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
withCredentials: true,
withXSRFToken: true,
})
axiosClient.interceptors.response.use( (response) =>{
return response;
}, error => {
if (error.response && error.response.status === 401){
router.push({name: 'Login'})
}
throw error;
})
export default axiosClient
env
VITE_API_BASE_URL=http://localhost:8000
logout function
function logout() {
axiosClient.post('/logout')
.then((response) => {
router.push({name: 'Login'})
})
}
for reference
login function
const data = ref({
email: '',
password: '',
})
const errorMessage = ref('')
function submit() {
axiosClient.get('/sanctum/csrf-cookie').then(response => {
axiosClient.post('/login', data.value)
.then(response => {
router.push({name: 'Home'})
})
.catch(error => {
console.log(error.response)
errorMessage.value = error.response.data.message;
})
});
}
How I tried to debug logout
- login
- check cookies are set
- use logout (error shows)
Would be grateful for your help
Best regards
dmytroshved wrote a reply+100 XP
5mos ago
I have a question, what do you mean by
... blocks the address from being used to create any new ones
I mean how should I block that email? Should I create separate table for bounced_emails and then create vlidation rule that checks if email is in the black list? Or there is another way to block email?