I am trying to implement MSAL auth via Redirect in Vue js.
I have followed the official guide but when I open an app, handleRedirect is started, then it redirects to a blank page and hangs there, the console looks like this:
handleRedirectPromise called but there is no interaction in progress, returning null.
Emitting event: msal:acquireTokenStart
Emitting event: msal:handleRedirectEnd
Null, no response
Emitting event: msal:acquireTokenStart
I cannot get how to implement the redirect flow (popup flow is not an option).
My config:
// login
var msalConfig = {
auth: {
clientId: clientId,
authority: this.authority, //https://login.microsoftonline.com/common/ default
redirectUri: this.redirect + 'redirects/login-msal-powerbi/index.html', // blank page
postLogoutRedirectUri: null
},
cache: {
cacheLocation: "localStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
},
allowRedirectInIframe: true
}
My main LoginUser function:
// login
loginUser: function() {
var that = this;
// Prepare config
this.msalInstance = new msal.PublicClientApplication(msalConfig);
// Register Callbacks for Redirect flow
var request = this.msalGetRequestLoginRedirect(this.powerbi.scopes, this.login.aadTenant); // returns an object with authority and scopes
this.msalInstance.handleRedirectPromise()
.then(function(responce) {
that.msalHandleResponse(responce, that.msalInstance);
})
.catch((error) => {
console.log(error);
});
My msalHandleResponse function:
msalHandleResponse: function(response, msalInstance) {
if (response !== null) {
console.log(response);
}else {
console.error('null!!') // returns null
return msalInstance.loginRedirect({scopes:[
"https://analysis.windows.net/powerbi/api/Dashboard.Read.All",
"https://analysis.windows.net/powerbi/api/Dataset.Read.All",
"https://analysis.windows.net/powerbi/api/Report.Read.All",
"https://analysis.windows.net/powerbi/api/Group.Read.All",
"https://analysis.windows.net/powerbi/api/Workspace.Read.All",
"https://analysis.windows.net/powerbi/api/UserState.ReadWrite.All"
], responseMode:"query"});
}
},
My acquireTokenRedirect function:
msalGetTokenRedirect: function(msalInstance) {
var that = this;
msalInstance.acquireTokenSilent(this.requestsObj.silentRequest)
.then(function(tokenResponse) {
// Optionally make something with tokenResponse
console.log("Access token acquired silently...");
return tokenResponse;
}).catch(function(error) {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return msalInstance.acquireTokenRedirect(this.requestsObj.request)
.then(function(tokenResponse) {
console.log('TOKEN RESPONSE',tokenResponse);
}).catch(function(error) {
console.error(error);
});
}
});
},
Are you calling handleRedirectPromise on your redirectUri (blank page)?
I am working with the FaceBook JavaScript SDK to log users in via FaceBook SSO. documentation
Clicking the SSO button opens a new window prompting the user to log in via FaceBook. If the user logs in there is no issue. However, if the user exits or cancels the SSO, I am not getting a response from FB.
Here is the function given in the FB documentation:
FB.login(function(response) {
if (response.authResponse) {
console.log('Welcome! Fetching your information.... ');
FB.api('/me', function(response) {
console.log('Good to see you, ' + response.name + '.');
});
} else {
console.log('User cancelled login or did not fully authorize.');
}
});
I have tried using this function exactly and again if I cancel the SSO the else block never runs as I would expect it to.
I am trying to modify the function slightly but I still am not able to get a response when the page is cancelled.
static login(callbacks, { mode = "login" }) {
const FB = window.FB;
FB.login(
(res) => {
console.log(res);
const { authResponse } = res;
if (mode === "login") {
Facebook.socialSignOn(authResponse, callbacks);
} else if (mode === "verify") {
Facebook.socialVerify(authResponse, callbacks);
}
},
{ return_scopes: "true", scope: "email" }
);
}
UPDATE
After playing around more it seems like if I try to use the FB SSO, cancel, and then try again all future FB.login() attempts immediately receive a response with authResponse : null
I am building SPA with react and I run into a problem with code directly from Azure Portal - Quickstart for Javascript. (you can download full code there)
If I create-react-app and I use the code it works just fine and I can authenticate, get token and use it in the post request.
But If I use the SAME code in my app (which is already styled and with all the functionality I need) it gives me Multiple authorities found in the cache. Pass authority in the API overload.|multiple_matching_tokens_detected` error when I authenticate.
Just to clarify authentication goes through and I see I am authenticated, just this error is bugging me and I have no idea how to debug it.
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
console.log(idToken); //note that I can get here!
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error); //this is where error comes from
// Call acquireTokenPopup (popup window) in case of acquireTokenSilent failure due to consent or interaction required ONLY
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenPopup(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
});
}
});
}
The main thing I don`t understand is that the same code works just fine in the fresh create-react-app project, but as I use it in an already existing project (just without authentication) it breaks with mentioned error.
Full code
import React, { Component } from 'react'
import * as Msal from 'msal'
export class test extends Component {
render() {
var applicationConfig = {
clientID: '30998aad-bc60-41d4-a602-7d4c14d95624', //This is your client ID
authority: "https://login.microsoftonline.com/35ca21eb-2f85-4b43-b1e7-6a9f5a6c0ff6", //Default authority is https://login.microsoftonline.com/common
graphScopes: ["30998aad-bc60-41d4-a602-7d4c14d95624/user_impersonation"],
graphEndpoint: "https://visblueiotfunctionapptest.azurewebsites.net/api/GetDeviceList"
};
var myMSALObj = new Msal.UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, acquireTokenRedirectCallBack,
{storeAuthStateInCookie: true, cacheLocation: "localStorage"});
function signIn() {
myMSALObj.loginPopup(applicationConfig.graphScopes).then(function (idToken) {
//Login Success
console.log(idToken);
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}, function (error) {
console.log(error);
});
}
function signOut() {
myMSALObj.logout();
}
function acquireTokenPopupAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
// Call acquireTokenPopup (popup window) in case of acquireTokenSilent failure due to consent or interaction required ONLY
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenPopup(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
});
}
});
}
function callMSGraph(theUrl, accessToken, callback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200)
callback(JSON.parse(this.responseText));
console.log(this.response);
}
xmlHttp.open("POST", theUrl, true); // true for asynchronous
xmlHttp.setRequestHeader('Authorization', 'Bearer ' + accessToken);
var dataJSON = JSON.stringify({ userEmail: null, FromDataUTC: "2012-04-23T18:25:43.511Z" })
xmlHttp.send(dataJSON);
}
function graphAPICallback(data) {
//Display user data on DOM
// var divWelcome = document.getElementById('WelcomeMessage');
// divWelcome.innerHTML += " to Microsoft Graph API!!";
// document.getElementById("json").innerHTML = JSON.stringify(data, null, 2);
}
function showWelcomeMessage() {
console.log("You are looged: " + myMSALObj.getUser().name);
// var divWelcome = document.getElementById('WelcomeMessage');
// divWelcome.innerHTML += 'Welcome ' + myMSALObj.getUser().name;
// var loginbutton = document.getElementById('SignIn');
// loginbutton.innerHTML = 'Sign Out';
// loginbutton.setAttribute('onclick', 'signOut();');
}
// This function can be removed if you do not need to support IE
function acquireTokenRedirectAndCallMSGraph() {
//Call acquireTokenSilent (iframe) to obtain a token for Microsoft Graph
myMSALObj.acquireTokenSilent(applicationConfig.graphScopes).then(function (accessToken) {
callMSGraph(applicationConfig.graphEndpoint, accessToken, graphAPICallback);
}, function (error) {
console.log(error);
//Call acquireTokenRedirect in case of acquireToken Failure
if (error.indexOf("consent_required") !== -1 || error.indexOf("interaction_required") !== -1 || error.indexOf("login_required") !== -1) {
myMSALObj.acquireTokenRedirect(applicationConfig.graphScopes);
}
});
}
function acquireTokenRedirectCallBack(errorDesc, token, error, tokenType)
{
if(tokenType === "access_token")
{
callMSGraph(applicationConfig.graphEndpoint, token, graphAPICallback);
} else {
console.log("token type is:"+tokenType);
}
}
// Browser check variables
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
var msie11 = ua.indexOf('Trident/');
var msedge = ua.indexOf('Edge/');
var isIE = msie > 0 || msie11 > 0;
var isEdge = msedge > 0;
//If you support IE, our recommendation is that you sign-in using Redirect APIs
//If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check
if (!isIE) {
if (myMSALObj.getUser()) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenPopupAndCallMSGraph();
}
}
else {
document.getElementById("SignIn").onclick = function () {
myMSALObj.loginRedirect(applicationConfig.graphScopes);
};
if (myMSALObj.getUser() && !myMSALObj.isCallback(window.location.hash)) {// avoid duplicate code execution on page load in case of iframe and popup window.
showWelcomeMessage();
acquireTokenRedirectAndCallMSGraph();
}
}
return (
<div>
<h2>Please log in from VisBlue app</h2>
<button id="SignIn" onClick={signIn}>Sign In</button>
<button id="SignOut" onClick={signOut}>Sign Out</button>
<h4 id="WelcomeMessage"></h4>
<br/><br/>
<pre id="json"></pre>
</div>
)
}
}
export default test
it gives me Multiple authorities found in the cache. Pass authority in
the API overload.|multiple_matching_tokens_detected` error when I
authenticate
This error is caused because the auth SDK finds multiple matching tokens in cache for the input given to acquireTokenSilent.
Try adding the authority, and user if necessary:
myMSALObj
.acquireTokenSilent(
applicationConfig.graphScopes,
applicationConfig.authority
)
.then(
...
Just to get back to it. I solve it by moving the whole project into fresh create-react-app. It looks like there was more than 1 instance of MSAL object thus more than one call/token at the same time.
Weird but solved my problem.
I know this is an old question, but I'll answer it anyway since I had the same issue.
My workaround for this problem was to just clear the cache whenever this error happened. It worked in my case since this wan't a regularly occurring error for my use case.
In my project's configuration, the site is also set up to refresh and retry when an error like this occurs. So after clearing the cache, the site would reload and would work as expected since there would be no conflicting tokens in the cache.
import { AuthCache } from 'msal/lib-commonjs/cache/AuthCache';
...
const authProvider = new MsalAuthProvider(
configuration,
authenticationParameters,
msalProviderConfig,
);
authProvider.registerErrorHandler((authError: AuthError | null) => {
if (!authError) {
return;
}
console.error('Error initializing authProvider', authError);
// This shouldn't happen frequently. The issue I'm fixing is that when upgrading from 1.3.0
// to 1.4.3, it seems that the new version creates and stores a new set of auth credentials.
// This results in the "multiple_matching_tokens" error.
if (authError.errorCode === 'multiple_matching_tokens') {
const authCache = new AuthCache(
configuration.auth.clientId,
configuration.cache.cacheLocation,
configuration.cache.storeAuthStateInCookie,
);
authCache.clear();
console.log(
'Auth cache was cleared due to incompatible access tokens existing in the cache.',
);
}
When user logs in I save his data in sessionStorage. In my home page I am checking whether sessionStorage is empty or not by using componentDidMount(). If there is no data it should redirect user to login page, otherwise continue. The problem is when in login page Log In button pressed componentDidMount() method in home page is being called before the browser saves user data in its sessionStorage (since there is no data in local storage yet, user can not be redirected to home page until he refreshes the page). How should I wait while browser will save data to sessionStorage?
LogIn Page
handleSubmit = e => {
e.preventDefault();
axios.post('url', { userId: e.target.elements.userId.value,
password: e.target.elements.password.value })
.then((response) => {
if(response.status=200){
this.setState({ loggedIn: true });
let responseJSON = response;
sessionStorage.setItem("userData", responseJSON);
} else {
console.log("Log In Error");
}
}).catch((error) => {
console.log(error);
this.setState({ loggedIn: false });
});
};
Home Page
componentDidMount() {
if( sessionStorage.getItem('userData') ){
console.log('User Logged In');
} else {
this.setState({redirect: true})
}
}
You can bind and event listener on the web storage inside your componentDidMount method.
Keep in mind this only works for changes happened within the same tab or frame.
window.addEventListener('storage', function (e) {
if (e.storageArea === sessionStorage) {
// handle change here
}
});
I am working on a project with angular & nodejs, there is a singin and logout function in my controller,
Main.signin(formData, function(res) {
if (res.type == false) {
alert(res.message);
} else {
$localStorage.token = res.token;
$location.path('/'); // redirect to index page.
}
}, function() {
//failed processing.
});
$scope.logout = function() {
Main.logout(function() {
window.location = "/"
window.location.replace("/");
}), function() {
alert("Failed to logout!");
};
};
the logic of the above code is simple but I can't save or delete the token with window.location = "/", but if I comment the window.location = "/", after I execute above methods, then refresh the browser, the token is deleted or saved.
Does anyone have idea about this?