DESCRIPTION J'ai un SPA
assez standard construit avec vue.js
où j'utilise Auth0 code> pour gérer la partie authentification en suivant officiel exemple. Le déroulement de l'application est le suivant:
Inscrivez-vous dans Initial.vue via Auth0 lock
-> Le rappel est appelé
-> L'utilisateur est redirigé vers / home
Tout dans le flux ci-dessus fonctionne bien mais voici le problème:
PROBLÈME
Une fois l'utilisateur enregistré et dans le / home
je veux qu'il puisse accéder à toutes les autres routes (par exemple / médecins
) s'il est authentifié et sinon il devrait être invité à se reconnecter. Selon le lien ci-dessus, cela est géré dans la fonction router.beforeEach
.
Mon problème apparaît lors de l'accès à la page de connexion /
( Initialvue
). Lorsque l'utilisateur est déjà inscrit et tente d'accéder à cette route, je veux qu'il soit redirigé vers la page / home
et ignore la page login
. J'ai essayé de l'implémenter avec une route beforeEnter
mais auth.isAuthenticated
échoue car le tokenExpiry
est nul (même si l'utilisateur est authentifié!
CODE
Mon AuthService.js
:
<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>
Mon Initial.vue :
//Form data before methods: { this.$store.dispatch(REGISTER,registerFormData) .then(() => this.$router.push('/home')); }
Mon Callback.vue
:
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 }); });
Mon router.js
:
<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>
Le 'CompleteSignup est mon formulaire d'inscription après l'enregistrement où l'utilisateur remplit un formulaire et puis le poster via
axios puis redirigé vers
/ home`:
<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>
J'utilise aussi vuetify code> et mon composant principal
App.vue
est:
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();
3 Réponses :
Vous pouvez simplifier le problème en faisant du comportement par défaut celui où votre utilisateur est connecté, puis en protégeant les routes respectives avec le garde-route.
1) pointez votre /
vers / home
2) créer une route séparée pour la connexion / "initiale"
3) utilisez le hook beforeEach
pour vous assurer qu'un utilisateur est authentifié et sinon le redirigez vers votre Initial.vue
(ou déclenchez auth.login () code > directement)
... { 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' }) } })
D'accord; ce serait une solution de contournement. Je vais l'essayer. À propos, un débogage plus poussé montre que toutes les valeurs de this
(par exemple idToken
, profile
, tokenExpiry
) sont nulles dans ce cas. Ce qui m'amène à croire que le composant n'est pas rendu lorsque j'essaie d'accéder à l'instance this
.
Je l'ai compris. Le problème réside dans le fait que les valeurs this.tokenExpiry
, this.idToken
, this.profile
reçoivent une valeur dans le vue de rappel
qui vient après la connexion
une et en plus de cela ces valeurs sont liées à l'instance de prototype Vue spécifique qui J'avais défini dans un mixin
. Ainsi, lorsque vous revenez à la page initiale, ce
n'est pas défini car il n'est pas associé à une instance de Vue spécifique.
J'avais été dans la même situation (avec React) et je vais essayer d'expliquer comment la résoudre.
Premièrement, dans l'authentification, il est nécessaire de stocker le token côté client (localStorage / cookie peut être utilisé)
Vous avez besoin d'un SecureComponent
, ce composant vérifiera uniquement si le jeton stocké existe
FetchHandler
ce composant gère chaque Application protégée
et inspectera le code de réponse, et s'il est 401 (par exemple), il redirigera un utilisateur vers le composant d'authentification. Cette étape peut être ajoutée à SecureComponent
en tant que couche supplémentaire.
Avez-vous vérifié le
authResult
danslocalLogin
et vérifié quetokenExpiry
est défini correctement en premier lieu?Oui. Après l'inscription, je peux naviguer dans l'application et je peux également voir dans la console que tokenExpiry a une valeur.