Vue 3 / Vite Broken? Cannot get Okta Widget to launch

160 Views Asked by At

I am updating a Vue2 to Vue 3/Vite (not Vue CLI) application, and am having issues getting the Vue framework to redirect to the widget. Using the okta Classic Flow.

I am following the updated Vue 3 samples. It just seems to skip the calling of or rendering of the Widget.

The issues seems to be when the '/' route is encountered, the Login.vue component is rendered, but the

          <div :id="okta-signin-container"></div>

never gets fired/called/whatever the right term is here in Login.vue .

This was working for a minute - and now, it's not. I'm working hard to find what I'm doing that is likely stupid, but I'm too close to see it. This is likely more of a Vue 3 issue than an okta issue. Thanks for any help.

Code:

*********************** main.js ***********************

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import OktaVue from '@okta/okta-vue'

import { oktaAuth } from './okta';

// 2023.06.24 Removed - this is done in okta/index.js
//const oktaAuth = new OktaAuth(authConfig.oidc);

const app = createApp(App)
    .use(router)
    .use(OktaVue, { 
        oktaAuth,
        onAuthRequired: () => {
            router.push('/login')
         },
         onAuthResume: () => {
            router.push('/login')
          }
        })
    //Added to make v-focus directive work on vue templates
    .directive( 'focus', {
        // When the bound element is inserted into the DOM...
        mounted: function (el) {
            // Focus the element
            el.focus()
        } 
    })
    .mount('#app');

*********************** router/index.js ******************

import { createRouter, createWebHistory } from 'vue-router'

// 2023.05.26 Added the two lines below to reflect new Okta signing process
import { LoginCallback, navigationGuard } from '@okta/okta-vue'

import Login from '@/components/Login.vue'
import StoreTips from '../views/StoreTips.vue';
import StoreHoursRTI from '../views/StoreHoursRTI.vue';
import StoreHoursDL from '../views/StoreHoursDL.vue';
import Payroll from '../views/Payroll.vue';
import SalesLedger from '../views/SalesLedger.vue';
import Config from '../views/Config.vue';

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'login',
      component: Login,
    },
    {
      path: '/login',
      component: Login ,
    },
    {
      // 2023.05.26 New Callback route for Okta
      path: '/login/callback',
      component: LoginCallback
    },
    {
      path: '/logout',
      beforeEnter (to, from, next) {
        Auth.logout();
        next('/');
      }
    },
    {
      path: '/help',
      name: 'help',
      //component: Help,
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/Help.vue'),
      meta: {
        requiresAuth: true
      }
    },
    {
      //DEBUG TEMPORARILY CHANGED REQUIRESAUTH TO FALSE
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/About.vue'),
      meta: {
        requiresAuth: false
      }
// .... other routes removed for brevity
    }
  ]
})
// Due to navigation guards mixin issue in vue-router-next, navigation guard logic need to be added manually
router.beforeEach(navigationGuard)

export default router

*********************** app.vue ***********************

<template>
  
  <div id="app">
    <!--<keep-alive>
        <template v-if="authenticated">
            <NavBar @user-logout="logout()">
            </NavBar>
        </template>
    </keep-alive> 
   -->

   <!-- DEBUGGING VERSION -->
    <keep-alive>
            <NavBar @user-logout="logout()">
            </NavBar>
    </keep-alive>
   
    <!-- SHOULD BE WORKING VERSION -->
    <!-- router-view inserts the router page destination here --> 
    <!-- VUE3 Version where router-view cannot be inside keep-alive -->
   <!-- $route.matched.length is being used for the Nav-Bar routing-->
    <template v-if="$route.matched.length"> 
        <router-view v-slot="{ Component }"  >
          <keep-alive>
            <component :is="Component" v-bind:authenticated="authenticated" />
          </keep-alive>
        </router-view>
    </template>


    <!-- PRIOR VERSION VUE2 that worked
      <template v-if="$route.matched.length">
            <keep-alive>
                <router-view v-bind:authenticated="authenticated"></router-view>
            </keep-alive>
        </template>
    -->

  </div>
</template> 


<script>

import { RouterLink, RouterView } from 'vue-router';

// Locally scope header component to App.vue
import NavBar from "./components/Navbar.vue";

export default {
        name: 'app',
        components: {
            NavBar
        },
        data() {
            return {
                navComp: "AppComp",
                // authenticated property for lower level components to view
                authenticated: false,
            }
        },
        async created() {
            // On creation, check for auth status
            await this.isAuthenticated();
            // Subscribe to the authStateManager to check for auth changes
            this.$auth.authStateManager.subscribe(this.isAuthenticated);
            },
        watch: {
            // Everytime the route changes, check for auth status
            '$route': 'isAuthenticated'
        },
        methods: {
            async isAuthenticated () {
                this.authenticated = await this.$auth.isAuthenticated()
            },
            async logout () {
                await this.$auth.signOut()
            }
        }
    }

</script>

<style>

    * {
        font-family: 'Work Sans', sans-serif;
        border-radius: 3px !important;
    }
    body {
        background-color: aliceblue;
    }
</style>

*********************** okta/index.js ***********************

import OktaSignIn from '@okta/okta-signin-widget'
import { OktaAuth } from '@okta/okta-auth-js'

const BASEURL = 'https://dev-xxxxx.okta.com'
const ISSUER = import.meta.env.VITE_APP_ISSUER || "https://dev-xxxxxx.okta.com/oauth2/default";
const SPA_CLIENT_ID = import.meta.env.VITE_APP_SPA_CLIENT_ID || "xxxxxxxx";
const CALLBACK_PATH = import.meta.env.VITE_APP_CALLBACK_PATH || window.location.origin + '/login/callback';
const SCOPES = ['openid', 'profile', 'email'];
const OKTA_TESTING_DISABLEHTTPSCHECK = import.meta.env.VITE_APP_OKTA_TESTING_DISABLEHTTPSCHECK || false;


const oktaSignIn = new OktaSignIn({
  baseUrl: BASEURL,
  clientId: SPA_CLIENT_ID,
  redirectUri: 'http://localhost:5173/login/callback',
  useClassicEngine: true, // Set to true to use the Classic Sign In Widget on your custom login page
  authParams: {
    pkce: true,
    issuer: ISSUER,
    display: 'page',
    scopes: SCOPES,
    testing: {
        disableHttpsCheck: OKTA_TESTING_DISABLEHTTPSCHECK
        }
    }
});
const reURI = window.location.origin;

const oktaAuth = new OktaAuth({
  issuer: ISSUER,
  clientId: SPA_CLIENT_ID,
  redirectUri: reURI + '/login/callback',
  scopes: ['openid', 'profile', 'email'],
})

export { oktaAuth, oktaSignIn };

*********************** Login.vue ***********************

<template>
  <div>
      <div :class="login" v-if="!authenticated">
          <h3>Login</h3>
          <div :id="okta-signin-container"></div>
      </div>
      <div v-else :class="login">
          <h4>Welcome! You are logged in. </h4>
          <h5>Select an option above.</h5>
          <br>
          <h6>See what's new in the <a href="/about">About screen</a></h6>
          <h6>Read how to use this site in the <a href="/help">Help screen</a></h6>
          
      </div>
  </div>
</template>

<script>
import {oktaSignIn} from '../okta'

export default {
  name: 'Login',
  props: [
    'authenticated'
  ],
  data() {
    return {}
  },
  mounted: function () {
    //nextTick waits until all child components have been rendered
    this.$nextTick(function () {
      oktaSignIn.showSignInAndRedirect(
        { el: '#okta-signin-container' },
      )
    })
  },
  unmounted () {
    // Remove the widget from the DOM on path change
    oktaSignIn.remove()
  }
}
</script>
1

There are 1 best solutions below

0
smportis On

Answering my own question here, kind of.

When I was using the colon shorthand for v-bind like I posted in the original question with a colon in front of the 'id':

<div :id="okta-signin-container"></div>

Vue 3/ Vite did not recognize the as having a valid name. In the runtime debugger using Vue Devtools, it showed "nAn" IIRC.

But when I removed the colon, thusly:

<div id="okta-signin-container"></div>

the behavior changed and it actually redirected to the Okta Widget and rendered the widget correctly.

I also noted that the VisualStudio Code display changed the appearance of the string from blue to orange. That's probably not indicative of how the compiler treats it but at least it made me notice that this might be my issue and try it without the colon. (I had added the colons upon migrating from Vue 2 to 3, as I was just trying to be a bit more precise in my coding.)

I'm not sure if this was something that I missed and was not supposed to use the colon, or if this is a Vue 3/Vite bug that needs to be fixed, or a setting that I missed.