Auth0 :Vue & typeScript快速入门SDK缺少的文档

・18 分钟阅读

配置Auth0

首先,你需要设置Auth0应用程序,请阅读文档第一节

创建示例应用程序


$ vue create auth0-ts-vue

出现提示时,选择Manually select features

我们需要BabeltypescriptRouter

安装SDK

完成后,我们需要安装auth0依赖项。


$ cd auth0-ts-vue-example
$ npm install @auth0/auth0-spa-js

修改Webpack配置

如果遵循了原始的Auth0教程配置部分,那么你已经设置了URL来监听端口3000,把这个硬编码到Webpack dev-server中。

在应用程序的vue.config.js目录中创建root文件。


const webpack = require('webpack')

module.exports = {
 devServer: {
 port: 3000
 }
}


启动应用程序


$ npm run serve

创建身份验证Wrapper

在你的src 目录,创建一个auth,然后创建以下文件:index.tsauth.tsVueAuth.ts

index.ts是一个简单的桶文件。


export * from './auth'


auth.ts是我们定义插件的地方,VueAuth.ts是一个auth0-spa-jsVue对象。

定义用户


import { camelCase } from 'lodash'

export class User {
 sub: string
 names: string
 nickname: string
 picture: string
 updatedAt: string
 email: string
 emailVerified: boolean

 provider?: string
 id?: string

 givenName?: string
 familyName?: string
 locale?: string
 [key: string]: string | boolean | undefined

 constructor (auth0User: { [key: string]: string | boolean | undefined }) {
 if (!auth0User) return
 for (const key in auth0User) {
 this[key] = auth0User[key]
 }

 this.sub = auth0User.sub as string
 this.provider = this.sub.split('|')[0]
 this.id = this.sub.split('|')[1]
 }
}


看看VueAuth.ts


import { Vue, Component } from 'vue-property-decorator'
import createAuth0Client, { PopupLoginOptions, Auth0Client, RedirectLoginOptions, GetIdTokenClaimsOptions, GetTokenSilentlyOptions, GetTokenWithPopupOptions, LogoutOptions } from '@auth0/auth0-spa-js'
import { User } from './User'

export type Auth0Options = {
 domain: string
 clientId: string
 audience?: string
 [key: string]: string | undefined
}

export type RedirectCallback = (appState) => void


@Component({})
export class VueAuth extends Vue {
 loading = true
 isAuthenticated? = false
 user?: User
 auth0Client?: Auth0Client
 popupOpen = false
 error?: Error

 async getUser () {
 return new User(await this.auth0Client?.getUser())
 }

 /** Authenticates the user using a popup window */
 async loginWithPopup (o: PopupLoginOptions) {
 this.popupOpen = true

 try {
 await this.auth0Client?.loginWithPopup(o)
 } catch (e) {
 console.error(e)
 this.error = e
 } finally {
 this.popupOpen = false
 }

 this.user = await this.getUser()
 this.isAuthenticated = true
 }

 /** Authenticates the user using the redirect method */
 loginWithRedirect (o: RedirectLoginOptions) {
 return this.auth0Client?.loginWithRedirect(o)
 }

 /** Returns all the claims present in the ID token */
 getIdTokenClaims (o: GetIdTokenClaimsOptions) {
 return this.auth0Client?.getIdTokenClaims(o)
 }

 /** Returns the access token. If the token is invalid or missing, a new one is retrieved */
 getTokenSilently (o: GetTokenSilentlyOptions) {
 return this.auth0Client?.getTokenSilently(o)
 }

 /** Gets the access token using a popup window */
 getTokenWithPopup (o: GetTokenWithPopupOptions) {
 return this.auth0Client?.getTokenWithPopup(o)
 }

 /** Logs the user out and removes their session on the authorization server */
 logout (o: LogoutOptions) {
 return this.auth0Client?.logout(o)
 }

