Router path not matched using Vue 3 + Vite - javascript

I am trying to make a Vue 3 project with Vite, that checks if user is logged in with AWS Cognito before entering to main page.
This is my router.js:
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import store from '../store';
import auth from '../auth';
import url from 'url'
function requireAuth(to, from, next) {
if (!auth.auth.isUserSignedIn()) {
next({
path: '/check-login',
query: { redirect: to.fullPath }
});
} else {
console.log("User already logged in");
if(store.getters.token==null) {
store.commit('setToken', auth.auth.signInUserSession.getIdToken().jwtToken);
}
next();
}
}
const routes = [
{
path: '/',
name: 'home',
component: HomeView,
beforeEnter: requireAuth
},
{
path: '/check-login', beforeEnter: () => {
auth.auth.getSession();
}
},
{
path: '/login*', beforeEnter: async () => {
let currUrl = window.location.href;
const queryObject = url.parse(currUrl,true);
const query = queryObject.hash.replace("#", "").split("&");
let id_token = "";
for(let i=0; i<query.length; i++){
if(query[i].indexOf("id_token")>-1) {
id_token = query[i];
id_token = id_token.replace("id_token=", "");
break;
}
}
if(id_token){
console.log("Setting token");
store.commit('setToken', id_token);
}
await auth.auth.parseCognitoWebResponse(currUrl);
goHome();
}
},
{
path: '/logout', beforeEnter: (to, from, next) => {
auth.logout();
next();
}
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
function goHome() {
router.push("/");
}
export default router
When I run the project using npm run dev the following warning appears:
[Vue Router warn]: No match found for location with path "/check-login?redirect=/"
I've tried to load manually other routes like login but same result. It seems that it doesn't recognize my routes (except Home), maybe because there is something wrong in the definition, but I can't find what it is...
Any idea?

Just found what was wrong. I had to include a component in every route to be detected well.
What I did is create a blank view with only a route-view inside.

Related

How should a user be redirected to another page after successful login in Vue 3

In a vue 3 application that uses composition api, I want to do one of 2 things based on if an authenticated user is verified or not. So if the user is not verified, send the user to the verification page. But if the user is verified, send the user to the dashboard.
To achieve this, I am using navigation guards in my router index.js like this
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/signin",
name: "signin",
component: () => import("../views/SignIn.vue"),
},
{
path: "/verify",
name: "verify",
component: () => import("../views/Verification.vue"),
meta: { needsAuth: true }
},
{
path: "/dashboard",
name: "dashboard",
component: () => import("../views/UserDashboard.vue"),
meta: { needsAuth: true },
beforeEnter: (to, from, next) => {
if (localStorage.getItem('verified') == "false") next('verify')
}
},
],
});
router.beforeEach(async (to, from, next) => {
if (to.meta.needsAuth && localStorage.getItem('accessToken') == null) next('signin')
else next()
})
export default router
Then in my sigin page script setup, I have the following code
import { ref } from "vue";
import { useRouter } from "vue-router";
import axios from "axios";
const router = useRouter();
const signin = ref({
email: null,
password: null,
});
const loginUser = async () => {
try {
const res = await axios.post(
"https://mydomain/api/login",
signin.value,
{
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
);
localStorage.setItem("accessToken", res.data.data.accessToken);
localStorage.setItem("verified", res.data.data.verified);
router.push({ name: "dashboard" });
} catch (error) {
alert(error.response.data.message);
}
};
Now when I login with an unverified user, I get redirected to the verification page as intended. But when I login with a verified user, I never get sent to the dashboard and instead the app remains on the signin view.
I can't seem to figure out what the problem is. How can I fix this routing issue?
Did you try with next() if user is verified:
beforeEnter: (to, from, next) => {
if (localStorage.getItem('verified') == "false") next('verify')
else next()
}

ExpressJS: res.redirect() does not work. How to redirect in single page app?

Issue:
I have a static single page application in which I press on a Submit input that sends a POST request using Axios from a Vue component to an Express server in which I handle the POST and try to redirect to another page, but res.redirect("/home") and/or res.sendFile(path.join(__dirname, "../dist/index.html")); do not redirect.
Expected behaviour:
I would like to redirect to /home after submit.
Everything else works just fine.
Server File:
const history = require('connect-history-api-fallback');
const path = require("path");
// serve the Vue Interface
const buildLocation = path.join(__dirname, "../dist");
app.use(express.static(buildLocation));
app.use(history({
disableDotRule: true,
verbose: true
}));
app.use(express.static(buildLocation));
app.get('/home', function(req, res) {
res.sendFile(path.join(__dirname, "../dist/index.html"));
});
app.post("/dhcpIP", function (req, res) {
// check if request body is empty
if (req.body.constructor === Object && Object.keys(req.body).length === 0)
return;
// doing things that work
res.redirect('/'); // this doesn't work
});
const http_server = http.createServer(app);
http_server.listen(http_port);
I build the Vue CLI application so that I can obtain the dist folder. In the dist folder I have an index.html that contains the whole application.
Vue component that sends the POST request:
<template>
<form action="javascript:void(0);" #submit="sendData" novalidate="true">
<input type="text"/>
<input type="password"/>
<input type="submit"/>
</form>
</template>
<script>
import axios from "axios";
const serverURL = location.origin;
const server = axios.create({ baseURL: serverURL });
export default {
name: "DHCP",
data() {
return {
visible: false,
ssid: "",
password: ""
};
},
methods: {
sendData: function() {
if (this.ssid === "" || this.password === "") return;
let currentUrl = window.location.pathname;
console.log("currentUrl:");
console.log(currentUrl);
server
.post(currentUrl, { ssid: this.ssid, password: this.password })
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
}
}
};
</script>
Router File
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
Vue.use(VueRouter);
const routes = [
{
path: "/",
redirect: "/home",
component: Home,
},
{
path: "/home",
name: "Home",
component: Home,
},
{
path: "/dhcpIP",
name: "DHCP",
component: () => import(/* webpackChunkName: "dhcpIP" */ "../views/DHCP.vue")
},
];
const router = new VueRouter({
mode: "history",
// base: process.env.BASE_URL,
routes,
});
export default router;
I have tried writing as little code as possible.
In your post route you are already returning something
app.post("/dhcpIP", function (req, res) {
// check if request body is empty
if (req.body.constructor === Object && Object.keys(req.body).length === 0)
return;
// doing things that work
res.redirect('/'); // this doesn't work
});
The return statement ends the function execution. That being said: You are returning "some things that work" and after that you use the res.redirect method, which is ignored because the function has already returned a value.
use router.push() instead, since you're not redirecting, but using the router
https://router.vuejs.org/guide/essentials/navigation.html
if what you want to achieve is to move away from the SPA by the redirect, then you can change the window.location depending on the server response, in that case, send a 301 or 302 from the server, and add an axios interceptor for it.
https://simplecheatsheet.com/axios-interceptors/
I have figured out the problem using this SO thread.
Apparently, if you are doing an AXIOS post, you can't redirect from the Express server and you must handle the redirect in the AXIOS method.
I haven't found any explanation or documentation on this subject yet. When I do I will update this answer.
I did it like this:
Server file:
app.post("/dhcpIP", function (req, res) {
// check if request body is empty
if (req.body.constructor === Object && Object.keys(req.body).length === 0)
return;
// doing things that work
if(errors.length != 0) {
res.sendStatus(500);
} else
res.sendStatus(200);
});
Vue component:
sendData: function() {
if (this.ssid === "" || this.password === "") return;
let currentUrl = window.location.pathname;
server
.post(currentUrl, { ssid: this.ssid, password: this.password })
.then(res => {
if(res.status == 200) {
alert("Settings have been saved.");
window.location = currentUrl;
} else {
alert("Something went wrong. Please try again");
}
})
.catch(err => {
console.log(err);
});
}

Authentication Middleware for routes not working

I am using Vue, Vuex with Quasar (Quasar might or might not be irrelevant here)
This is my first Application and so I am not sure if I am doing things correctly or not
Below is the code snippet for my routes
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
/*
* If not building with SSR mode, you can
* directly export the Router instantiation
*/
const routes = [
{
path: '/',
component: () => import('layouts/MyLayout.vue'),
beforeEnter: ifAuthenticated ,
children: [
{ path: '', component: () => import('./../container/Index.vue') }
]
}
]
const ifNotAuthenticated = (to, from, next) => {
console.log("here")
if (!store.getters.isAuthenticated) return next()
else next('/')
}
const ifAuthenticated = (to, from, next) => {
if (store.getters.isAuthenticated) return next()
else next('/login')
}
export default function (store) {
const Router = new VueRouter({
mode: 'history',
routes,
// Leave these as is and change from quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
})
return Router
}
Here notice this line of code
const ifNotAuthenticated = (to, from, next) => {
console.log("here")
if (!store.getters.isAuthenticated) return next()
else next('/')
}
With this I was expecting to do client side authentication and based on my understanding/reading about client Authentication
I thought this would be called when I did something like this
beforeEnter: ifAuthenticated ,
But unfortunately that function isn't being called (since it isn't console logging anything).
Any idea of what I might be doing wrong?
You're not calling ifNotAuthenticated anywhere in your code, so you won't see the console log message.
It's probably easier and better to use global beforeEach guard for checking authentication. Here's a very simple example code how you can do it using meta data in your routes config.
I added comments in the code to explain things.
I recommend you to read Navigation Guards in Vue Router documentation.
Also a nice article explaining things more in detail.
const routes = [
{
path: "/dashboard",
name: "Dashboard",
component: () => import("path/to/Dashboard.vue"),
// Add a metadata like this to your private routes
// Tip: If you have a lot of private routes, you can add
// meta: { public: true } to public routes and make
// all other routes private by default.
// In this case you need to change the logic in beforeEach
// below.
meta: { requiresAuth: true },
},
];
export default function(store) {
const Router = new VueRouter({
mode: "history",
routes,
// Other stuff...
});
Router.beforeEach((to, from, next) => {
// See if any of the matched routes has meta "requiresAuth"
if (to.matched.some(route => route.meta.requiresAuth)) {
// Yes this route requires authentication. See if the user is authenticated.
if (store.getters.isAuthenticated) {
// User is authenticated, we allow access.
next();
} else {
// User is not authenticated. We can redirect her to
// our login page. Or wherever we want.
next("/login");
}
} else {
next();
}
});
return Router;
}

How to run js code before everything in the page? I have session check in js and it should redirect to another page

I have session and js checks whether if it exists. It works fine, but dashboard page is seen for miliseconds, like a blink. But I need webpage to redirect before loading html code. How can I do this? I use vue js, even beforeCreate can't help.
Like #Jayem163 said in the notes I would run the authentication verification in your router on beforeRouteEnter Below is a basic example. The router code will run before any render of your component. This way you don't have duplicate code for each route.
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// no authentication required -- OPTIONAL
const noAuthenticationRequired = (to, from, next) => {
// in my apps right here I force logout. You don't want to go to a login page while logged in. But it's optional
next()
}
// make sure user is authenticated
const requiresAuthentication = (to, from, next) => {
// YOUR CHECK AUTH CODE WOULD GO HERE //
if (success) {
return next()
}
// not successful
next('/login')
}
export default new Router({
routes: [
{
path: '/',
name: 'dashboard',
beforeEnter: requiresAuthentication, // this route will require authentication
component: () => import('./views/Dashboard.vue')
},
{
path: '/login',
name: 'login',
beforeEnter: noAuthenticationRequired,
component: () => import('./views/Login.vue')
},
{
path: '/register',
name: 'register',
beforeEnter: noAuthenticationRequired,
component: () => import('./views/Register.vue')
},
{
path: '/password/forgot',
name: 'forgotPassword',
beforeEnter: noAuthenticationRequired,
component: () => import('./views/ForgotPassword.vue')
}
]
})

