I am trying to implement a login using the azure-msal-browser package in a Vue2 application.
I am struggling to get it running even after checking a couple of different tutorials.
My plan is as follows: (a) i want to have a seperate service which contains the login and getbearertoken method (b) this service is initalized in the beforeCreate() of my Vue instance so that i can only access the page after login and (c) the get tokenmethod should be usable from any other .ts file in my project so that i can append this to an post / get request.
So far my login is working, but i am struggling to get the token:
import { msalConfig, useAuthStore } from '@/stores/auth'
import { PublicClientApplication } from '@azure/msal-browser'
class MsalService {
msalInstance: PublicClientApplication
constructor() {
this.msalInstance = new PublicClientApplication(msalConfig)
this.initialize()
}
async initialize() {
try {
await this.msalInstance.initialize()
} catch (error) {
console.log('Initialization error', error)
}
}
login() {
const authStore = useAuthStore()
this.msalInstance
.handleRedirectPromise()
.then((response: any) => {
if (!response) {
this.msalInstance.loginRedirect()
} else {
authStore.setUser(response.account)
}
})
.catch((error: any) => {
console.error('Failed to handle redirect:', error)
})
}
async getToken(): Promise<any> {
try {
const accounts = this.msalInstance.getAllAccounts()
if (accounts.length === 0) {
throw new Error('No accounts found. Please login first.')
}
const silentRequest = {
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`],
account: accounts[0]
}
const silentResponse = await this.msalInstance.acquireTokenSilent(silentRequest)
return silentResponse.accessToken
} catch (error) {
console.error('Token acquisition error:', error)
throw error
}
}
}
export const msalService = new MsalService()
In my main.ts the code looks like this
import { msalService } from "@/stores/auth"
export const instance = new Vue({
router,
...
beforeCreate() {
msalService.initialize().then(() => {
msalService.login()
})
}
}
console.log(await msalService.getToken())
this getToken() call returns a "BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API" error in the console.
As mentioned, the login works and i am asked to input my credentials. What might be the issue?
I tried the below Vue2 application using the @azure/msal-browser
package to log in and retrieve the access token. I successfully logged in and retrieved the access token.
src/services/msalservice.ts :
import { PublicClientApplication, AuthenticationResult } from '@azure/msal-browser';
import { config } from '../Config';
class MsalService {
private msalInstance: PublicClientApplication;
private isInitialized: boolean = false;
constructor() {
this.msalInstance = new PublicClientApplication({
auth: {
clientId: config.appId,
redirectUri: config.redirectUri,
authority: config.authority,
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true,
},
});
}
public async initialize(): Promise<void> {
try {
await this.msalInstance.initialize();
this.isInitialized = true;
console.log('MSAL instance initialized successfully');
} catch (error) {
this.isInitialized = false;
console.error('Error initializing MSAL instance:', error);
throw error;
}
}
private checkInitialization() {
if (!this.isInitialized) {
throw new Error("MSAL has not been initialized yet.");
}
}
public getMsalInstance() {
this.checkInitialization();
return this.msalInstance;
}
public async login(): Promise<AuthenticationResult | null> {
this.checkInitialization();
try {
const loginResponse = await this.msalInstance.loginPopup({
scopes: config.scopes,
prompt: 'select_account',
});
return loginResponse;
} catch (error) {
console.error(error);
return null;
}
}
public async getAccessToken(): Promise<string | null> {
this.checkInitialization();
try {
const account = this.msalInstance.getAllAccounts()[0];
if (account) {
const tokenResponse = await this.msalInstance.acquireTokenSilent({
scopes: config.scopes,
account: account,
});
return tokenResponse.accessToken;
}
return null;
} catch (error) {
console.error(error);
return null;
}
}
public logout(): void {
this.checkInitialization();
this.msalInstance.logout();
}
}
export const msalService = new MsalService();
src/components/App.vue :
<template>
<div id="app">
<h1>MSAL Vue 2 App</h1>
<div v-if="!account">
<button @click="login">Login</button>
</div>
<div v-else>
<p>Welcome, {{ account.name }}</p>
<p v-if="accessToken">
<strong>Access Token:</strong>
<pre>{{ accessToken }}</pre>
</p>
<button @click="logout">Logout</button>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { msalService } from '../services/msalService';
export default Vue.extend({
data() {
return {
account: null as any,
accessToken: null as string | null,
isMsalInitialized: false,
};
},
async mounted() {
try {
await msalService.initialize();
this.isMsalInitialized = true;
console.log('MSAL instance initialized');
const msalInstance = msalService.getMsalInstance();
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
this.account = accounts[0];
await this.fetchAccessToken();
}
} catch (error) {
console.error('Error initializing MSAL:', error);
}
},
methods: {
async login() {
if (!this.isMsalInitialized) {
console.error('MSAL is not initialized yet.');
return;
}
try {
const response = await msalService.login();
if (response) {
this.account = response.account;
await this.fetchAccessToken();
}
} catch (error) {
console.error('Login error:', error);
}
},
async fetchAccessToken() {
try {
this.accessToken = await msalService.getAccessToken();
} catch (error) {
console.error('Error fetching access token:', error);
}
},
logout() {
msalService.logout();
this.account = null;
this.accessToken = null;
}
},
});
</script>
src/Config.ts :
export const config = {
appId: '<clientID>',
redirectUri: 'http://localhost:8080',
scopes: ['openid', 'profile', 'user.read'],
authority: 'https://login.microsoftonline.com/<tenantID>',
};
main.ts :
import Vue from 'vue';
import App from './components/App.vue';
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
}).$mount('#app');
I have added the below redirect URI in the service principle's Authentication under Single-page application.
http://localhost:8080
Output :
I successfully ran the Vue2 project and got the below output in the browser.
I successfully logged in and retrieved the access token.