 /** Use this lifecycle method to instantiate the SDK client */
 async init (onRedirectCallback: RedirectCallback, redirectUri: string, auth0Options: Auth0Options) {
 // Create a new instance of the SDK client using members of the given options object
 this.auth0Client = await createAuth0Client({
 domain: auth0Options.domain,
 client_id: auth0Options.clientId, // eslint-disable-line @typescript-eslint/camelcase
 audience: auth0Options.audience,
 redirect_uri: redirectUri // eslint-disable-line @typescript-eslint/camelcase
 })

 try {
 // If the user is returning to the app after authentication..
 if (
 window.location.search.includes('error=') ||
 (window.location.search.includes('code=') && window.location.search.includes('state='))
 ) {
 // handle the redirect and retrieve tokens
 const { appState } = await this.auth0Client?.handleRedirectCallback() ?? { appState: undefined }

 // Notify subscribers that the redirect callback has happened, passing the appState
 // (useful for retrieving any pre-authentication state)
 onRedirectCallback(appState)
 }
 } catch (e) {
 console.error(e)
 this.error = e
 } finally {
 // Initialize our internal authentication state when the page is reloaded
 this.isAuthenticated = await this.auth0Client?.isAuthenticated()
 this.user = await this.getUser()
 this.loading = false
 }
 }
}



在原始教程中,我们在创建类时会创建Vue对象,以简化其注释。


 // The 'instance' is simply a Vue object
 instance = new Vue({
 ...
 })


现在我们打开它。

首先,我们需要导入一些类型,包括User类。

然后,为了方便起见,我们创建Auth0OptionsRedirectCallback类型别名。

我们没有创建简单的Vue对象,而是定义一个类组件,公共字段与原始中的data对象相同,而静态字段是传递给插件的参数。

Promise<RedirectCallbackResult>

最后,我们设置用户,并检查是否经过身份验证。

变成一个插件

现在,我们只需要创建一个合适的插件。

如果查看vue插件的文档,你会看到我们需要创建一个公开install方法的对象,当对象传递给Vue.use时,将调用此方法,并且该方法将接收Vue构造函数,


type Auth0PluginOptions = {
 onRedirectCallback: RedirectCallback,
 redirectUri: string,
 domain: string,
 clientId: string,
 audience?: string,
 [key: string]: string | RedirectCallback | undefined
}

export const Auth0Plugin = {
 install (Vue: VueConstructor, options: Auth0PluginOptions) {
 Vue.prototype.$auth = useAuth0(options)
 }
}


install方法中,向$auth成员添加了一个Vue

实现useAuth函数。


/** Define a default action to perform after authentication */
const DEFAULT_REDIRECT_CALLBACK = () =>
 window.history.replaceState({}, document.title, window.location.pathname)

let instance: VueAuth

/** Returns the current instance of the SDK */
export const getInstance = () => instance

/** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */
export const useAuth0 = ({
 onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
 redirectUri = window.location.origin,
 ...options
}) => {
 if (instance) return instance

 // The 'instance' is simply a Vue object
 instance = new VueAuth()
 instance.init(onRedirectCallback, redirectUri, options as Auth0Options)

 return instance
}


useAuth返回一个VueAtuh实例,并从onRedirectCallback和提取,剩下的是Auth0Options类型,直接传递给auth0Client

可以看到我们之前创建的init方法,在上面,我们还公开了一个getInstance函数,以防需要在Vue组件之外使用它。

下面看看整个auth.ts,以方便复制粘贴:


import { VueConstructor } from 'vue'
import { VueAuth, Auth0Options, RedirectCallback } from './VueAuth'

type Auth0PluginOptions = {
 onRedirectCallback: RedirectCallback,
 domain: string,
 clientId: string,
 audience?: string,
 [key: string]: string | RedirectCallback | undefined
}

/** Define a default action to perform after authentication */
const DEFAULT_REDIRECT_CALLBACK = (appState) =>
 window.history.replaceState({}, document.title, window.location.pathname)

let instance: VueAuth

/** Returns the current instance of the SDK */
export const getInstance = () => instance

/** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */
export const useAuth0 = ({
 onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
 redirectUri = window.location.origin,
 ...options
}) => {
 if (instance) return instance

 // The 'instance' is simply a Vue object
 instance = new VueAuth()
 instance.init(onRedirectCallback, redirectUri, options as Auth0Options)

 return instance
}

// Create a simple Vue plugin to expose the wrapper object throughout the application
export const Auth0Plugin = {
 install (Vue: VueConstructor, options: Auth0PluginOptions) {
 Vue.prototype.$auth = useAuth0(options)
 }
}



{
 "domain": "your tenant's domain",
 "clientId": "your app's clientId"
}


确保添加 "resolveJsonModule": true, 到tsconfig.json。

最后,我们创建main.ts


