How to check multiple conditions using Vue Router global navigation guard - javascript

In a vue 3 application which is using pinia, I want to achieve the following
redirect a user to the sign in page whenever a user is not authenticated
redirect a user to a verification page if the user authenticated but not verified
redirect a user to dashboard if the user is authenticated & verified
At the moment I have been able to redirect unauthenticated users to the sign in page and redirect them to the dashboard when authenticated by writing my router index.js file like this
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
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 }
}
]
})
router.beforeEach(async (to, from, next) => {
if (to.meta.needsAuth && localStorage.getItem('accessToken') == null) next('signin')
else next()
})
export default router
and here is the method that handles signin
const loginUser = async () => {
try {
const res = await axios.post(
"https://some-api-url/login",
signin.value,
{
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
);
localStorage.setItem("accessToken", res.data.data.accessToken);
// redirect home
router.push("/dashboard");
} catch (error) {
error = error.response.data.message;
alert(error);
}
};
Now my challenge is the signin endpoint I am calling only return an access token but the dashboard endpoint return the verified status. How can I achieve redirect unverified users to the verification page?

I was able to redirecting an authenticated but unverified user to the verification page by adding pre-route guard to the dashboard part like this
{
path: "/dashboard",
name: "dashboard",
component: () => import("../views/UserDashboard.vue"),
meta: { needsAuth: true },
beforeEnter: (to, from, next) => {
if (localStorage.getItem('verified') == "false") next('verify')
}
}
So what happens is the global guard will check if the user is authenticated while the pre route guard will check if the user is verified. It works now but I am not sure if this is the most efficient way to do it as I have more routes that need the user to be verified.
Isn't it possible to do it inside the global guard as well? Funny I am asking a question inside a supposed answer

Related

Vuex getter authenticated returns false in route guard. I assume it evaluates to false because at the time of calling autoLogin hasn't executed yet

So I have a /login route that renders the login view and I'm trying to make it so that if the user is already logged in, he gets redirected to another route. The problem is that when I type the url www.example.com/login, authenticated evaluates to false for some reason. This is how my code works:
Login Url:
{
path: '/login',
name: 'login',
component: Login,
beforeEnter: (to, from, next) => {
if (store.getters.authenticated) {
next({ name: "adminOrders" })
} else {
next()
}
}
},
Vuex authentication store:
import router from '../router'
import axios from 'axios'
const authentication = {
state: {
user: null
},
mutations: {
setUser(state, user){
state.user = user
localStorage.setItem('userId', user.id);
localStorage.setItem('username', user.username);
localStorage.setItem('token', user.token);
}
},
actions: {
autoLogin({commit, dispatch}){
const userId = localStorage.getItem('userId')
const username = localStorage.getItem('username')
const token = localStorage.getItem('token')
if (!token) {
return
}
let user = {
id: userId,
username: username,
token: token
}
commit('setUser', user)
axios.interceptors.request.use(function (config) {
config.headers.Authorization = 'Bearer ' + token
return config
});
},
},
getters: {
authenticated: state => {
return state.user ? true : false
}
}
}
export default authentication
And I call the autoLogin on App.vue mounted like this:
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
mounted(){
this.$store.dispatch('autoLogin');
}
}
</script>
beforeEnter is called before App is mounted, because router gets instantiated as soon as App is created.
Since you call autoLogin action from App's mounted() it's actually run after the router reads the getter from the store.
However, you could call an async action from beforeEnter, which would return whether or not there is a token in localStorage.
At a minimum, here's what would work:
// routes:
beforeEnter: async (to, from, next) => {
const hasToken = await store.dispatch('hasToken');
next(hasToken ? { name: 'adminOrders' } : undefined);
}
// store:
actions: {
hasToken() {
return !!localStorage.getItem('token')
}
// ...
}
Make sure you wipe the token out from localStorage when you get a 401 error in your axios interceptors (basically means "token expired"). If you do not clear the token from localStorage before trying to go to /login (which is what usually happens on 401), the beforeEnter will redirect to adminOrders, adminOrders will attempt to load data, data calls will return 401 as token is expired and you end up in a loop.
Alternatively, you could just get a new token on 401's and update localStorage.

