我正在尝试使用 Vue 3 + Vue Router 4 + TypeScript设置全局路由防护。
在 Global Route Guard 内部,我正在调用 Auth0 插件。
但 TypeScript 显示错误The 'this' context of type 'Auth0Plugin' is not assignable to method's 'this' of type 'undefined'. ts(2684)
。
此错误消息使我无所适从。
这是完整的仓库,我复制了index.ts
下面的路由器文件:
import {
createRouter,
createWebHistory,
RouteRecordRaw,
RouteLocationNormalized,
} from "vue-router";
import Home from "../views/Home.vue";
import { Auth0 } from "@/auth";
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/profile",
name: "Profile",
component: () =>
import(/* webpackChunkName: "profile" */ "../views/Profile.vue"),
},
{
path: "/faunaapi",
name: "FaunaApi",
component: () =>
import(/* webpackChunkName: "faunaapi" */ "../views/FaunaApi.vue"),
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
router.beforeEach(
(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: Function
) => {
Auth0.routeGuard(to, from, next()); // <-- ERROR: The 'this' context of type 'Auth0Plugin' is not assignable to method's 'this' of type 'undefined'. ts(2684)
}
);
export default router;
这是 Auth0 插件:
import createAuth0Client, {
Auth0Client,
GetIdTokenClaimsOptions,
GetTokenSilentlyOptions,
GetTokenWithPopupOptions,
LogoutOptions,
RedirectLoginOptions,
User
} from '@auth0/auth0-spa-js'
import {App, Plugin, computed, reactive, watchEffect} from 'vue'
import {NavigationGuardWithThis} from "vue-router";
let client: Auth0Client;
interface Auth0PluginState {
loading: boolean,
isAuthenticated: boolean;
user: User | undefined,
popupOpen: boolean;
error: any
}
const state = reactive<Auth0PluginState>({
loading: true,
isAuthenticated: false,
user: {},
popupOpen: false,
error: null,
})
async function handleRedirectCallback() {
state.loading = true;
try {
await client.handleRedirectCallback();
state.user = await client.getUser();
state.isAuthenticated = true;
} catch (e) {
state.error = e;
} finally {
state.loading = false;
}
}
function loginWithRedirect(o: RedirectLoginOptions) {
return client.loginWithRedirect(o);
}
function getIdTokenClaims(o: GetIdTokenClaimsOptions) {
return client.getIdTokenClaims(o);
}
function getTokenSilently(o: GetTokenSilentlyOptions) {
return client.getTokenSilently(o);
}
function getTokenWithPopup(o: GetTokenWithPopupOptions) {
return client.getTokenWithPopup(o);
}
function logout(o: LogoutOptions) {
return client.logout(o);
}
const authPlugin = {
isAuthenticated: computed(() => state.isAuthenticated),
loading: computed(() => state.loading),
user: computed(() => state.user),
getIdTokenClaims,
getTokenSilently,
getTokenWithPopup,
handleRedirectCallback,
loginWithRedirect,
logout,
}
const routeGuard: NavigationGuardWithThis<undefined> = (to: any, from: any, next: any) => {
const {isAuthenticated, loading, loginWithRedirect} = authPlugin;
const verify = async () => {
// If the user is authenticated, continue with the route
if (isAuthenticated.value) {
return next();
}
// Otherwise, log in
await loginWithRedirect({appState: {targetUrl: to.fullPath}});
}
// If loading has already finished, check our auth state using `fn()`
if (!loading.value) {
return verify();
}
// Watch for the loading property to change before we check isAuthenticated
watchEffect(() => {
if (!loading.value) {
return verify();
}
})
}
interface Auth0PluginOptions {
domain: string,
clientId: string,
audience: string,
redirectUri: string,
onRedirectCallback(appState: any): void
}
async function init(options: Auth0PluginOptions): Promise<Plugin> {
client = await createAuth0Client({
// domain: process.env.VUE_APP_AUTH0_DOMAIN,
// client_id: process.env.VUE_APP_AUTH0_CLIENT_KEY,
domain: options.domain,
client_id: options.clientId,
audience: options.audience,
redirect_uri: options.redirectUri,
});
try {
// If the user is returning to the app after authentication
if (
window.location.search.includes('code=') &&
window.location.search.includes('state=')
) {
// handle the redirect and retrieve tokens
const {appState} = await client.handleRedirectCallback();
// Notify subscribers that the redirect callback has happened, passing the appState
// (useful for retrieving any pre-authentication state)
options.onRedirectCallback(appState);
}
} catch (e) {
state.error = e;
} finally {
// Initialize our internal authentication state
state.isAuthenticated = await client.isAuthenticated();
state.user = await client.getUser();
state.loading = false;
}
return {
install: (app: App) => {
app.provide('Auth', authPlugin);
},
}
}
interface Auth0Plugin {
init(options: Auth0PluginOptions): Promise<Plugin>;
routeGuard: NavigationGuardWithThis<undefined>
}
export const Auth0: Auth0Plugin = {
init,
routeGuard
}
我通过遵循之前不同但类似问题的答案解决了这个问题。
我删除了 的所有实例NavigationGuardWithThis<undefined>
并将其替换NavigationGuard
为 ,如下所示。
此解决方案有效。
但我不明白为什么它会起作用。
谁能解释这里发生了什么?
使用更新的代码NavigationGuard
:
import createAuth0Client, {
Auth0Client,
GetIdTokenClaimsOptions,
GetTokenSilentlyOptions,
GetTokenWithPopupOptions,
LogoutOptions,
RedirectLoginOptions,
User,
} from "@auth0/auth0-spa-js";
import { App, Plugin, computed, reactive, watchEffect } from "vue";
import { NavigationGuard } from "vue-router";
let client: Auth0Client;
interface Auth0PluginState {
loading: boolean;
isAuthenticated: boolean;
user: User | undefined;
popupOpen: boolean;
error: any;
}
const state = reactive<Auth0PluginState>({
loading: true,
isAuthenticated: false,
user: {},
popupOpen: false,
error: null,
});
async function handleRedirectCallback() {
state.loading = true;
try {
await client.handleRedirectCallback();
state.user = await client.getUser();
state.isAuthenticated = true;
} catch (e) {
state.error = e;
} finally {
state.loading = false;
}
}
function loginWithRedirect(o: RedirectLoginOptions) {
return client.loginWithRedirect(o);
}
function getIdTokenClaims(o: GetIdTokenClaimsOptions) {
return client.getIdTokenClaims(o);
}
function getTokenSilently(o: GetTokenSilentlyOptions) {
return client.getTokenSilently(o);
}
function getTokenWithPopup(o: GetTokenWithPopupOptions) {
return client.getTokenWithPopup(o);
}
function logout(o: LogoutOptions) {
return client.logout(o);
}
const authPlugin = {
isAuthenticated: computed(() => state.isAuthenticated),
loading: computed(() => state.loading),
user: computed(() => state.user),
getIdTokenClaims,
getTokenSilently,
getTokenWithPopup,
handleRedirectCallback,
loginWithRedirect,
logout,
};
const routeGuard: NavigationGuard = (to: any, from: any, next: any) => {
const { isAuthenticated, loading, loginWithRedirect } = authPlugin;
const verify = async () => {
// If the user is authenticated, continue with the route
if (isAuthenticated.value) {
return next();
}
// Otherwise, log in
await loginWithRedirect({ appState: { targetUrl: to.fullPath } });
};
// If loading has already finished, check our auth state using `fn()`
if (!loading.value) {
return verify();
}
// Watch for the loading property to change before we check isAuthenticated
watchEffect(() => {
if (!loading.value) {
return verify();
}
});
};
interface Auth0PluginOptions {
domain: string;
clientId: string;
audience: string;
redirectUri: string;
onRedirectCallback(appState: any): void;
}
async function init(options: Auth0PluginOptions): Promise<Plugin> {
client = await createAuth0Client({
// domain: process.env.VUE_APP_AUTH0_DOMAIN,
// client_id: process.env.VUE_APP_AUTH0_CLIENT_KEY,
domain: options.domain,
client_id: options.clientId,
audience: options.audience,
redirect_uri: options.redirectUri,
});
try {
// If the user is returning to the app after authentication
if (
window.location.search.includes("code=") &&
window.location.search.includes("state=")
) {
// handle the redirect and retrieve tokens
const { appState } = await client.handleRedirectCallback();
// Notify subscribers that the redirect callback has happened, passing the appState
// (useful for retrieving any pre-authentication state)
options.onRedirectCallback(appState);
}
} catch (e) {
state.error = e;
} finally {
// Initialize our internal authentication state
state.isAuthenticated = await client.isAuthenticated();
state.user = await client.getUser();
state.loading = false;
}
return {
install: (app: App) => {
app.provide("Auth", authPlugin);
},
};
}
interface Auth0Plugin {
init(options: Auth0PluginOptions): Promise<Plugin>;
routeGuard: NavigationGuard;
}
export const Auth0: Auth0Plugin = {
init,
routeGuard,
};