Search code examples
laravelvue.jsstate-managementpinia

redirect logged in user if they went to login page


Super beginner here, I just want to know where should I put navigation guards on my app. I have the following code from my backend/src/main.js

import { createApp, markRaw } from 'vue'
import { createPinia } from 'pinia'

import './axios'
import store from './store'
import router from './router' 
import './index.css'

import App from './App.vue'

const pinia = createPinia();

pinia.use(({ store }) => {
    store.router = markRaw(router);
})
const app = createApp(App)

app.use(router)
app.use(pinia)
app.use(store)
app.mount('#app')

backend/src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import Dashboard from '../views/Dashboard.vue'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import RequestPassword from '../views/RequestPassword.vue'
import ResetPassword from '../views/ResetPassword.vue'
import Register from '../views/Register.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/dashboard',
    name: 'dashboard',
    component: Dashboard

  },
  {
    path: '/login',
    name: 'login',
    component: Login
  },
  {
    path: '/forgot-password',
    name: 'requestPassword',
    component: RequestPassword,
  },
  {
    path: '/password-reset/:token',
    name: 'resetPassword',
    component: ResetPassword,
  },
  {
    path: '/register',
    name: 'register',
    component: Register
  },
];

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: routes,
  linkActiveClass: 'active'
})


export default router

and in backend/src/store/auth.js

import { defineStore } from 'pinia';
import axios from 'axios';

export const useAuthStore = defineStore("auth", {
    state: () => ({
        authUser: null,
        authErros: [],
        authStatus: null,
        isLoggedin: false,
    }),
    getters: {
        user: (state) => state.authUser,
        errors: (state) => state.authErros,
        status: (state) => state.authStatus,
        loggedin: (state) => state.isLoggedin,
    },
    actions: {
        async getToken() {
            await axios.get('/sanctum/csrf-cookie');
        },
        async getUser() {
            await this.getToken();
            const data = await axios.get('api/user');
            this.authUser = data.data;
        },
        async handleLogin (data) {
            this.authErros = [];
            await this.getToken();

            try{
                await axios.post('/login', {
                    email: data.email,
                    password: data.password
                });
                this.router.push("/dashboard");
            } catch(error){
                if(error.response.status === 422){
                    this.authErros = error.response.data.errors
                }
            }
        },
        async handleRegister (data)  {
            this.authErros = [];
            await this.getToken();

            try{
                await axios.post('/register', {
                    name: data.name,
                    email: data.email,
                    password: data.password,
                    password_confirmation: data.password_confirmation
                });
                this.router.push("/dashboard");
            } catch(error){
                if(error.response.status === 422) {
                    this.authErros = error.response.data.errors;
                }
            }
        },
        async handleLogout () {
            await axios.post('/logout');
            this.authUser = null;
        },
        async handleForgotPassword (email) {
            this.authErros = [];
            this.getToken();
            try {
                const response = await axios.post('/forgot-password', {
                    email: email
                });
                this.authStatus = response.data.status;
            } catch (error) {
                if(error.response.status === 422) {
                    this.authErros = error.response.data.errors;
                }
            }
        },
        async handleResetPassword (resetData) {
            this.authErros = [];
            try {
                const response = await axios.post('/reset-password', resetData);
                this.authStatus = response.data.status;
            } catch (error) {
                if(error.response.status === 422) {
                    this.authErros = error.response.data.errors;
                }
            }
        }
    }
})

I have searched everywhere and tried everything. Since I am using pinia not vuex for state management, I cannot get the solution that I want. Thanks for the help!


Solution

  • The way I like to do it is by using a navigation guard in the Login.vue itself. This can be done using In-Component Guards.

    Vue Router provides a navigation guard called beforeRouteEnter. It will fire before entering the route.

    The idea is that - when the user tries to enter the login page, before entering that page check whether the user is already logged in or not. If yes then abort the routing, else let the routing continue.

    It will look something like this:

    Options API:

    export default {
      components: {},
      props: {},
      beforeRouteEnter(to, from) {
        // Your code here
      },
      // other component options (like data, methods, etc)
    }
    

    Composition API:

    export default {
      components: {},
      props: {},
      beforeRouteEnter(to, from) {
        // Your code here
      },
      setup() {
        // other component logic
      }
    }
    

    Note: The beforeRouteUpdate and beforeRouteLeave guards have alternatives for both Options API and Composition API. But I am not sure whether beforeRouteEnter has an alternative for Composition API or not.

    If you are using the <script setup> syntax, then I should mention that there is no support for something like this in <script setup> yet (Reference). Instead, you may try a separate ordinary <script> block just for using this guard, like this:

    <script>
      export default {
        beforeRouteEnter(to, from) {
          // Your code here
        },
      }
    </script>
    
    <script setup>
       // Component logic here
    </script>
    

    I see that you are storing the login info in the pinia state. So you can just access that info in the beforeRouteEnter and then if the user is logged in then abort the navigation. You can abort navigation by returning false from the navigation guard. You can read about it here.

    Hence the code will be something like:

    export default {
      beforeRouteEnter(to, from) {
        const isLoggedIn = getLoginStateFromPinia();
    
        if (isLoggedIn) {
          return false;
        }
      },
    }
    

    If you don't want to use In-Component Guards, then you may want to check out Per-Route Guards. Per-Route Guards are to be added in your routes.js file where you are declaring all your routes. But the catch here is that you need to make sure that the pinia instance is created before the routes are resolved. Otherwise, you will get an error saying that somePiniaFunction() was called with no active Pinia instance. I would like to redirect you to this post for this purpose.

    You can read about the sequence of how these guards are called here and choose the one you need.