Vue 3 accessing vue instance in vue router. Outside a Vue file

Viewed 3766

I'm stuck with this since awhile. I want to access my keycloak instance from within the vue router.js file.

const preventRoutes = {
  beforeEnter: (to, from, next) => {
    console.log(App.$keycloak.authenticated); //here. (Undefined)
    if (store.getters.getLoginState === "true") {
      ...
    } else {
      ...
    }
  }
}

access vue 3 app instance api from non-vue fiile

I'm trying this solution. As it's the only VUE3 I can find, but still, I think he skipped some steps. Can someone simplify please?

Adviced Solution

const preventRoutes = {
  beforeRouteEnter(to, from, next) {
    next(vm => {
      // access to component instance via `vm`
      // console.log(vm.$keycloak.authenticated);
      if (this.vm.$keycloak.authenticated) {
        next();
      } else {
        next("/");
      }
    })
  }
}

main.js

const myApp = createApp(App)

let initOptions = {
  url: KeycloakConfig["auth-server-url"], realm: KeycloakConfig.realm, clientId: KeycloakConfig.resource, clientSecret: KeycloakConfig.credentials.secret
}

const keycloak = Keycloak(initOptions)

myApp.config.globalProperties.$keycloak = keycloak;
myApp.use(VueAxios, axios)
myApp.use(store)
myApp.use(router)
myApp.mount("#app");

keycloak.init({
  onLoad: "check-sso",

  checkLoginIframe: false
}).then(async (auth) => {
  if (!auth) {

  } else if (auth) {
    router.push("/dashboard"); 
  }
}).catch((e) => {
  console.log('Serwer lezy: ' + e)
})
3 Answers

I have another solution (maybe its more a workaround, but it feels kinda correct)

You could wrap your router inside a custom plugin install method, like:

const router = createRouter({
    history: createWebHashHistory(),
    routes,
});

export default {
    install(app, options) {
        router.install(app)

        router.beforeEach((to, from, next) => {
            // access all used plugins
            console.log(app.config.globalProperties.$store)
            console.log(app.config.globalProperties.$First)
            // ... stuff ...
        });
    },
};

In your main.js you can just use(...) the router like before:

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import * as plugins from "./plugins";

const app = createApp(App)

app.use(store)
   .use(plugins.First)
   .use(plugins.Second)
   .use(router, app) // add "app" as second param, that way you can access the store and every other plugin within the router
   .mount("#app");

One solution is for router.js to export a function that captures a given app argument, which could be used either in a route's beforeEnter hook:

// router.js
import { createRouter as createVueRouter, createWebHistory } from 'vue-router'

const createRoutes = (app) => [
  {
    path: '/restricted',
    component: () => import('@/views/Restricted.vue'),
    beforeEnter(to, from, next) {
                             
      const authenticated = app.config.globalProperties.$keycloak?.authenticated
      if (authenticated) {
        next()
      } else {
        next('/login')
      }
    }
  },
  //...
]

export const createRouter = (app) => {
  return createVueRouter({
    history: createWebHistory(),
    routes: createRoutes(app), 
  })
}

demo 1

...or in the global beforeEach hook:

// router.js
import { createRouter as createVueRouter, createWebHistory } from 'vue-router'

const createRoutes = () => [
  {
    path: '/restricted',
    component: () => import('@/views/Restricted.vue'),
    meta: { requiresAuth: true }, 
  },
  //...
]

export const createRouter = (app) => {
  const router = createVueRouter({
    history: createWebHistory(),
    routes: createRoutes(),
  })

  router.beforeEach((to, from, next) => {
                          
    const authenticated = app.config.globalProperties.$keycloak?.authenticated
    if (to.meta.requiresAuth && !authenticated) {
      next({ name: 'Login' })
    } else {
      next()
    }
  })
  return router
}

demo 2

Then your main.js would use it like this:

import { createRouter } from './router'

const app = createApp(App)
const router = createRouter(app)
app.use(router)

const keycloak = /*...*/

keycloak.init(/*...*/)
  .then((auth) => {
    if (auth) {
      router.push('/dashboard')
    }
  })

app.mount('#app')

The beforeEnter and the beforeRouteEnter HAVE NO access to the Vue instance.

You can either change your logic to use beforeRouteLeave that HAS access to the instance using this or you can add a callback function like:

beforeRouteEnter (to, from, next) {
  next(vm => {
    // access to component instance via `vm`
    console.log(vm.$keycloak.authenticated);
  })
}

On your specific case I would change the logic to use the beforeRouteLeave and prevent the next() function to be called in case the user is not authenticated.

You can find this information in more details here: https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards

Related