import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { Auth0Plugin } from './auth'
import { domain, clientId } from '../auth.config.json'

Vue.use(Auth0Plugin, {
 domain,
 clientId,
 onRedirectCallback: (appState) => {
 router.push(
 appState && appState.targetUrl
 ? appState.targetUrl
 : window.location.pathname
 )
 }
})

Vue.config.productionTip = false

new Vue({
 router,
 render: h => h(App)
}).$mount('#app')


登录到应用程序

首先,添加一个Login/Logout按钮到Home.vue


<template>
 <div class="home">
 <img alt="Vue logo" src="../assets/logo.png" />
 <HelloWorld msg="Welcome to Your Vue.js App" />

 <!-- Check that the SDK client is not currently loading before accessing is methods -->
 <div v-if="!$auth.loading">
 <!-- show login when not authenticated -->
 <button v-if="!$auth.isAuthenticated" @click="login">Log in</button>
 <!-- show logout when authenticated -->
 <button v-if="$auth.isAuthenticated" @click="logout">Log out</button>
 </div>
 </div>
</template>


我们还需要更新script标记Home中的逻辑


<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import HelloWorld from '@/components/HelloWorld.vue'

@Component({
 components: {
 HelloWorld
 }
})
export default class Home extends Vue {
 login () {
 this.$auth.loginWithRedirect({})
 }

 // Log the user out
 logout () {
 this.$auth.logout({
 returnTo: window.location.origin
 })
 }
}
</script>


首先,原始示例组件转换为类组件,第二,这些方法只是调用VueAuth的方法,由Auth0Plugin公开。

如果尝试编译代码,将得到以下错误:

我们需要告诉编译器已经使用$auth成员扩充了Vue构造函数。

shims-auth0.d.ts目录中创建一个src文件,如果使用VSCode,需要重新加载窗口以使错误消失。


import { VueAuth } from './auth/VueAuth'
declare module 'vue/types/vue' {
 interface Vue {
 $auth: VueAuth
 }
}


现在,尝试编译代码,如果正确配置了Auth0凭据,则在单击登录时,应重定向到Auth0 Universal Login页面,然后重新登录应用程序。

显示用户的配置文件

Profile.vue中创建一个src/views的文件。


<template>
 <div>
 <div>
 <img :src="$auth.user.picture">
 <h2>{{ $auth.user.name }}</h2>
 <p>{{ $auth.user.email }}</p>
 </div>

 <div>
 <pre>{{ JSON.stringify($auth.user, null, 2) }}</pre>
 </div>
 </div>
</template>


就这样,我们从$auth.user读取了我们在VueAuth.ts中已经设置的所有信息,

将路由添加到配置文件组件

更新应用程序的路由配置,以便用户可以访问他们的配置文件。

打开src/router/index.ts并将以下内容添加到routes数组中。


//.. other imports

// NEW - Import the profile component
import Profile from "../views/Profile.vue";

Vue.use(VueRouter)

const routes: Array<RouteConfig> = [
 routes: [
 // .. other routes and pages ..

 // NEW - add the route to the /profile component
 {
 path: "/profile",
 name: "profile",
 component: Profile
 }
 ]
});

export default router


现在我们需要更新App.vue中的导航栏


<template>
 <div id="app">
 <div id="nav">
 <router-link to="/">Home</router-link> |
 <router-link to="/about">About</router-link>
 <span v-if="$auth.isAuthenticated"> |
 <router-link to="/profile">Profile</router-link>
 </span>
 </div>
 <router-view/>
 </div>
</template>


保护配置文件页面

src/auth中创建一个authGaurd.ts的新文件。


import { getInstance } from './auth'
import { NavigationGuard } from 'vue-router'

export const authGuard: NavigationGuard = (to, from, next) => {
 const authService = getInstance()

 const fn = () => {
 // Unwatch loading
 unwatch && unwatch()
 
 // If the user is authenticated, continue with the route
 if (authService.isAuthenticated) {
 return next()
 }

 // Otherwise, log in
 authService.loginWithRedirect({ appState: { targetUrl: to.fullPath } })
 }

 // If loading has already finished, check our auth state using `fn()`
 if (!authService.loading) {
 return fn()
 }

 // Watch for the loading property to change before we check isAuthenticated
 const unwatch = authService.$watch('loading', (loading: boolean) => {
 if (loading === false) {
 return fn()
 }
 })
}


Yanyan profile image