我正在尝试从 Azure 获取访问令牌,但在运行代码时遇到错误:
BrowserAuthError:uninitialized_public_client_application:在尝试调用任何其他 MSAL API 之前,必须调用并等待初始化函数。
有人可以帮我解决这个问题吗?
这是我的 msalConfig.ts
export const msalConfig = {
auth: {
clientId: import.meta.env.VITE_APP_CLIENT_ID,
authority: `https://login.microsoftonline.com/${import.meta.env.VITE_APP_TENANT_ID}`,
redirectUri: "https://localhost/spa/sign-in",
postLogoutUrl: "https://localhost",
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: false,
},
};
export const loginRequest = {
scopes: ["User.Read"],
};
我的initializeMsal.ts代码:
import { PublicClientApplication } from "@azure/msal-browser";
import { msalConfig } from "@/config/msalConfig";
let msalInstance: PublicClientApplication | null = null;
export const initializeMsal = async (): Promise<PublicClientApplication> => {
if (!msalInstance) {
msalInstance = new PublicClientApplication(msalConfig);
}
return msalInstance;
};
我的main.ts:
import { createApp } from 'vue';
import App from './App.vue';
import { initializeMsal } from './msalAuthService';
const initApp = async () => {
try {
await initializeMsal();
createApp(App).mount('#app');
} catch (error) {
console.error('Failed to initialize MSAL', error);
}
};
initApp();
我的 msalAuthService.ts:
import type { AccountInfo, AuthenticationResult } from "@azure/msal-browser";
import { loginRequest } from "@/config/msalConfig";
import { initializeMsal } from "@/config/initializeMsal";
export const login = async (): Promise<AuthenticationResult | null> => {
try {
const msalInstance = await initializeMsal();
return await msalInstance.loginPopup(loginRequest);
} catch (error) {
console.error(error);
return null;
}
};
export const logout = async (): Promise<void> => {
try {
const msalInstance = await initializeMsal();
await msalInstance.logout();
} catch (error) {
console.error(error);
}
};
export const getAccount = async (): Promise<AccountInfo | null> => {
const msalInstance = await initializeMsal();
const accounts = msalInstance.getAllAccounts();
return accounts.length > 0 ? accounts[0] : null;
};
export const acquireToken = async (): Promise<string | null> => {
const msalInstance = await initializeMsal();
try {
const account = getAccount();
if (!account) {
throw new Error('No signed in account found.');
}
const response = await msalInstance.acquireTokenSilent({
...loginRequest,
account,
});
return response.accessToken;
} catch (error) {
console.error(error);
return null;
}
};
BrowserAuthError:uninitialized_public_client_application:在尝试调用任何其他 MSAL API 之前,必须调用并等待初始化函数。
我的环境中也遇到了上述错误。我尝试了一个示例 Vue.js 项目作为前端,JavaScript 作为后端来获取访问令牌。
代码:
vueapp/src/components/AuthButton.vue:
<template>
<div>
<button @click="redirectToAuth">Login with Microsoft</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AuthButton',
methods: {
redirectToAuth() {
const clientId = process.env.VUE_APP_CLIENT_ID;
const redirectUri = 'http://localhost:3000/callback';
const authUrl = `https://login.microsoftonline.com/${process.env.VUE_APP_TENANT_ID}/oauth2/v2.0/authorize`;
const scope = encodeURIComponent('openid profile offline_access');
const authorizationUrl = `${authUrl}?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}`;
window.location.href = authorizationUrl;
}
}
});
</script>
vueapp/src/views/HomePage.vue:
<template>
<div>
<h1>Welcome to the Vue Azure Auth App</h1>
<AuthButton />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import AuthButton from '@/components/AuthButton.vue';
export default defineComponent({
name: 'HomePage',
components: {
AuthButton
}
});
</script>
vueapp/src/views/OAuthCallback.vue:
<template>
<div>
<p v-if="error">{{ error }}</p>
<p v-if="accessToken">Access Token: {{ accessToken }}</p>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import axios from 'axios';
const accessToken = ref('');
const error = ref('');
onMounted(async () => {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
console.log('Authorization Code:', code);
if (code) {
try {
const response = await axios.post('/api/token', { code });
console.log('Token Response:', response.data);
accessToken.value = response.data.access_token;
} catch (err) {
console.error('Error exchanging authorization code for access token:', err);
error.value = 'Failed to obtain access token';
}
}
});
</script>
<style scoped>
</style>
vueapp/src/main.ts:
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App).use(router).mount('#app');
vueapp/src/router.ts:
import { createRouter, createWebHistory } from 'vue-router';
import HomePage from '@/views/HomePage.vue';
import OAuthCallback from '@/views/OAuthCallback.vue';
const routes = [
{ path: '/', component: HomePage },
{ path: '/callback', component: OAuthCallback },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
vueapp/src/App.vue:
<template>
<router-view />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App'
});
</script>
<style scoped>
</style>
vueapp/vue.config.js :
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true
}
}
}
};
vueapp/.env:
VUE_APP_CLIENT_ID='<CLIENT_ID>'
VUE_APP_TENANT_ID='<TENANT_ID>'
后端/server.js :
const express = require('express');
const axios = require('axios');
const bodyParser = require('body-parser');
require('dotenv').config();
const app = express();
const port = 3001;
app.use(bodyParser.json());
app.post('/api/token', async (req, res) => {
const { code } = req.body;
const tokenUrl = `https://login.microsoftonline.com/${process.env.VUE_APP_TENANT_ID}/oauth2/v2.0/token`;
const params = new URLSearchParams({
grant_type: 'authorization_code',
client_id: process.env.VUE_APP_CLIENT_ID,
client_secret: process.env.VUE_APP_CLIENT_SECRET,
code: code,
redirect_uri: 'http://localhost:3000/callback',
});
try {
const response = await axios.post(tokenUrl, params, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
res.json(response.data);
} catch (error) {
console.error('Error occured on getting access token:', error);
res.status(500).send('Internal Server Error');
}
});
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
后端/.env:
VUE_APP_CLIENT_ID='<CLIENT_ID>'
VUE_APP_CLIENT_SECRET='<CLIENT_SECRET>'
VUE_APP_TENANT_ID='<TENANT_ID>'
后端/package.json:
{
"name": "backend",
"version": "1.0.0",
"main": "server.js",
"dependencies": {
"axios": "^0.21.1",
"body-parser": "^1.19.0",
"dotenv": "^8.2.0",
"express": "^4.17.1"
}
}
我在 Azure AD 应用程序的 Authentication 中添加了以下 URL 作为 单页应用程序 和 Web:
http://localhost:3000
http://localhost:3000/callback
输出:
我运行后端项目如下:
然后,我运行了Vue.js项目,如下所示:
浏览器输出:
我在浏览器中获得了以下内容以及 Vue.js 输出 URL,然后单击 用 Microsoft 登录:
我获得的访问令牌如下所示: