How to redirect to home page after logout in nuxt? - vue-router

after login in site i want when i click to specific button , user log out from site to home page.
here is my template code:
<template>
<nav id="header" class="navbar navbar-expand header-setting">
<div class="main-content">
<div class="notification" #click="LogOut()"></div>
</div>
</div>
</nav>
</template>
and here is my script code:
export default {
name: 'HeadeAfterLogin',
methods: {
LogOut() {
localStorage.removeItem('token')
}
}
}
any one can help me to complete LogOut function ?

Inside your component you have access to this.$router
So you can easily do:
export default {
name: 'HeadeAfterLogin',
methods: {
LogOut() {
localStorage.removeItem('token')
this.$router.push('/')
}
}
}

What I am doing is:
Component:
### html
LogOut
### script
export default {
methods: {
signOut() {
this.$auth.logout();
}
}
}
nuxt.config.js
auth: {
strategies: {
...
},
redirect: {
login: '/login',
logout: '/login', # after logout, user will be redirected here.
home: '/'
},
}

Related

Vue dynamically adding routes from wordpress menus api, possible reaactivity problem

I have a vue application on the frontend and a wordpress api on the backend. I am hitting the menus api and dynamically adding routes to the frontend at run time.
This works great. Until I reset the page on one of the dynamic routes. The component does not load and mounted() is never called. At this point, I can click the router link in the nav bar and the page component renders as expected.
For example. In the wordpress admin, I create a page called hello-world and add it to the primary menu. Vue will hit the api and create a route with the same name. I then load up the page and it loads fine. I click the hello world link in the nav bar, and it renders beautifully.
Now, I'm sitting at http://website.com/hello-world, and I reset the page. The app mounts and the nav bar renders. However, the page component does not render. If I click the link in the nav bar again, then it renders fine.
I am thinking this is a reactivity problem, but I can't find it. Any suggestions?
Edit. Been pondering this. The router component is loaded, and fetches the menu items asynchronously. Now, Im already sitting on one of the dynamic routes, /hello-world. The app is now loaded and there doesn't exist yet a hello-world route, since the api request is still pending. Since there is no matching route, the vue application doesn't know which component to mount... Perhaps there is a way to make the router component itself reactive?
relevant router code...
store.dispatch("getPrimaryMenu").then(() => {
store.state.menu.items.forEach((item) => {
if (item.object === "post") {
router.addRoute({
path: `/${item.slug}`,
name: item.slug,
component: () => import("#/views/Post.vue"),
});
}
if (item.object === "page") {
router.addRoute({
path: `/${item.slug}`,
name: item.slug,
component: () => import("#/views/Page.vue"),
});
}
});
});
and my store...
export default createStore({
state: {
menu: {
items: [],
},
page: {
title: {},
content: {},
},
post: {
title: {},
content: {},
},
},
mutations: {
SET_MENU(state, data) {
state.menu = data
},
SET_PAGE(state, data) {
state.page = data
},
SET_POST(state, data) {
state.post = data
},
},
actions: {
getPrimaryMenu({ commit, state }) {
console.log('get menus')
return new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`http://sslchkr.com/wp-json/menus/v1/menus/primary`, {
headers: {
'Content-Type': 'application/json'
}
}
)
commit('SET_MENU', data)
resolve(data)
} catch (e) {
reject(e)
}
})
},
getPage({ commit, state }, payload) {
console.log('get page')
return new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`http://sslchkr.com/wp-json/wp/v2/pages/${payload.id}`, {
headers: {
'Content-Type': 'application/json'
}
}
)
commit('SET_PAGE', data)
resolve(data)
} catch (e) {
reject(e)
}
})
},
getPost({ commit, state }, payload) {
console.log('get post')
return new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`http://sslchkr.com/wp-json/wp/v2/posts/${payload.id}`, {
headers: {
'Content-Type': 'application/json'
}
}
)
commit('SET_POST', data)
resolve(data)
} catch (e) {
reject(e)
}
})
},
},
}
a page component...
I am matching the route name to an item slug from the menu object, and using that item object_id to fetch the page object.
<template>
<div class="page">
<div>
<h1>{{ page.title.rendered }}</h1>
</div>
<div v-html="page.content.rendered"></div>
</div>
</template>
<script>
export default {
name: "Page",
computed: {
menuItem() {
return this.$store.state.menu.items.find(
(item) => item.slug === this.$route.name
);
},
page() {
return this.$store.state.page;
},
},
mounted() {
this.$store.dispatch("getPage", { id: this.menuItem.object_id });
},
};
</script>
and the nav component for completeness...
<template>
<ul id="menu-primary list-inline">
<li
v-for="item in menu.items"
:key="item.ID"
class="nav-item list-inline-item"
>
<router-link :to="slash(item.slug)" class="nav-link">{{
item.title
}}</router-link>
</li>
</ul>
</template>
<script>
export default {
name: "Nav",
computed: {
menu() {
return this.$store.state.menu;
},
},
methods: {
slash(s) {
return `/${s}`;
},
},
};
</script>
Edit to include main.js and App.vue
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap/dist/js/bootstrap.js'
import 'vue-toastification/dist/index.css'
import { createApp } from 'vue'
import Toast, { POSITION } from 'vue-toastification'
import App from './App.vue'
import router from './router'
import store from './store'
let app = createApp(App)
app.use(store)
app.use(router)
app.use(Toast, { position: POSITION.TOP_CENTER })
app.mount('#app')
<template>
<link rel="stylesheet" :href="theme" />
<Nav />
<div class="container-fluid">
<div class="row padding-top">
<div class="col-md-2"></div>
<div class="col-md-8">
<router-view :key="$route.path" />
</div>
<div class="col-md-2"></div>
</div>
</div>
</template>
<script>
import Nav from "#/components/Nav.vue";
export default {
components: {
Nav,
},
computed: {
theme() {
return this.$store.state.theme;
},
},
mounted() {
this.$store.dispatch("getTheme");
},
};
</script>

