Используя vue.js и Auth0, как я могу пропустить страницу входа, если пользователь уже аутентифицирован? - PullRequest
3 голосов
/ 11 мая 2019

ОПИСАНИЕ

У меня есть довольно стандартный SPA, построенный с vue.js, где я использую Auth0 для обработки части аутентификации, следуя официальному пример.Процесс приложения выглядит следующим образом:

Register in the Initial.vue via Auth0 lock -> Callback is called -> User's redirected to /home

Все в вышеописанном потоке работает нормально, но вот проблема:

ПРОБЛЕМА

После того, как пользователь зарегистрировался и в /home я хочу, чтобы он мог получить доступ ко всем другим маршрутам (например, /doctors), если он аутентифицирован, и если нет, то он должен быть подсказчикомвойти снова.Согласно приведенной выше ссылке это обрабатывается в функции router.beforeEach.

Моя проблема появляется при доступе к странице входа в систему / (Initialvue).Когда пользователь уже зарегистрирован и пытается получить доступ к этому маршруту, я хочу, чтобы он был перенаправлен на /home и пропустил страницу login.Я пытался реализовать это с маршрутом beforeEnter, но auth.isAuthenticated не удается из-за того, что tokenExpiry имеет значение null (даже если пользователь аутентифицирован!

CODE

Мой 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();

Мой 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>

Мой 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>

Мой 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 });
});

'CompleteSignup is my signup form after registering where the user's is filling out a form and then posting it via axios and then redirected to / home`:

//Form data before
methods: {
this.$store.dispatch(REGISTER,registerFormData)
          .then(() => this.$router.push('/home'));
}

Я также использую vuetify, и мой основной компонент App.vue:

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

Ответы [ 3 ]

2 голосов
/ 13 мая 2019

Вы можете упростить проблему, установив поведение по умолчанию так, как если бы пользователь вошел в систему, а затем защитить соответствующие маршруты с помощью средства защиты маршрутов.

1) укажите / на /home
2) создать отдельный маршрут для входа в систему / "intial"
3) использовать хук beforeEach для проверки подлинности пользователя и, если нет, перенаправить его на ваш Initial.vue (или напрямую вызвать auth.login())

...
  {
    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' })
  }
})
0 голосов
/ 20 мая 2019

Я был в такой же ситуации (с React), и я попытаюсь объяснить, как ее решить.

  1. Во-первых, при аутентификации необходимо хранить токен нана стороне клиента (можно использовать localStorage / cookie)

  2. Вам необходимо SecureComponent, этот компонент будет проверять только, существует ли сохраненный токен

  3. FetchHandler этот компонент обрабатывает каждый Protected Application и проверяет код ответа, а если он равен 401 (например), он перенаправляет пользователя на компонент аутентификации.Этот шаг можно добавить к SecureComponent в качестве дополнительного слоя.
0 голосов
/ 17 мая 2019

Я понял это. Проблема состоит в том, что значениям this.tokenExpiry, this.idToken, this.profile присваивается значение в представлении callback, которое появляется после login one и в В дополнение к этому эти значения привязаны к конкретному экземпляру прототипа Vue, который я определил в mixin. Поэтому при переходе к начальной странице this не определено, поскольку оно не связано с конкретным экземпляром Vue.

...