dmytroshved liked a comment+100 XP
5mos 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
5mos ago
Yeah, I have also struggled with this while learning. So, I remembered a little bit. I'am happy that it helps you.
As for now I have shifted towards the inertia so I just forgotten some amazing tips which I had learnt.
dmytroshved wrote a reply+100 XP
5mos 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
5mos ago
Appreciate your help :)
dmytroshved liked a comment+100 XP
5mos 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
5mos 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
5mos 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
5mos 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