vue-router Navigation Guard does not cancle navigation - vue.js

Before accessing any page, except login and register; I want to authenticate the user with Navigation Guards.
Following you can see my code for the vue-router. The "here" gets logged, but the navigation is not cancelled in the line afterwards. It is still possible that if the user is not authenticated that he can access the /me-route
my router-file:
import { createRouter, createWebHistory } from "vue-router";
import axios from "axios";
import HomeView from "../views/HomeView.vue";
import RegisterView from "../views/RegisterView.vue";
import LoginView from "../views/LoginView.vue";
import MeHomeView from "../views/MeHomeView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/register",
name: "register",
component: RegisterView,
},
{
path: "/login",
name: "login",
component: LoginView,
},
{
path: "/me",
name: "me",
component: MeHomeView,
},
],
});
router.beforeEach((to, from) => {
if(to.name !== 'login' && to.name !== 'register') {
console.log(to.name);
axios.post("http://localhost:4000/authenticate/", {accessToken: localStorage.getItem("accessToken")})
.then(message => {
console.log(message.data.code);
if(message.data.code === 200) {
} else {
console.log("here");
return false;
}
})
.catch(error => {
console.log(error);
return false;
})
}
})
export default router;

Navigation guards support promises in Vue Router 4. The problem is that promise chain is broken, and return false doesn't affect anything. As a rule of thumb, each promise needs to be chained.
It should be:
return axios.post(...)
The same function can be written in more readable and less error-prone way with async..await.

Related

Trouble getting user data inside vue-router from composition

I'm very new to vue.js, I am currently working on my final assignment for university.
I'm trying to get information of my user into my router, this works fine on my usual pages/components, but the techniques used on those files don't seem to work here. I've tried reading through some of the documention for router and composition, but I can't seem to figure out where I'm going wrong. This is my latest attempt as earlier I was not using setup() and getting the error; inject() can only be used inside setup() or functional components.
The problem is occuring with "useAuth," I'm not getting any data, my console.log(isAdmin) is displaying 'undefined,' this should be a boolean true/false.
Router code:
import { createWebHistory, createRouter } from "vue-router";
import Dashboard from "../pages/DashboardSDT.vue";
import Events from "../pages/EventsSDT.vue";
import Results from "../pages/ResultsSDT.vue";
import Admin from "../pages/AdminSDT.vue";
import Settings from "../pages/SettingsSDT.vue";
import Login from "../pages/LoginSDT.vue";
import Register from "../pages/RegisterSDT.vue";
import { getAuth } from "firebase/auth";
import useAuth from "../composition/useAuth";
const routes = [
{
path: "/",
name: "Dashboard",
component: Dashboard
},
{
path: "/Events",
name: "Events",
component: Events
},
{
path: "/Results",
name: "Results",
component: Results
},
{
path: "/Admin",
name: "Admin",
component: Admin,
meta: { onlyAdminUser: true }
},
{
path: "/Settings",
name: "Settings",
component: Settings,
meta: { onlyAuthUser: true }
},
{
path: "/Login",
name: "Login",
component: Login,
meta: { onlyGuestUser: true }
},
{
path: "/Register",
name: "Register",
component: Register,
meta: { onlyGuestUser: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, _, next) => {
const isAuth = !!getAuth().currentUser;
const isAdmin = useAuth.admin;
console.log(isAdmin)
if (to.meta.onlyAuthUser) {
if (isAuth) {
next()
} else {
next({ name: "Login" })
}
// } else if(to.meta.onlyAdminUser) {
// if(isAdmin) {
// next()
// }
// else {
// next({name: "BasicUser"})
// }
} else if (to.meta.onlyGuestUser) {
if (isAuth) {
next({ name: "Dashboard" })
} else {
next()
}
} else {
next()
}
})
export default {
setup() {
return {
...useAuth()
}
},
...router
}
useAuth code:
import { useStore } from 'vuex'
import { computed } from 'vue'
export default function useAuth() {
const store = useStore();
const { state } = store;
const error = computed(() => state.user.auth.error);
const isProcessing = computed(() => state.user.auth.isProcessing);
const isAuthenticated = computed(() => store.getters["user/isAuthenticated"]);
const user = computed(() => state.user.data);
const admin = computed(() => state.user.data.admin);
return {
error,
isProcessing,
isAuthenticated,
user,
admin
}
}
vue-router's index file is not like a vue component file and does not have a setup() function. I've never tried but it's unlikely you can use composable functions either, especially when using vue composition API functions like computed()
You can however import the vuex store and access all it's state, getters, etc. like you want.
import store from '/store/index.js'; // or wherever your store lives
Then inside your router guard
const isAuthenticated = store.getters["user/isAuthenticated"];
const isProcessing = store.state.user.auth.isProcessing
// ...etc

Vue 3 router: props and query not working on beforeEach navigation guard

Using Vue 3 / Vue Router 4: I'm trying to implement a login screen that redirects to the requested deep link after login. But any prop or query I add to the navigation guard (so I can pass the requested URL to the login component) isn't visible to the login component. Here's the relevant code:
// Router
import { createRouter, createWebHistory } from "vue-router";
import Login from "#/views/Login.vue";
import Header from "#/components/Header.vue";
const routes = [
{
path: "/",
name: "Login",
components: {
default: Login,
Header: Header,
},
props: {
Header: { showMenu: false },
},
meta: { requiresAuth: false },
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
router.beforeEach((to) => {
if (to.meta.requiresAuth && !router.app.user.isAuthenticated()) {
return { name: "Login", props: { default: { target: to.name } } };
}
});
// Login.vue
<script>
export default {
name: "Login",
props: {
target: {
type: String,
default: "Home",
},
},
</script>
The target property remains at the default value no matter which named route I try to request. Nor does passing the value through the query string appear to work. I'm able to pass properties to components in the route definitions themselves without incident, it's just the navigation guard function that causes problems. What am I missing?
I might be missing something but the code you posted throws errors for me and the way you handle the navigation guard seems a bit strange (you should always have at least one next() in the guard).
Anyway, if I understand correctly and if you insist on using the same route for Header and Login pages, you could do this in your SFC and remove the guard from router file:
// App.vue
<template>
<router-view :name="page" />
</template>
<script>
export default {
data() {
return {
user: null
}
},
computed: {
page() {
if (this.$route.meta.requiresAuth && !this.user?.isAuthenticated()) {
return 'Login'
}
return undefined
}
}
created() {
this.user = <your_method_to_get_user>
}
}
</script>
// Router
import { createRouter, createWebHistory } from "vue-router";
import Login from "#/views/Login.vue";
import Header from "#/components/Header.vue";
const routes = [
{
path: "/",
name: "Login",
components: {
default: Login,
Header: Header,
},
props: {
Header: { showMenu: false }, // showMenu prop will beaccessible in Header
},
meta: { requiresAuth: false },
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
But I'd suggest using 2 different routes for login and header and redirecting from header to login if user not logged in and vice versa via the next() as described here.

Vue router - navigation guard is skipped when using router.push()

I have a Vue SPA with i18n and some views that require authentication via navigation guard.
When i am not authenticated and go to url via my browser:
examplepage.com/en/lockedpage
i get redirected to:
examplepage.com/en/login
which is good, however when i click a button that runs:
#click="$router.push(`/${$i18n.locale}/lockedpage`)"
i get in to the page even if i am not authenticated.
I want to get redirected to the login page if not authenticated
this is my router.js:
import Vue from 'vue';
import Router from 'vue-router';
import Home2 from './views/Home2.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';
import ErrorLanding from './views/ErrorLanding.vue'
import Root from "./Root"
import i18n, { loadLocaleMessagesAsync } from "#/i18n"
import {
setDocumentLang
} from "#/util/i18n/document"
Vue.use(Router);
const { locale } = i18n
export const router = new Router({
mode: 'history',
base: '/',
routes: [
{
path: '/',
redirect: locale
},
{
path: "/:locale",
component: Root,
children: [
{
name: 'Home',
path: '',
component: Home2,
},
{
name: 'Home2',
path: '/',
component: Home2,
},
{
name: 'Login',
path: 'login',
component: Login,
},
{
path: 'register',
component: Register,
},
{
path: 'lockedpage',
name: 'lockedpage',
webpackChunkName: "lockedpage",
meta: {authRequired: true},
component: () => import('./views/LockedPage.vue')
},
{
path: '*',
component: ErrorLanding,
name: 'NotFound'
}
]
}
],
router.beforeEach((to, from, next) => {
if (to.params.locale === from.params.locale) {
next()
return
}
const { locale } = to.params
loadLocaleMessagesAsync(locale).then(() => {
setDocumentLang(locale)
const publicPages = [ `/`, `/${locale}`, `/${locale}/`];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
const redirect = to.path;
if (authRequired && loggedIn === null) {
if(to.meta.authRequired === false) {
next();
}
else
next({ name: 'Login', query: { redirect: redirect } });
} else {
next();
}
})
next()
});
Why does my navigationguard skip when using router.push() ?
This issue started after adding i18n, with localeredirect. so all routes comes after a locale for example: /en/.../
As Estus points out in the comments, the issue is that the first thing you're checking for is if the locales match, and if they do you're calling next() and sending the user to the page.
As outlined here:
Make sure that the next function is called exactly once in any given pass through the navigation guard. It can appear more than once, but only if the logical paths have no overlap, otherwise the hook will never be resolved or produce errors.
If you need to keep the locale check between the to and from pages, you could do something like:
if (to.params.locale === from.params.locale && loggedIn) {
next()
return
}
which will check if the loggedIn variable is truthy before pushing the user to the page they're trying to navigate to.
I believe just removing the if statement that checks if the locales match would also work.

Laravel 5.6 + vue.js app router + laravel passport

I am testing Laravel5.6+Vue app, I have activated Laravel Auth and Passport.
My project:
API uses Laravel Passport.
Web Laravel Auth is active.
Vue App
Since Laravel Auth is active, it seems like Auth triggers and validate user session, if he is login or not and redirect to Auth /login route.
When I disable auth routes in web.php, error occurs.
// Auth::routes();
Error
InvalidArgumentException
Route [login] not defined.
Since I am using Vue app and Laravel API with Passport, how can avoid Laravel web Auth to validate Vue app?
web.php
Auth::routes();
Route::get('{any}', 'HomeController#index')->name('home')->where('any','.*');
api.php
Route::group([
'prefix' => 'auth'
], function () {
Route::group(['middleware' => 'auth:api'], function() {
Route::get('logout', 'API\AuthController#logout');
Route::get('user', 'API\AuthController#user');
});
});
Route::group(['middleware' => 'auth:api'], function() {
Route::get('/application/all', 'ApplicationController#index');
});
Route::post('/auth/login', 'API\AuthController#login');
Route::post('/auth/register', 'API\AuthController#register');
Vue
routes.js
import Home from './components/Home.vue';
import Login from './components/auth/Login.vue';
import Logout from './components/auth/Logout.vue';
import Profile from './components/auth/Profile.vue';
import Register from './components/auth/Register.vue';
import ApplicationList from './components/content-app/ApplicationList.vue';
export const routes = [
{
path: '/',
component: Home,
meta: {
requiresVisitor: true,
}
},
{
name: 'user-login',
path: '/user/login',
component: Login,
meta: {
requiresVisitor: true,
}
},
{
name: 'user-register',
path: '/user/register',
component: Register,
meta: {
requiresVisitor: true,
}
},
{
name: 'user-profile',
path: '/user/profile',
component: Profile,
meta: {
requiresAuth: true,
}
},
{
path: '/logout',
component: Logout,
meta: {
requiresAuth: true,
}
},
{
path: '/content/application-list',
component: ApplicationList,
meta: {
requiresAuth: true,
}
}
];
app.js
require('./bootstrap');
import Vue from 'vue';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import axios from 'axios'
import {routes} from './routes';
import StoreDate from './store';
import MainApp from './components/MailApp.vue';
Vue.use(VueRouter);
Vue.use(Vuex);
const store = new Vuex.Store(StoreDate);
const router = new VueRouter({
routes,
mode: 'history'
});
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!store.getters.loggedIn) {
next({
path: '/login',
query: { auth: 'unauthenticated' } //query: { redirect: to.fullPath }
})
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresVisitor)) {
if (store.getters.loggedIn && (to.path == '/login' || to.path == '/register')) {
next({
path: '/profile',
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
const app = new Vue({
el: '#app',
router,
store,
components: {
MainApp
}
});
///////// OVER WRITING JS
$('.navbar-nav>li>a').on('click', function(){
$('.navbar-collapse').collapse('hide');
});
$('.navbar-nav>navbar-brand').on('click', function(){
$('.navbar-collapse').collapse('hide');
});

Vue router navigation gaurd from within the component

I use vuex from centralized state management
in my vuex store.js i store the login status as a boolean value like below
export const store = new Vuex.Store({
state: {
loggedIn: false,
userName: 'Guest',
error: {
is: false,
errorMessage: ''
}
},
getters: {
g_loginStatus: state => {
return state.loggedIn;
},
g_userName: state => {
return state.userName;
},
g_error: state => {
return state.error;
}
}
)};
When the user logs in i set the loginstatus to true and remove the login button and replace it with log out button
everything works fine but the problem is when the user is logged in and if i directly enter the path to login component in the search bar i am able to navigate to login page again
I want to preent this behaviour
If the uses is logged in and searches for the path to loginpage in the searchbar he must be redirected to home page
I have tried using beforeRouteEnter in the login component
But we do not have acess to the this instance since the component is not yet loaded
So how can i check for login status from my store
my script in login.vue
script>
export default{
data(){
return{
email: '',
password: ''
};
},
methods: {
loginUser(){
this.$store.dispatch('a_logInUser', {e: this.email, p: this.password}).then(() =>{
this.$router.replace('/statuses');
});
}
},
beforeRouteEnter (to, from, next) {
next(vm => {
if(vm.$store.getters.g_loginStatus === true){
//what shall i do here
}
})
}
}
It is much better to put the navigation guards in routes not in pages/components and call the state getters on route file.
// /router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import store from '../store'
// Protected Pages
import Dashboard from '#/views/dashboard'
// Public Pages
import Dashboard from '#/views/login'
Vue.use(Router)
// If you are not logged-in you will be redirected to login page
const ifNotAuthenticated = (to, from, next) => {
if (!store.getters.loggedIn) {
next()
return
}
next('/') // home or dashboard
}
// If you are logged-in/authenticated you will be redirected to home/dashboard page
const ifAuthenticated = (to, from, next) => {
if (store.getters.loggedIn) {
next()
return
}
next('/login')
}
const router = new Router({
mode: 'history',
linkActiveClass: 'open active',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
path: '/',
redirect: '/dashboard',
name: 'Home',
component: Full,
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: Dashboard,
beforeEnter: ifAuthenticated
},
]
},
{
path: '/login',
name: 'Login',
component: Login,
beforeEnter: ifNotAuthenticated
},
{
path: '*',
name: 'NotFound',
component: NotFound
}
]
})
export default router
You can also use vue-router-sync package to get the value of store values
You can redirect the user to the home page or some other relevant page:
mounted () {
if(vm.$store.getters.g_loginStatus === true){
this.$router('/')
}
}
beforeRouteEnter (to, from, next) {
next(vm => {
if(vm.$store.getters.g_loginStatus === true){
next('/')
}
})
}
From the docs:
next: Function: this function must be called to resolve the hook. The action depends on the arguments provided to next:
next(): move on to the next hook in the pipeline. If no hooks are left, the navigation is confirmed.
next(false): abort the current navigation. If the browser URL was changed (either manually by the user or via back button), it will be reset to that of the from route.
next('/') or next({ path: '/' }): redirect to a different location. The current navigation will be aborted and a new one will be started.