How to redirect to desired page in Vue js using vue-router?

This is my code for requiresAuth and requiresGuest
requiresAuth : Users who satisfy this cannot go to login page or signup page ( meta for / )
requiresGuest : Users who satisfy this cannot go to / or any other page having requiresAuth meta for /login and /signup
These 2 conditions are working perfectly fine for my page
Problem :
Step-1 Lets say i have been given a url like localhost:8000/api/createapi/.......
Step-2 So currently i am not logged in, I enter the above URL and it redirects me to the log in page (which is expected)
Step-3 But when i log back in it redirects me to / (which is not ideal )
What i want :
After Step-2 When i log in it should redirect me automatically to localhost:8000/api/createapi/.......
Since that was the requested URL in Step-1
router.beforeEach((to, from, next) => {
// check for required auth guard
if (to.matched.some(record => record.meta.requiresAuth)) {
requiresAuthLogic(to, next, from)
} else if (to.matched.some(record => record.meta.requiresGuest)) {
requiresGuestLogic(to, next)
} else {
// Proceed to route
next()
}
})
function requiresAuthLogic (to:Route, next:Function) {
// check if NOT logged in
if (!isUserLoggedIn()) {
// Go to login
next({
path: '/login',
query: {
redirect: to.fullPath
}
})
} else if (isUserEmailVerified() === true) {
// Proceed to route
next()
}
}
function requiresGuestLogic (to:Route, next:Function) {
if (isUserLoggedIn() && isUserEmailVerified() === true) {
next({
path: '/',
query: {
redirect: to.fullPath
}
})
} else {
// Proceed to route
next()
}
}
If I've understood correctly you need to use the value which is being passed via the redirect parameter
This should be done in your login function if login is successful, you haven't shared your login function but something like this:
loginUser() {
this.$store.dispatch('loginUser', {
email: this.email,
password: this.password
})
.then(() => {
this.$router.push(this.$route.query.redirect)
})
}

How to redirected to home after successful login with Vue.JS

I have a login page. I want my app to redirect the user to the homepage if the login is successful. Then credentials are checked with an API. My problem is, my vue page redirect the users before the credentials are successfully checked.
Seeing similar topic on the vue.js help forum, I understand I am supposed to send the login request, and then wait for the response promise to resolve. I feel like this is what i am doing, but it clearly does not wait for the response to be resolved before redirecting.
Here is my code in my vue page (the script part) . When I click the "signin" button, the onSigninClick() method is called :
import { mapActions, mapGetters } from 'vuex'
export default {
name: 'SignInLayout',
data () {
return {
username: null,
password: null
}
},
computed: {
...mapGetters('TemplaterAuth', [
'logged',
'getUsername',
'getJwtToken'
])
},
methods: {
...mapActions('TemplaterAuth', [
'authenticate'
]),
onSigninClick () {
let creds = {
username: this.username,
password: this.password
}
this.authenticate(creds).then(response => {
console.log(this.getUsername)
console.log(this.getJwtToken)
console.log('logged:')
console.log(this.logged)
this.$router.push('/')
})
}
}
}
and my authenticate() method :
export function authenticate (context, creds) {
let requestConfig = {
headers: {
'Content-Type': 'application/json'
}
}
Vue.http.post(
url + apiPaths.auth,
creds,
requestConfig
).then(response => {
return response.json()
}).then(data => {
context.commit('setUsername', creds.username)
context.commit('setJwtToken', data.token)
}).catch(error => {
console.log('error:')
console.log(error)
})
}
When i click once the login button, my console log shows null for both the username and the jwtToken . Few moments later, the values are updated in the store and then I am able to login.
So, I got my answer on the Vue forum just a few seconds after posting this : I need to return the promise of my authenticate method. So the new code is :
export function authenticate (context, creds) {
let requestConfig = {
headers: {
'Content-Type': 'application/json'
}
}
return Vue.http.post(
url + apiPaths.auth,
creds,
requestConfig
).then(response => {
return response.json()
}).then(data => {
context.commit('setUsername', creds.username)
context.commit('setJwtToken', data.token)
}).catch(error => {
console.log('error:')
console.log(error)
})
}
Source
This looks a bit fishy for me.
First: I hope, you are not using vuex to hold your crentials/token.
Security tip #1, Security tip #2.
I would recommend only setting authenticated:true and use that for your requests.
Then:
Have a look at global guards of the vue router, which could be leveraged, to check, whether the current user is logged in.
Last:
To programatically route to sections of your app, you could use router.push.
So, you have to store, which route the user wanted before the login page and programmatically push this route afterwards.

