'azure ad authentication issue with razor handlers
I'm experiencing a strange issue using msal.js for user authentication in a web app that already has a standard login page. I'm totally new to Azure AD, so probably I'm missing something basic. I've been pointed out to msal.js by aonther person working for the same company, but he uses PHP while our app is a NET Core 3 app. The standard login works perfectly, firing the code behind handler when the form containing username and password is submitted. This is the javascript snippet I've added to the page in order to handle the Azure AD authentication:
<script type="text/javascript">
const appCfg =
{
auth:
{
clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // Carlsberg Prospect.
authority: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/', // Url autorità con id tenant della directory predefinita.
redirectUri: 'https://localhost:44385/Index'
}
};
var msalObj = new msal.PublicClientApplication(appCfg);
function openLogin()
{
msalObj.loginPopup().then
(function (idToken)
{
$('#LoginModel_rmUsername').val(idToken.account.username);
$('#LoginModel_rmPassword').val(idToken.account.username);
$('#main-login-btn').trigger('click');
}
);
}
The call to loginPopup opens a popup where the user has to insert his mail and password, and the promise works well, giving me the token associated with the user. The jquery statement simply simulates the user clicking the login button, but the handler never fires, and instead the borwser redirects to an empty page showing a 400 error code. By the mean of the dev tools, the network tab shows the same call made from the standard login and the button click simulated after the response from the Azure AD authentication. I'm using the latest msal.js version (2.24.0). I'm not a huge fan of NET Core for web apps, but the project is a legacy of a person not working anymore for us.
EDIT
Better adding some code to try to explain my problem. This is the code inside Index.cshtml:
@page
@model IndexModel
@{
Layout = "_LayoutLogin";
}
@{
ViewBag.Title = "Login";
}
@{
string test = (Model.testEnvironment ? " block" : "none");
}
<div style="text-align:center;padding:.125rem;display:@test">
<div style="border-radius:.25rem;color:white;background-color:#18A754;"><strong>** TEST **</strong></div>
</div>
<div class="album py-5 bg-light">
<div class="container">
<div class="row">
<div class="col-md-6 offset-md-3">
<center>
<h6><strong>Sia Field</strong> - Application Login</h6>
</center>
<div class="card mb-12 shadow-sm">
<div class="card-body">
<form method="POST" id="login-form" asp-page-handler="LogIn">
<p class="card-text text-center">
@Html.AntiForgeryToken()
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.LoginModel.Azienda)
<select asp-for="LoginModel.Azienda" class="form-control" asp-items="@Model.ListOfAziende"></select>
@Html.ValidationMessageFor(m => m.LoginModel.Azienda, "", new { @class = "text-danger" })
</div>
<div class="form-group">
@Html.LabelFor(m => m.LoginModel.Username)
@Html.TextBoxFor(m => m.LoginModel.Username, new { placeholder = "Username", @class = "form-control" })
@Html.ValidationMessageFor(m => m.LoginModel.Username, "", new { @class = "text-danger" })
</div>
<div class="form-group">
@Html.LabelFor(m => m.LoginModel.Password)
@Html.PasswordFor(m => m.LoginModel.Password, new { placeholder = "Password", @class = "form-control" })
@Html.ValidationMessageFor(m => m.LoginModel.Password, "", new { @class = "text-danger" })
</div>
@Html.HiddenFor(m => m.LoginModel.rmUsername)
@Html.HiddenFor(m => m.LoginModel.rmPassword)
<button id="main-login-btn" class="btn btn-primary cbutton" style="width: 100%;">Login</button>
<button type="button" id="login-button" class="btn btn-info" style="width: 100%;margin-top:.75rem;" onclick="openLogin();">Entra con l'account corporate Microsoft</button>
</p>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
const appCfg =
{
auth:
{
clientId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
authority: 'https://login.microsoftonline.com/common/', // Url autorità con id tenant della directory predefinita.
redirectUri: 'https://localhost:44385/index'
}
};
const req =
{
// prompt: 'login'
};
var msalObj = new msal.PublicClientApplication(appCfg);
function openLogin()
{
msalObj.acquireTokenPopup(req)
.then
(
function (response)
{
$('#LoginModel_Username').val(response.account.username);
$('#LoginModel_Password').val(response.account.username);
$('#main-login-btn').trigger('click');
}
).catch
(
function (error)
{
console.log(error);
}
);
}
</script>
and this is an excerpt of the code inside Index.cshtml.cs:
public async Task<IActionResult> OnPostLogIn()
{
try
{
// Verification.
if (ModelState.IsValid)
{
DSUtenti dsUsers = new DSUtenti();
// Initialization.
IQueryable<Utenti> loginInfo = dsUsers.GetMyUtente(Convert.ToInt32(this.LoginModel.Azienda), this.LoginModel.Username, this.LoginModel.Password);
if (loginInfo != null && loginInfo.Count() > 0)
{
// Initialization.
Utenti logindetails = loginInfo.First();
// Login In.
await this.SignInUser(logindetails, false);
The handler OnPostLogIn
is correctly fired if I do a "normal" login, supplying username and password and clicking on the 'Login' button, but if I use 'Entra con l'account corporate Microsoft' button the call to acquireTokenPopup()
opens the Microsoft Login Popup, and when the promise returns the correct token with the needed info on the user, on the subsequent (simulated) press of the Login button the `OnPostLogin' handler never get fired, returning an HTTP 400 error. Obviously if at this moment I supply the credentials in the standard fields and press again the Login button returns the same error, until I refresh the page.
Sorry for the long edit post.
Solution 1:[1]
• I would suggest you to please correctly use the error handling in authentication flows with redirect methods (loginRedirect, acquireTokenRedirect)
to register the callback which is called with success or failure after the redirect using handleRedirectCallback()
method. Also, the methods for pop-up experience (loginPopup, acquireTokenPopup)
return promises such as you can use the promise pattern (.then and .catch)
to handle them as shown below : -
myMSALObj.acquireTokenPopup(request).then(
function (response) {
// success response
}).catch(function (error) {
console.log(error);
});
• Also, you can use the acquireTokenPopup
or acquireTokenRedirect
method to remediate the issue by calling an interactive method as below: -
// Request for Access Token
myMSALObj.acquireTokenSilent(request).then(function (response) {
// call API
}).catch( function (error) {
// call acquireTokenPopup in case of acquireTokenSilent failure
// due to consent or interaction required
if (error.errorCode === "consent_required"
|| error.errorCode === "interaction_required"
|| error.errorCode === "login_required") {
myMSALObj.acquireTokenPopup(request).then(
function (response) {
// call API
}).catch(function (error) {
console.log(error);
});
}
});
For more details on resolving the issue regarding the login pop-up sign-in, kindly refer to the documentation link below: -
https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-error-handling-js
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | KartikBhiwapurkar-MT |