Vue router guard triplicating navigation

I have a router guard beforeEach route to watch if there's user authenticated:
import Vue from "vue";
import VueRouter from "vue-router"
import Login from "../views/Login.vue"
import Home from "../components/Home.vue"
import Register from "../views/Register.vue"
import Dashboard from "../views/Dashboard.vue"
import Pricing from "../components/Pricing.vue"
import Invoices from "../components/Invoices.vue"
import { FirebaseAuth } from "../firebase/firebase"
Vue.use(VueRouter);
const routes = [
{
path: "*",
redirect: "/login",
},
{
path: "/dashboard",
name: "dashboard",
component: Dashboard,
children: [
{
path: "home",
name: "home",
component: Home,
},
{
path: "pricing",
name: "pricing",
component: Pricing,
},
{
path: "invoices",
name: "invoices",
component: Invoices,
}
],
meta: {
auth: true,
},
redirect: "home"
},
{
path: "/login",
name: "login",
component: Login,
},
{
path: "/register",
name: "register",
component: Register,
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
router.beforeEach((to, from, next)=>{
let user = FirebaseAuth.currentUser;
let auth = to.matched.some(record => record.meta.auth);
if (auth && !user) {
next('/login');
} else if (!auth && user) {
next('/dashboard/home');
} else{
next();
}
});
export default router;
When I perform logouts and logins there's an error about redundant navigation, however, I just assumed that it's ok if I just catch this.$router.push('/dashboard/home').catch(err => err); and move on without the console.log err. But creating an alert on component created() I've noticed that the thing is just more serious than what I thought, the component that shows the alert on created() it's showing it three times, and as I have a fetch for restore items on created(), that function is being triggered three times which is obviously not the performance wanted.
async created() {
alert("created")
this.credits = await fetchCredits(this.$firestore, this.$auth.currentUser);
let role = await getCustomClaimRole(this.$auth.currentUser);
this.subscription = role
? role.charAt(0).toUpperCase() + role.slice(1) + " " + "plan"
: "You haven't subscribed yet";
this.isLoading();
},
inside fetchCredits() is the console.log triggering three times
export const fetchCredits = async function (firestore, currentUser) {
// firestore collection of customers
const db = firestore.collection("customers");
/**
* Let's fetch the credits from the user:
*/
const credits = (await db.doc(currentUser.uid).get()).data();
if (credits !== "undefined") {
console.log(credits);
return credits.credits
} else {
return 0;
}
}
I think the problem is with the navigation guard, however, correct me if I'm wrong, but how to solve this?
I think that it has something to do with your router path:
{
path: "*",
redirect: "/login",
},
I have used Vue Router several times, but since I hadn't used wildcards before, I built a simplified Vue 2 CLI test application.
My router:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import Home from '#/components/stackoverflow/router-wildcard-match/Home'
import RouteOne from '#/components/stackoverflow/router-wildcard-match/RouteOne'
import RouteTwo from '#/components/stackoverflow/router-wildcard-match/RouteTwo'
import WildCard from '#/components/stackoverflow/router-wildcard-match/WildCard'
const routes = [
{
path: '/*',
name: 'wildcard',
component: WildCard
},
{
path: '/home',
name: 'home',
component: Home,
},
{
path: '/routeone',
name: 'routeOne',
component: RouteOne,
},
{
path: '/routetwo',
name: 'routeTwo',
component: RouteTwo,
},
]
export default new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
and my navbar component that routes programmatically:
<template>
<div class="navbar-sandbox">
<nav class="navbar navbar-expand-md navbar-light bg-light">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="#" #click.prevent="navigate('home')">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" #click.prevent="navigate('routeone')">RouteOne</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" #click.prevent="navigate('routetwo')">RouteTwo</a>
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
data() {
return {
//currentRoute: 'home',
currentPath: 'home'
}
},
methods: {
// NOTE: Using route names work regardless of having wildcard path
// navigate(route) {
// if (route !== this.currentRoute) {
// this.currentRoute = route;
// this.$router.push({ name: route });
// }
// },
navigate(path) {
if (path !== this.currentPath) {
this.currentPath = path;
this.$router.push({ path: path });
}
}
}
}
</script>
As you can see in my code comments, when I programmatically route via the route names, it works even with having a wildcard path, but when I route via the actual route path, the routes are all intercepted by the wildcard.
My wildcard path is a bit different that yours, /* vs *.

Vue how to redirect user after login to main components

I'm just started learning Vue and I don't understand how to make "structure" of components, I mean: my login page should be as a main component which is loaded as first page in application, then after success login user should be redirected to main components (core application).
Can someone simply explain how to do that? What should be in App.vue? Where to place router-view responsible for login-view and where to place routers-views which are responsible for views which can be showed just after loggin?
Here's couple of pieces of my code:
App.vue
<template>
<div>
<left-menu></left-menu>
<nav-menu></nav-menu>
<router-view></router-view>
</div>
</template>
<script>
import NavMenu from './NavMenu.vue';
import LeftMenu from './LeftMenu.vue';
export default {
components: {
navMenu: NavMenu,
leftMenu: LeftMenu
},
computed: {
auth() {
return this.$store.getters.isAuthenticated;
}
}
}
</script>
That's my routes.js, every component has same structure:
(basically that particular component should be loaded after loggin)
{
path: '/orders',
component: Orders,
beforeEnter(to, from, next) {
if (store.state.jwt) {
next();
} else {
next('/login')
}
}
}
and here's my Login component:
<template>
<div class="login-content">
<form #submit.prevent="submit">
<label>Podaj login</label>
<input type="text" v-model="login">
<br>
<label>Podaj hasło</label>
<input type="password" v-model="password">
<button type="submit">Zaloguj</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
login: '',
password: ''
}
},
methods: {
submit() {
const formData = {
login: this.login,
password: this.password
}
this.$store.dispatch('login', {
login: formData.login,
password: formData.password
})
}
}
}
With those setting, my Login component is loaded when user isn't authenticated, but the content appear on components which shouldn't be available to see before loggin.
I've seen Redirect to requested page after login using vue-router but I don't get it.
Redirecting to the home page after successful login can be done in the method itself.
submit() {
const formData = {
login: this.login,
password: this.password
}
this.$store.dispatch('login', formData)
.then(response=>{
if(response.data.status){
this.$router.push({name:'home'})
}
})
}
Now you need to handle the access to the login page. I managed this by adding meta values to the router objects.
{
name: 'login',
path: '/login',
component: Login,
meta: {
public: true,
disableIfLoggedIn: true
}
},
public : If this value set to true then, this page can be accessed by without authentication.
disableIfLoggedIn: If this value is set to true, then the page cannot access once authenticated.
These values are checked and managed the redirection in the beforeEach section like follows
router.beforeEach((to, from, next) => {
// if the route is not public
if (!to.meta.public) {
// if the user authenticated
if (store.getters.isAuthenticated) {
// continue to the route
next();
} else {
// set redirect to path after login
let redirect = {};
if (to.name !== 'home') {
redirect = {redirect: to.path};
}
// redirect to login
next({name: 'login', query: redirect});
}
}
// if the user is authenticated and the route is disabled for authenticated window
if (store.getters.isAuthenticated && to.meta.disableIfLoggedIn) {
// redirect to home
next({name: 'home'});
}
next();
});
This will check before every router navigation and control the access to the routes.
Note
The redirect section can be removed. This is to handle redirect to specific path after login.

Using vue.js and Auth0 how can i skip the login page if the user's already authenticated?

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.

Vue router - API Data not fetching on redirect

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.