The most of my routes are protected and require permissions to access them. When the user signed in successfully my Navbar component makes an API call and retrieves a bunch of routes the user is able to access.
After that I add all the view files matching to the routes to the navbar.
This is an example code showing the process
<template>
<div>
<router-link
v-for="navItem in navItems"
:key="navItem.title"
:to="navItem.url"
>{{ navItem.title }}</router-link>
</div>
</template>
<script>
export default {
data: function() {
return {
navItems: []
};
},
created: async function() { // Setup the router here
this.navItems = [
// !!! API CALL !!!
{
title: "Dashboard",
url: "/Dashboard"
},
{
title: "Users",
url: "/users/Users"
},
{
title: "Groups",
url: "/groups/Groups"
}
];
const routes = await this.navItems.map(async navItem => {
const { url } = navItem;
return {
path: url,
component: await import(`../views${url}.vue`)
};
});
this.$router.addRoutes(routes);
}
};
</script>
Unfortunately I get this error
Uncaught (in promise) Error: [vue-router] "path" is required in a
route configuration.
but as you can see in the example code this attribute is set. I created an sample project here
https://codesandbox.io/s/vue-routing-example-i2znt
If you call this route
https://i2znt.codesandbox.io/#/Dashboard
I would expect the router to render the Dashboard.vue file.
the routes array that you build doesn't contains your routes objects.
It's an array of promises.
you should do something like
Promise.all(routes).then(resolvedRoutes => {
this.$router.addRoutes(resolvedRoutes)
})
Related
In my nuxt project i have the following structure in my pages folder:
index.vue
tools
index.vue
_id.vue
page-not-found.vue
in my nuxt config i have this on router section:
router: {
extendRoutes(routes, resolve) {
routes.push(
{
name: '404',
path: '*',
component: resolve(__dirname, 'pages/page-not-found.vue')
},
)
},
}
when i land in url other than on my pages folder, i have the url and my 404 component showed (ex: my-domaine/other-path show 404 content). So that i want is with my route tools-id i want to fetch data depend on parameter id, if got data, i show the page with details, but if i got no data, i want to have the same behavior as my 404 route; like, in my url i have my-domaine/tools/parameter_value but my pages show the 404 content.
Take a look at the Nuxt documentation for error pages: https://nuxtjs.org/docs/concepts/views/#error-page.
You can create a layout file called error.vue which serves as a base for all error pages in your site. From that layout you can reference the appropriate error component. For example:
<template>
<div>
<NuxtLink to="/">Home page</NuxtLink>
<PageNotFound v-if="error.statusCode === 404" :message="error.message" />
</div>
</template>
<script>
export default {
props: ['error'],
layout: 'error'
}
</script>
Vue will automatically render the content of the error page on the invalid route. You can also programmatically open the error page by calling the context.error() method. For example:
asyncData ({ params, error }) {
return axios.get(`https://my-api/posts/${params.id}`)
.then((res) => {
return { title: res.data.title }
})
.catch((e) => {
error({ statusCode: 404, message: 'Post not found' })
})
}
If you're using the error function inside the asyncData or fetch methods, then you can access it like I showed in the answer above, otherwise in any other method defined in the Vue instance you can access it via this.error()
export default {
data: {...},
methods: {
validateRoute() {
if (404) {
this.error({statusCode: 404, message: "Page not found"})
}
},
axiosRequest() {
try {
const response = await axios.get('...');
return response;
} catch(err) {
this.error({statusCode: 404, message: err.message });
}
}
}
}
I have a problem using IBM App Id and Nuxt.js project. When I am trying to do the signIn method from
IBM AppId, it thows an error, error setAlgAndProvider hash alg set fail alg=sha256/RangeError: Maximum call stack size exceeded
that's my code from my login component:
<template>
<div class="login" #click="login">
CLICK TO LOGIN
</div>
</template>
<script>
export default {
name: "Login",
data: () => ({
appID: null
}),
mounted() {
this.appID = new AppID();
this.init();
},
methods: {
async init() {
await this.appID.init({
clientId: '************',
discoveryEndpoint: '********'
});
},
async login() {
const tokens = await this.appID.signin();
console.log("***TOKENS***", tokens);
}
}
}
</script>
Any ideas? I see that error on my browser (attached image)
I need to download files from remote api by vue-router link. Remote api returns files in base64.
I add route:
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/files/:id', name: 'file', component: Vue.component('vue-file'), props: true }
],
});
and add component for it:
<template>
<div>
</div>
</template>
<script>
export default {
name: 'file',
props: {
id: String
},
mounted: function() {
this.download();
},
methods: {
download: function() {
let $this = this;
axios
.get('apiurl' + encodeURIComponent(this.id))
.then(function (response) {
download(response.data.base64content)
});
}
}
}
</script>
It works but I don't want to show component template <template><div></div></template>. I even don't want to refresh the content on the screen. Is it possible?
You shouldn't. Vue Components were done for rendering. Your implementation would be nicer as a mixin or plugin, if you don't want to render anything.
Although, I think you can do something like:
render() {
return null;
},
DESCRIPTION
I have a pretty standard SPA built with vue.js where I'm using Auth0 to handle the authentication part by following the official example. The app flow is as follows:
Register in the Initial.vue via Auth0 lock -> Callback is called -> User's redirected to /home
Everything in the above flow works fine but here's the problem:
PROBLEM
Once the user's registered and in the /home I want him to be able to access all the other routes (e.g. /doctors) if authenticated and if not he should be prompter to login again. According to the above link this is handled in the router.beforeEach function.
My problem appears when accessing the / login page (Initialvue). When the user's already registered and trying to access that route I want him to get redirected to the /home and skip the login page. I've tried implementing this with a beforeEnter route but auth.isAuthenticated fails due to the tokenExpiry being null (even-though user's authenticated!
CODE
My AuthService.js:
import auth0 from 'auth0-js';
import EventEmitter from 'events';
import authConfig from '../config/auth_config.json';
const webAuth = new auth0.WebAuth({
domain: authConfig.domain,
redirectUri: `${window.location.origin}/callback`,
clientID: authConfig.clientId,
responseType: 'id_token',
scope: 'openid profile email'
});
const localStorageKey = 'loggedIn';
const loginEvent = 'loginEvent';
class AuthService extends EventEmitter {
idToken = null;
profile = null;
tokenExpiry = null;
// Starts the user login flow
login(customState) {
webAuth.authorize({
appState: customState
});
}
// Handles the callback request from Auth0
handleAuthentication() {
return new Promise((resolve, reject) => {
webAuth.parseHash((err, authResult) => {
if (err) {
reject(err);
} else {
this.localLogin(authResult);
resolve(authResult.idToken);
}
});
});
}
localLogin(authResult) {
// console.log(authResult); TODO-me: Handle this
this.idToken = authResult.idToken;
this.profile = authResult.idTokenPayload;
// Convert the JWT expiry time from seconds to milliseconds
this.tokenExpiry = new Date(this.profile.exp * 1000);
localStorage.setItem(localStorageKey, 'true');
this.emit(loginEvent, {
loggedIn: true,
profile: authResult.idTokenPayload,
state: authResult.appState || {}
});
}
renewTokens() {
return new Promise((resolve, reject) => {
if (localStorage.getItem(localStorageKey) !== "true") {
return reject("Not logged in");
}``;
webAuth.checkSession({}, (err, authResult) => {
if (err) {
reject(err);
} else {
this.localLogin(authResult);
resolve(authResult);
}
});
});
}
logOut() {
localStorage.removeItem(localStorageKey);
this.idToken = null;
this.tokenExpiry = null;
this.profile = null;
webAuth.logout({
returnTo: window.location.origin
});
this.emit(loginEvent, { loggedIn: false });
}
isAuthenticated() {
console.log('In tokenExp is:');
console.log(this.tokenExpiry); //THIS returns null when /home -> /
return (
Date.now() < this.tokenExpiry &&
localStorage.getItem(localStorageKey) === 'true'
);
}
}
export default new AuthService();
My Initial.vue:
<template>
<v-container
app
fluid
>
<v-parallax
src="https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg"
height="1000"
>
<v-layout
row
wrap
>
<!-- LOGIN-->
<v-toolbar
flat
light
dense
color="transparent"
>
<v-spacer></v-spacer>
<v-toolbar-items>
<v-btn
medium
color="lime lighten-2"
#click="login"
class="font-weight-bold title text-uppercase"
>
Login
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-layout
align-center
column
>
<h1 class="display-2 font-weight-thin mb-3 text-uppercase lime--text lighten-2" >Pulse</h1>
<h4 class="subheading">A digital intelligent insurance built for you!</h4>
</v-layout>
</v-layout>
</v-parallax>
</v-container>
</template>
<script>
import VContainer from "vuetify/lib/components/VGrid/VContainer";
import VFlex from "vuetify/lib/components/VGrid/VFlex";
import VLayout from "vuetify/lib/components/VGrid/VLayout";
import VBtn from "vuetify/lib/components/VBtn/VBtn";
import VToolbar from "vuetify/lib/components/VToolbar/VToolbar";
import VParallax from "vuetify/lib/components/VParallax/VParallax";
export default {
name: "Initial",
components: {
VContainer,
VLayout,
VFlex,
VBtn,
VToolbar,
VParallax
},
data() {
return {
isAuthenticated: false
};
},
async created() {
try {
await this.$auth.renewTokens();
} catch (e) {
// console.log(e);
}
},
methods: {
login() {
this.$auth.login();
},
// logout() {
// this.$auth.logOut();
// },
handleLoginEvent(data) {
this.isAuthenticated = data.loggedIn;
this.profile = data.profile;
}
}
}
</script>
<style scoped>
</style>
My Callback.vue:
<template>
<div>
<p>Loading...</p>
</div>
</template>
<script>
export default {
methods: {
handleLoginEvent(data) {
console.log('State.target is:');
console.log(data.state.target);
//If user has just signed up redirect to complete-signup form
if ((data.profile['<AUTH_DOMAIN>'].justSignedUp) && (data.state.target===undefined)){
// this.$router.push(data.state.target || "/complete-signup");
this.$router.push('/complete-signup');
}else {
// this.$router.push('/home');
this.$router.push(data.state.target);
}
}
},
created() {
this.$auth.handleAuthentication();
}
}
</script>
<style scoped>
</style>
My router.js:
import Vue from 'vue';
import Router from 'vue-router';
import auth from '../auth/AuthService';
import Callback from '../components/Callback';
Vue.use(Router)
// export default new Router({
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
// {
// path: '/',
// name: 'login',
// component: () => import('#/views/Login')
// },
{
path: '/',
name: 'initial',
component: () => import('#/views/Initial'),
// meta: {isAuth: true},
beforeEnter: ((to, from, next) => {
// if (to.matched.some(record => record.meta.isAuth)) {
console.log(auth.isAuthenticated()); //THIS is false for the above scenario
if (auth.isAuthenticated()) {
next({
path: '/home',
query: {redirect: to.fullPath}
})
} else {
next()
}
// }
})
},
{
path: '/callback',
name: 'callback',
component: Callback
},
{
path: '/home',
name: 'home',
component: () => import('#/views/Home')
},
{
path: '/doctors',
name: 'doctors',
component: () => import('#/views/Doctors')
},
{
path: '/complete-signup',
name: 'complete-signup',
component: () => import('#/views/CompleteSignup')
},
]
});
// Add a `beforeEach` handler to each route
router.beforeEach((to, from, next) => {
if (to.path === "/" || to.path === "/callback" || auth.isAuthenticated()) {
return next();
}
// Specify the current path as the customState parameter, meaning it
// will be returned to the application after auth
console.log('OUT beforeach if');
auth.login({ target: to.path });
});
The 'CompleteSignupis my signup form after registering where the user's is filling out a form and then posting it viaaxiosand then redirected to/home`:
//Form data before
methods: {
this.$store.dispatch(REGISTER,registerFormData)
.then(() => this.$router.push('/home'));
}
I'm also using vuetify and my main App.vue component is:
<template>
<v-app
style= "background: #E0EAFC; /* fallback for old browsers */
background: -webkit-linear-gradient(to left, #CFDEF3, #E0EAFC); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to left, #CFDEF3, #E0EAFC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
"
>
<v-content>
<router-view></router-view>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'App',
components: {
}
};
</script>
<style>
</style>
You can simplify the problem by making the default behaviour the one where your user is logged in and then protect the respective routes with the route guard.
1) point your / to /home
2) create separate route for login/"intial"
3) use the beforeEach hook to ensure a user is authenticated and if not redirect him to your Initial.vue (or trigger auth.login() directly)
...
{
path: '/',
redirect: 'home'
},
{
path: '/initial',
...
...
}
...
router.beforeEach((to, from, next) => {
if(to.name == 'callback' || auth.isAuthenticated()) {
next()
} else { // trigger auth0 login or redirect to your Initial.vue
auth.login()
// next({ path: '/initial' })
}
})
I figured it out. The problem lies in the fact that the this.tokenExpiry, this.idToken, this.profile values are being assigned a value within the callback view which comes after the login one and in addition to that these values are tied to the specific Vue prototype instance that I'd defined in a mixin. So when navigating back to the initial page this is undefined since it's not associated to a specific Vue instance.
I had been in the same situation (with React) and I will try to explain how to solve it.
First, in Authentication, it is necessary to store the token on the client side (localStorage / cookie can be used)
You need a SecureComponent, this component will only check if the stored token exists
FetchHandler this component handles eachProtected Application
and will inspect the response code, and if it is 401 (for example) it will redirect a User to the Authentication Component. This step can be added to SecureComponent as an additional layer.
I'm building an application with JWT Login and i check if the user is logged in (when visit /) and then i redirect to Dashboard:
let routes = [
{ path: '', component: Login,
beforeEnter(to, from, next) {
if (auth.loggedIn()) {
next({ path: '/dashboard' });
} else {
next();
}
}
},
{ path: '/dashboard', component: Dashboard }
];
The Dashboard component is simple:
export default {
created() {
this.loadOrders();
},
methods: {
loadOrders() {
// Load Orders
}
},
watch: {
'$route': 'loadOrders'
},
}
If i Login, i will be redirected to /dashboard and the data is fetched.
If i'm on Dashboard (http://localhost:8080/dashboard) and i hit "refresh" on browser, this works too.
But, if i'm on this url http://localhost:8080/dashboard and i delete dashboard (so i just digit http://localhost:8080) the beforeEnter see that i'm authenticated and redirect me to /dashboard, but the data is not fetched (created, mounted etc are not called).
Why there is no data section on your Dashboard component? If you use some data (ex: loading, error, post) on template, then you need to return them in data section. Try to add that section.
example:
<template>
<div>
<div v-if="loading">
Loading...
</div>
<div v-if="!loading">
{{ error }}
</div>
<div>
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
export default {
data () {
return {
loading: false,
error: null,
post: null
}
},
created () {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData () {
this.loading = true
...
this.error = msg;
this.post = post
}
}
};
When any action is taken against an API, the server responds with relevant status.
So when you are deleting the product, you have to ignore the response from the server and then push the new path to Vue router.