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 via
axiosand 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' })
}
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With