Issue with Vue.js, showing the Log in screen shortly, when user authenticated (full page refresh)

I have my routing working fine, using navigation guards so that user is not able to visit login or register routes once signed in.. However when I type in in addres bar /auth/signin, login screen does appear shortly before redirecting to dashboard (as it detects in beforeEach that the route is requiresGuest).
router.beforeEach(function (to, from, next) {
// prevent access to login & register after signing in
if (to.matched.some(record => record.meta.requiresGuest)
&& auth.user.authenticated)
{
next({
path: '/dashboard'
});
}
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.user.authenticated) {
next({
path: '/auth/signin',
query: { redirect: to.fullPath }
})
}
}
next() // make sure to always call next()!
});
Is there a way to prevent component from flash-appearing like that!?
Isn't that beforeEach triggered before the component is even created?
Change your if else conditional statements.
router.beforeEach(function(to, from, next) {
// prevent access to login & register after signing in
if (to.matched.some(record => record.meta.requiresGuest) && auth.user.authenticated) {
next({
path: '/dashboard'
});
} else if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.user.authenticated) {
next({
path: '/auth/signin',
query: {
redirect: to.fullPath
}
})
}
} else {
next() // make sure to always call next()!
}
})

Angular2 - redirect after login

I want to navigate my user on another route after login.
I found out in documentation that something like this should work for it correctly:
this.router.navigate(['/users']);
My full method in compoenent looks like:
// imports on top
import { Component } from '#angular/core';
import { LoginService } from './login.service';
import { Router } from '#angular/router';
// method somewhere below
login(event, username, password) {
event.preventDefault();
let body = JSON.stringify({ username, password });
this.loginService.login(body)
.then(res => localStorage.setItem('token', res.msg))
.catch(error => this.error = error);
this.router.navigate(['/users']);
}
However it doesn't redirect me. Basically route stays without change and no error spotted in console.
What am I doing wrong?
Edit:
My routes looks like:
const appRoutes: Routes = [
{ path: 'login', component: LoginComponent},
// users route protected by auth guard
{ path: 'users', component: UsersComponent, canActivate: [AuthGuard] },
// { path: 'user/:id', component: HeroDetailComponent },
{ path: '**', redirectTo: 'users' }
];
My AuthGuard looks like:
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate() {
if (localStorage.getItem('token')) {
// logged in so return true
return true;
}
// not logged in so redirect to login page
this.router.navigate(['/login']);
return false;
}
}
I had a very similar problem on my post. The problem is that there is a delay in authenticating your user but router.navigate executes instantly. As the users part of your site is protected and your technically still unauthenticated, it fails the redirect.
By redirecting on the .then it waits for your user to login, and then if successful it redirects your user.
Try using this:
this.loginService.login(body)
.then(
res => localStorage.setItem('token', res.msg)
this.router.navigate(['/users']);
)
.catch(error => this.error = error);
this.router.navigate(['/users']);
This should be called after localStorage.setItem(), inside the .then() function
If it still doesn't work, may you put the code of the class in your message please

Categories