vue.jsvue-routervuejs3oktavue-composition-api

How to set up a Vue app with Okta auth with Composition API


I am setting up a basic Vue authentication app using Okta, based on the following: https://github.com/okta/okta-vue. I am attempting to convert the logic that handles routing and signin redirecting from Options API to Composition API. In other words, the login/logout functionality in App.vue, which is currently written like this:

    async login () {
      await this.$auth.signInWithRedirect()
      this.$router.push('/protected')
    },
    async logout () {
      await this.$auth.signOut()
    }

https://github.com/okta/okta-vue#show-login-and-logout-buttons

Would probably look something like this in Composition API (I think):

    setup() {
      const router = useRouter()
      
      const login = () => {
        this.$auth.signInWithRedirect()
      }

      const logout = () => {
        this.$auth.signOut()
      }

      return {login, logout, router}
    }

However, I'm not sure how to change this.$auth.signInWithRedirect() and this.$auth.signOut() to work with Composition API, since $auth doesn't appear to be an accepted property. How can I set up the Okta methods signInWithRedirect() and signOut() to work with composition API? I'm new to Composition API and Vue in Typescript. Feedback is appreciated!

Here is the rest of the code below:

App.vue

<template>
  <div id="app">
    <button v-if='authState && authState.isAuthenticated' @click='logout' id='logout-button'> Logout </button>
    <button v-else @click='login' id='login-button'> Login </button>
    <router-view/>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { useRouter } from 'vue-router'

export default defineComponent({
  name: 'app',
    setup() {
      const router = useRouter()
      
      const login = () => {
        this.$auth.signInWithRedirect()
        router.push('/protected')
      }

      const logout = () => {
        this.$auth.signOut()
      }

      return {login, logout, router}
    }
  
})
</script>

main.ts

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

const oktaAuth = new OktaAuth({
    issuer: 'https://{dev-id}.okta.com/oauth2/default',
    clientId: '{clientId}',
    redirectUri: 'http://localhost:8080/login/callback',
    scopes: ['openid', 'profile', 'email']
  })

  const app = createApp(App)
  app.use(OktaVue, { oktaAuth })
  app.use(router)
  app.mount('#app')

router/index.ts

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { LoginCallback } from '@okta/okta-vue'
import { navigationGuard } from '@okta/okta-vue'
import Protected from '../views/Protected.vue'

const routes: Array<RouteRecordRaw> = [
  { 
    path: '/login/callback', 
    component: LoginCallback 
  },
  {
    path: '/protected',
    name: 'Protected',
    component: Protected,
    meta: {
      requiresAuth: true
    }
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

router.beforeEach(navigationGuard)

export default router

Solution

  • The okta-vue plugin configures a global property named $auth (which enables this.$auth usage in the Options API). The property's value is actually the same oktaAuth instance passed into the plugin via app.use(OktaVue, { oktaAuth }) (i.e., this.$auth is set to oktaAuth).

    In the Composition API, you could access the app's global properties via getCurrentInstance():

    // MyComponent.vue
    <script lang="ts">
    import { getCurrentInstance } from 'vue'
    import type { OktaAuth } from '@okta/okta-auth-js'
    
    export default {
      setup() {
        const oktaAuth = getCurrentInstance()!!.appContext.app.config.globalProperties.$auth as OktaAuth
        const login = () => oktaAuth.signInWithRedirect()
        const logout = () => oktaAuth.signOut()
        ā‹®
      }
    }
    </script>
    

    Another approach is to move the oktaAuth instance into a shared file that could be imported when needed, given that oktaAuth is the same as the $auth global property:

    // auth.ts
    import { OktaAuth } from '@okta/okta-auth-js'
    
    export const oktaAuth = new OktaAuth({
      issuer: `https://${process.env.VUE_APP_OKTA_DOMAIN}/oauth2/default`,
      clientId: `${process.env.VUE_APP_OKTA_CLIENT_ID}`,
      redirectUri: window.location.origin + '/login/callback',
      scopes: ['openid', 'profile', 'email'],
      pkce: true,
    })
    
    // main.ts
    import OktaVue from '@okta/okta-vue'
    import { oktaAuth } from './auth' šŸ‘ˆ
    
    const app = createApp(App)
    app.use(OktaVue, { oktaAuth })
    ā‹®
    
    // MyComponent.vue
    <script lang="ts">
    import { oktaAuth } from './auth' šŸ‘ˆ
    
    export default {
      setup() {
        const login = () => oktaAuth.signInWithRedirect()
        const logout = () => oktaAuth.signOut()
        ā‹®
      }
    }
    </script>
    

    demo