Vue Router does not redirect

I am currently trying to only show pages, if the user is logged in. The problem I face is that requireAuth() seems to get called endless amount of times.
The code is use is:
// Routes
const routes = [
{
path: '/',
component: Dashboard,
beforeEnter: (to, from, next) => {
requireAuth(to, from, next);
},
children: [
{
path: '',
name: 'dashboard',
component: DashboardIndex
}, {
path: '*',
name: '404',
component: NotFound
}
]
}, {
path: '/login',
component: Login,
name: 'login',
},
];
function requireAuth (to, from, next) {
if (!localStorage.token) {
console.log('testing');
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
}
// Routing logic
let router = new VueRouter({
routes: routes,
mode: 'hash'
});
testing is output ~1000 times before I receive the error:
[vue-router] uncaught error during route navigation:
warn # app.js
app.js RangeError: Maximum call stack size exceeded
How can I make sure that /login is redirected to if !localStorage.token?
I faced this same issue as the respective error's source all boils down to next() function which is the required to navigate to the path which has to.path as value. If you'll use router.push or router.replace then possibility is to get called endless amount of times as callstack max error displays. So use simply next() and let router API do cumbersome work
I have done this type of thing, but in different manner. I handled all logic in main.js file. and routes.js file contains -
var routes = [{
path:'/login',
component: Login
},
{
path:'/',
component: dashboard
}]
Now I have controlled all type of validation in main.js file using vue-router API as taking help from this - https://router.vuejs.org/en/api/route-object.html
So now main.js would contain -
const checkToken = () => {
if(localStorage.getItem('token') == (null || undefined) ){
console.log('token is not there : ' + localStorage.getItem('token'));
return false;
}
else{
return true
}
}
//then Use `router.push('/login')` as
router.beforeEach((to, from, next) => {
if(to.path == '/') {
if(checkToken()) {
console.log('There is a token, resume. (' + to.path + ')' + 'localstorage token ' + localStorage.getItem("token"));
next();
} else {
console.log('There is no token, redirect to login. (' + to.path + ')');
router.push('/login');
}
}
So you can structure like this as control all the things in main.js and leave route.js outta everything
If you don't have a localStorage token present you are redirecting a user to /login.
Because this is also a a Vue route, your requireAuth logic will run again(because it runs for every route). Meaning you have just created a infinite loop where a user will constantly be redirected to /login even if a user is already on that page.
To stop this simply do not redirect to /login when you already are on /login.
I will leave that part to you but it shouldn't be that hard if you understand what is going on.

Categories

Resources