Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Okta Auth plugin internally fails with an undefined instance of this.oktaAuth which leads to our Web SPA failing on startup and each route change. #14

Open
2 tasks
CVANCGCG opened this issue Jun 25, 2020 · 7 comments

Comments

@CVANCGCG
Copy link

CVANCGCG commented Jun 25, 2020

I'm submitting a:

  • [X ] Bug report I read through this related issue here but did not find an applicable solution.
  • Feature request
  • Other (Describe below)

Current behavior

Our Vue single page application fails on startup and displays a blank page stemming from an error that originates from within the Okta Plugin within @okta/okta-vue/Auth.js that indicates the internal instance of await oktaAuth is undefined and fails to establish a user identity from an accessToken/idToken.

Uncaught (in promise) TypeError: Cannot read property 'oktaAuth' of undefined
    at _callee$ (webpack-internal:///./src/router/index.js:94)
    at tryCatch (webpack-internal:///./node_modules/regenerator-runtime/runtime.js:62)
    at Generator.invoke [as _invoke] (webpack-internal:///./node_modules/regenerator-runtime/runtime.js:296)
    at Generator.prototype.<computed> [as next] (webpack-internal:///./node_modules/regenerator-runtime/runtime.js:114)
    at step (webpack-internal:///./node_modules/@vue/babel-preset-app/node_modules/@babel/runtime/helpers/builtin/es6/asyncToGenerator.js:12)
    at _next (webpack-internal:///./node_modules/@vue/babel-preset-app/node_modules/@babel/runtime/helpers/builtin/es6/asyncToGenerator.js:27)

Expected behavior

We expect the normal behavior where the redirect to Okta to result in either in a successful login or refresh of the existing access and id token. In either case we expect the library to always internally resolve to a defined (non-null and non-undefined) instance in all of the references to: "this.oktaAuth"

Minimal reproduction of the problem with instructions

  1. Create new Vue application
  2. Add this route initializer
    Note: In the code snippet below, you see that our initialization of the Auth plugin uses defaults that result in autoRenew: true and the use of local storage. And because our application displays confidential data, we take a vigilant approach to checking the user on each route change that uses the router::beforeEach() and router::beforeResolve() event hooks. The intent is to ensure we authenticate users and renew session tokens via router::beforeEach() event and subsequently invalidate/synchronize the user in the router::beforeResolve() event handler.
import Auth from '@okta/okta-vue'
import store from '@state/store'

Vue.use(Auth, {
  issuer: process.env.VUE_APP_OKTA_ISSUER_URL,
  clientId: process.env.VUE_APP_OKTA_CLIENT_ID,
  redirectUri: `${window.location.origin}/implicit/callback`,
  scopes: ['openid', 'profile', 'email'],
  pkce: true,
})

Vue.use(VueRouter)
Vue.use(VueMeta, {
  // The component option name that vue-meta looks for meta info on.
  keyName: 'page',
})

const router = new VueRouter({
  routes: allRoutes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  },
})
  1. Add these pre and post route guards to ensure only authenticated users are granted access
router.beforeEach(Vue.prototype.$auth.authRedirectGuard())

router.beforeResolve(async (routeTo, routeFrom, next) => {
  // Fetch actively logged in use from IDP and conditionally update store
  const authenticatedIDPUser = await Vue.prototype.$auth.getUser()
  if (!authenticatedIDPUser) {
    debugger
    alert('[router.beforeResolve] "$auth.getUser()" returned null/undefined')
  }
  const isAuthorizedUserLoggedIn = store.getters['auth/isLoggedIn']
  const synchronizeUserStoreRequired = authenticatedIDPUser && !isAuthorizedUserLoggedIn
  if (synchronizeUserStoreRequired) {
    await store.dispatch('auth/onLoggedIn', Vue.prototype.$auth)
  }

  // If we reach this point, continue resolving the route.
  next()
})
  1. start application via npm run serve

Extra information about the use case/user story you are trying to implement

We need to ensure each user is authenticated and has a valid access token before access is granted to the application and data is displayed via APIs.

Environment

  • Package version: 1.1.1
  • Vue version: 2.6.10
  • Browser: Chrome
  • OS: MacOS Mojave, i.e. OSX 10.14.5
  • Node version (node -v): 12.0.0
  • Other:
@CVANCGCG CVANCGCG changed the title Okta Auth plugin internally fails with an undefined instance of this.oktaAuth Okta Auth plugin internally fails with an undefined instance of this.oktaAuth which leads to our Web SPA failing on startup and each route change. Jun 26, 2020
@CVANCGCG
Copy link
Author

CVANCGCG commented Jul 1, 2020

image

@ca

@CVANCGCG
Copy link
Author

CVANCGCG commented Jul 6, 2020

Also an update: as we employ workarounds, we are noticing this logged error:

Error in [router.beforeResolve] maintaining profile: OAuthError: The client specified not to prompt, but the user is not logged in.

When searching for this topic, I come across this lively Github issue here. Is there a known solution for this problem that has yet to be officially documented?

@aarongranick-okta
Copy link
Contributor

@CVANCGCG Based on the error "Cannot read property 'oktaAuth' of undefined", It looks like Vue.prototype.$auth has not been initialized. This is done in the install() method defined here: https://github.com/okta/okta-oidc-js/blob/master/packages/okta-vue/src/okta-vue.js#L6

It could be that the install method has not been called yet. It may also be possible that the constructor is throwing an exception. If you are able to set a breakpoint in the debugger in this function you should be able to see what is happening. okta-vue is distributed with external source maps, you can load these into your app using the source-map-loader plugin for webpack.

This error: "OAuthError: The client specified not to prompt, but the user is not logged in." will occur during token renew or any call to token.getWithoutPrompt when there is not a currently active Okta session. It is probably not related to this issue, which I suspect is either about timing or configuration.

@CVANCGCG
Copy link
Author

CVANCGCG commented Jul 9, 2020

@aarongranick-okta, I've enabled source maps in vue.config.js as follows.

const { sslConfig, registerMiddleware } = require('./server/util')

module.exports = {
  transpileDependencies: ['vuetify'],
  css: {
    // Enable CSS source maps.
    sourceMap: true,
    // Note!: We use 'The easy solution' outlined at https://joshuatz.com/posts/2019/vue-mixing-sass-with-scss-with-vuetify-as-an-example/
    //        It is critical to use the 'scss' instead of 'sass' key to use SCSS flavor of SASS
    loaderOptions: {
      scss: {
        data: `@import "src/styles/base/_variables.scss";`
      }
    }
  },
  // Configure Webpack's dev server.
  // https://cli.vuejs.org/guide/cli-service.html
  devServer: {
    before: registerMiddleware,
    host: 'localhost',
    // We don't have the ssl certs in Codebuild
    https: process.env.CODEBUILD_BUILD_ID ? false : sslConfig(),
    progress: false,
    allowedHosts: ['localhost', '127.0.0.1', '.amazonaws.com', '.capgroup.com', '.oktapreview.com', '.okta.com']
  },
  configureWebpack: {
    devtool: 'eval-source-map',
    entry: './src/main.ts',
    // stats: 'errors-warnings',
    plugins: [],
  },
}

Unfortunately the source code I'm debugging is a tokenized version and not what I expect. Here's a screenshot.
image

Do you see anything wrong that is causing that esoteric transformed JS code? Suppose we fix the source map for the okta distribution, what would we walk it back to in our application code? Do you see anything unexpected with my initialization of the Auth plugin and use of route guards? It appears to be standard and consistent with the Okta developer guidance?

@aarongranick-okta
Copy link
Contributor

To include sourcemaps, the source-map-loader should be added to the webpack config, under the "module / rules" section, as described here: https://webpack.js.org/loaders/source-map-loader/

Having the sourcemaps will make it easier to debug the code. This line in your code:

Vue.use(Auth, {
  issuer: process.env.VUE_APP_OKTA_ISSUER_URL,
  clientId: process.env.VUE_APP_OKTA_CLIENT_ID,
  redirectUri: `${window.location.origin}/implicit/callback`,
  scopes: ['openid', 'profile', 'email'],
  pkce: true,
})

should create an instance of the auth service and attach it to the Vue prototype as Vue.prototype.$auth. This may not happen if an exception is thrown during this step. (The exception might be caught by Vue, try enabling "Break on all exceptions" in your Chrome developer tools (by default it breaks only on UNCAUGHT exceptions).

The most common reasons why an error would occur during construction are invalid configuration or insufficient browser capabilities (such as on IE, which requires a polyfill).

The error trace shows the error being thrown at ./src/router/index.js:94 Definitely put a breakpoint or try/catch at or before this location. Hopefully debugging will be able to reveal why this variable is undefined.

@aarongranick-okta
Copy link
Contributor

The problem may be caused by the logic in router.beforeResolve running on the callback route. The callback route should not contain any logic, including reading tokens or retrieving user info. It is in the middle of an auth flow and should complete before any questions are asked. If you had a block if (routeTo === '/implicit/callback') return it might fix it for you.

@CVANCGCG
Copy link
Author

Hi @aarongranick-okta, I appreciate the suggestion and will test this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants