I'm trying to develop a VueJS single page application that logs you into AAD so that I can get an access token to call various APIs (e.g. Graph).
Once a user is logged in, you have to acquire a token and there are two ways of doing this: silently (and if this fails, using the redirect experience).
However, I am unable to acquire a token using both approaches:
export default class AuthService {
constructor() {
console.log('[AuthService.constructor] started constructor');
this.app = new Msal.PublicClientApplication(msalConfig);
this.signInType = 'loginRedirect';
}
init = async () => {
console.log('[AuthService.init] started init');
await this.app.handleRedirectPromise().catch(error => {
console.log(error);
});
try {
let signedInUser = this.app.getAllAccounts()[0];
// if no accounts, perform signin
if (signedInUser === undefined) {
alert(this.app.getAllAccounts().length == 0)
await this.signIn();
signedInUser = this.app.getAllAccounts()[0];
console.log("user has been forced to sign in")
}
console.log("Signed in user is: ", signedInUser);
// Acquire Graph token
try {
var graphToken = await this.app.acquireTokenSilent(authScopes.graphApi);
console.log("(Silent) Graph token is ", graphToken);
alert(graphToken);
} catch (error) {
alert("Error when using silent: " + error)
try {
var graphToken = await this.app.acquireTokenRedirect(authScopes.graphApi);
} catch (error) {
alert ("Error when using redirect is: " + error)
}
alert("(Redirect) Graph token is " + graphToken);
}
} catch (error) {
console.log('[AuthService.init] handleRedirectPromise error', error);
}
}
signIn = async () => {
console.log('[AuthService.signIn] signInType:', this.signInType);
this.app.loginRedirect("user.read", "https://xxx.azurewebsites.net/user_impersonation");
}
signOut = () => {
this.app.logout();
}
}
Once I load the SPA, I get redirected to the AAD login page.
And then I get the following alert prompts:
Error when using silent: ClientAuthError: no_account_in_silent_request: Please pass an account object, silent flow is not supported without account information
Error when using redirect is: BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.
(Redirect) Graph token is undefined
Even though I'm signed in, why does acquireTokenSilent
think I'm not signed in?
And what does BrowserAuthError: interaction_in_progress
mean? I searched this online and the only result I found was because someone was using an outdated version of msal-browser. In my case, I'm using the latest and greatest (v2.0.1).
Update 1:
I've fixed my silent token acquisition by using the following code excerpt:
const silentRequest = {
account: signedInUser,
scopes: authScopes.graphApi.scopes1
}
var graphToken = await this.app.acquireTokenSilent(silentRequest);
Looks like I was passing my scopes in the wrong format (i.e. supposed to pass an array and it wasn't actually an array!)
There is however a discrepancy in the Microsoft documentation for how to use acquireTokenSilent
Over here, we're told to just pass in a set of scopes to the acquireTokenSilent
method. However, over here, we're told to also pass in the accountInfo alongside the scopes.
Let's see if I can get acquireTokenRedirect
working now...
Update 2:
After much trial and error, I finally got acquireTokenRedirect
working.
import * as Msal from '@azure/msal-browser';
const msalConfig = {
auth: {
clientId: "XYZ",
authority: "ABC",
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true
}
};
export default class AuthenticationService {
constructor() {
this.app = new Msal.PublicClientApplication(msalConfig)
}
init = async () => {
try {
let tokenResponse = await this.app.handleRedirectPromise();
let accountObj;
if (tokenResponse) {
accountObj = tokenResponse.account;
} else {
accountObj = this.app.getAllAccounts()[0];
}
if (accountObj && tokenResponse) {
console.log("[AuthService.init] Got valid accountObj and tokenResponse")
} else if (accountObj) {
console.log("[AuthService.init] User has logged in, but no tokens.");
try {
tokenResponse = await this.app.acquireTokenSilent({
account: this.app.getAllAccounts()[0],
scopes: ["user.read"]
})
} catch(err) {
await this.app.acquireTokenRedirect({scopes: ["user.read"]});
}
} else {
console.log("[AuthService.init] No accountObject or tokenResponse present. User must now login.");
await this.app.loginRedirect({scopes: ["user.read"]})
}
} catch (error) {
console.error("[AuthService.init] Failed to handleRedirectPromise()", error)
}
}
}
In MSAL, you can get access tokens for the APIs your app needs to call using the acquireTokenSilent method which makes a silent request(without prompting the user with UI) to Azure AD to obtain an access token.
Acquiring an access tokenreact-aad-msal exposes a getAccessToken method you can use to obtain an access token before calling an API. import { MsalAuthProvider } from "react-aad-msal"; const authProvider = new MsalAuthProvider(config, authenticationParameters, options); const accessToken = authProvider.
Sign-out with a redirect MSAL. js provides a logout method in v1, and logoutRedirect method in v2 that clears the cache in browser storage and redirects the window to the Azure AD sign-out page.
js is to first attempt a silent token request by using the acquireTokenSilent method. When this method is called, the library first checks the cache in browser storage to see if a valid token exists and returns it. When no valid token is in the cache, it attempts to use its refresh token to get the token.
Had to go through some trial and error but finally got acquireTokenRedirect
working successfully, this is the excerpt which may help others:
import * as Msal from '@azure/msal-browser';
const msalConfig = {
auth: {
clientId: "XYZ",
authority: "ABC",
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: true
}
};
export default class AuthenticationService {
constructor() {
this.app = new Msal.PublicClientApplication(msalConfig)
}
init = async () => {
try {
let tokenResponse = await this.app.handleRedirectPromise();
let accountObj;
if (tokenResponse) {
accountObj = tokenResponse.account;
} else {
accountObj = this.app.getAllAccounts()[0];
}
if (accountObj && tokenResponse) {
console.log("[AuthService.init] Got valid accountObj and tokenResponse")
} else if (accountObj) {
console.log("[AuthService.init] User has logged in, but no tokens.");
try {
tokenResponse = await this.app.acquireTokenSilent({
account: this.app.getAllAccounts()[0],
scopes: ["user.read"]
})
} catch(err) {
await this.app.acquireTokenRedirect({scopes: ["user.read"]});
}
} else {
console.log("[AuthService.init] No accountObject or tokenResponse present. User must now login.");
await this.app.loginRedirect({scopes: ["user.read"]})
}
} catch (error) {
console.error("[AuthService.init] Failed to handleRedirectPromise()", error)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With