4
votes

En utilisant vue.js et Auth0, comment puis-je ignorer la page de connexion si l'utilisateur est déjà authentifié?

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();


2 commentaires

Avez-vous vérifié le authResult dans localLogin et vérifié que tokenExpiry 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.


3 Réponses :


3
votes

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' })
  }
})


1 commentaires

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 .



0
votes

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.


0 commentaires

0
votes

J'avais été dans la même situation (avec React) et je vais essayer d'expliquer comment la résoudre.

  1. Premièrement, dans l'authentification, il est nécessaire de stocker le token côté client (localStorage / cookie peut être utilisé)

  2. Vous avez besoin d'un SecureComponent , ce composant vérifiera uniquement si le jeton stocké existe

  3. 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.

0 commentaires