Why is Angular router.navigate() not navigating? - javascript

I'm building an authentication in angular. When logged in, the user should be redirected. My problem is that the router.navigate() function doesn't seem to work...
This is the code that doesn't work:
async login() {
if(!this.email || !this.password) {
return;
}
this.showSpinner = true;
this.authService.login(this.email, this.password).then(
(data) => {
this.router.navigate(['/chore/show']);
console.log(this.router);
},
(error) => {
this.error = error.message;
this.showSpinner = false;
}
);
}
The console.log does show when the login succeeds, but it won't navigate to the chore/show route.
app routes:
const routes: Routes = [
{ path: '', redirectTo: 'auth', pathMatch: 'full' },
{ path: 'auth', loadChildren: './modules/auth/auth.module#AuthModule' },
{ path: 'chore', loadChildren:
'./modules/chore/chore.module#ChoreModule', canActivate: [AuthGuard] }
];
auth module routes:
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent},
{ path: '', redirectTo: 'login', pathMatch: "full"}
];
and the chore module routing:
const routes: Routes = [
{ path: 'show', component: ShowChoreComponent},
{ path: '', redirectTo: 'show', pathMatch: 'full' },
];
For some reason i can't get it to redirect after login. Any suggestions?
EDIT: Added app routes. Code is on https://github.com/tomgelmers/stack

Checking your Code on Github, i would say the problem is that the navigation gets blocked by your AuthGuard.
The AuthGuard is calling the isLoggedIn function of your authService, which checks localstorage for userdata, while the authState subscriber is still writing to the localstorage.
This kind of thing can either work or don't work, it is pretty much luck and how fast the localstorage api works.
I recommend you check in the isLoggedIn function the variable user of your AuthService instead of localstorage. You can check localstorage in case the user variable is undefined.
That should be relatively save, however i don't know what the timing is between the login function returning and the authState subscriber firing. You might want to set the user variable in the login function. the signInWithEmailAndPassword function does return a Promise with Credentials that has a user property on it.
You could change the login function to
async login(email: string, password: string) {
return this.afAuth.signInWithEmailAndPassword(email, password).then(userData => {
this.user = userData.user
return userData
}
}
And the isLoggedIn function to
get isLoggedIn(): boolean {
if(this.user) return !!this.user
const user = JSON.parse(localStorage.getItem('user'));
return user !== null;
}
Oh and for completions sake, you should probably set the user variable to undefined or null in your logout function. Even if the user logs out, the user data still hangs around in memory

Related

How to check multiple conditions using Vue Router global navigation guard

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

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 another route in Framework7 router?

I'm using Framework7 with Vue in my project and having troubles with pretty basic stuff. In my f7 routes I want to have a guard on a / route which checks if user is logged in and then redirects to /home/ another otherwise redirects to login screen at /login/. But with the current setup redirects do not work. Any ideas why?
routes.js
const routes = [
{
path: '/',
async: function (routeTo, routeFrom, resolve, reject) {
if (isUserLoggedIn()) {
console.log(`*** redirecting to /home/`);
resolve({redirect: '/home/'});
} else {
console.log(`*** redirecting to /login/`);
resolve({redirect: '/login/'});
}
},
},
{
path: '/login/',
loginScreen: Login,
on: {
pageBeforeIn: function (event, page) {
console.log(`*** /login/ onBeforeIN`);
// never called
}
}
},
{
path: '/home/',
component: HomePage,
on: {
pageBeforeIn: function (event, page) {
console.log(`*** /home/ onBeforeIN`);
// never called
}
}
}
];
Answered by Vlad himself on F7 forum here:
async is not for redirects, you need to use route’s beforeEnter (https://framework7.io/docs/routes.html#route-before-enter-leave 1) for this:
beforeEnter(to, from, resolve, reject) {
const router = this;
if (/* need redirect */) {
reject();
router.navigate('/another-path/');
return;
}
resolve();
}
In addition to this my issue what that I had loginScreen: Login, instead of component: Login, in login route.
Making both changes did resolve the issue.

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