azure ad authentication issue with razor handlers - razor

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.

• 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://learn.microsoft.com/en-us/azure/active-directory/develop/msal-error-handling-js

Related

Angular: Re-render asynchronous data in image tag

I am using Angular to fetch user profile picture from backend(Node.js/Express). Everything is working perfectly except for one thing. The Angular does not re-render the HTML that displays the profile picture incase, the user has updated his picture or if no picture is present and user uploads his first image. As expected, the Angular is rendering the HTML only once and isn't re-rendering again. I don't know how can I wait for asynchronous data in HTML as I am directly targeting an endpoint in HTML instead of TS.
Here's my code:
userProfile.component.html
<div class = "imgClass">
<img class = "img-thumbnail rounded-circle imgclass"
src="http://localhost:3000/api/getProfilePhoto?id={{cookieData}}">
//angular is sending request to the above endpoint to fetch the image only once at the time
application starts or user logs in. How can I send a request again?
<div class="middle">
<div class="text"><button type="button" name="button" (click) = "selectImage()" class = "btn
btn-outline-primary"> <i class="bi bi-plus"></i> </button></div>
<input type="file" id="imgUpload" (change) = "handleImageInput($event.target.files)">
</div>
</div>
userProfile.component.ts
selectImage()
{
document.getElementById('imgUpload').click();
}
handleImageInput(files: FileList)
{
this.imageUpload = files.item(0);
this.uploadImage();
}
uploadImage()
{
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {params, responseType: "text"})
.subscribe(responseData => {
this.imageChanged = true; //I have tried using this as *ngIf in HTML but it is not working either
}
,error => {
console.log("Image uploading failed" + error.message);
})
}
Does anybody know how can I send the request to an endpoint in HTML once user changes/uploads his first picture?
You need to trigger the image fetch request for each update/upload requests. Or you could adjust the backend to return the image data from the update/upload requests.
Option 1: manually fetch image for each update/upload requests
Use RxJS switchMap operator to switch to image fetch request after the uploading has completed. It'll not be fetched if the uploading failed.
profileImage: any;
selectImage() {
document.getElementById('imgUpload').click();
}
handleImageInput(files: FileList) {
this.imageUpload = files.item(0);
this.uploadImage();
}
uploadImage() {
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {
params,
responseType: "text"
}).pipe(
tap(null, error => console.log("Image uploading failed" + error.message)),
switchMap(_ => this.http.get(`http://localhost:3000/api/getProfilePhoto?id${this.cookieData}`))
).subscribe(
image => {
this.profileImage = image;
},
error => {
console.log("Image fetching failed" + error.message);
}
);
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">
Option 2: Return the image from upload/update request
Adjust the backend to return the image data from the Upload POST request.
profileImage: any;
uploadImage() {
const formData = new FormData();
const params = new HttpParams().set('id', sessionStorage.getItem('cookie'));
formData.append("file", this.imageUpload, this.imageUpload.name);
this.http.post('http://localhost:3000/api/updateImage', formData, {
params,
responseType: "text"
}).subscribe(
image => {
this.profileImage = image;
},
error => {
console.log("Image uploading failed" + error.message);
}
);
}
<img class="img-thumbnail rounded-circle imgclass" [src]="profileImage">
As a sidenote, using document.getElementById() in Angular will search the whole DOM, not just the individual component. In relatively complex apps, it might lead to performance issues. Instead try to use an event handler or if it's not possible, use Angular ViewChild with a template reference parameter to get an element from the current component's DOM.
if the webservice resolving the image url returns an Observable, you can make the call from typescript like below
imageData$: Observable<number>;
getImage(id): Observable<string> {
this.imageData$=http.get(url?id=<some_id>);
return this.imageData$
}
and the adding async pipe on it
<img class = "img-thumbnail rounded-circle imgclass" [src]="imageData$ | async">
Basically The async pipe subscribes to an Observable or Promise and
returns the latest value it has emitted. When a new value is emitted,
the async pipe marks the component to be checked for changes. When the
component gets destroyed, the async pipe unsubscribes automatically to
avoid potential memory leaks.

Controller Void Method On Button Click Using Ajax. ASP.NET

So I have a .ASP MVC Web Application project. I want to run a void method from the controller class when I press a button using AJAX. No variable input or output data needed. I just want to create a pdf file and save it on my local machine.
Right now, nothing at all happens when I click the button. I don't think the ajax script works, 0 connection.
This is my Controller method:
[HttpPost]
public void Test()
{
string dok = System.IO.File.ReadAllText("C:\\Users\\axel\\Desktop\\Repo\\Cert\\employee_regular.html");
var Renderer = new IronPdf.HtmlToPdf();
var HtmlTemplate = dok;
var Pdf = Renderer.RenderHtmlAsPdf(HtmlTemplate);
Pdf.SaveAs("C:\\Users\\axel\\Desktop\\Repo\\Cert\\Arbetsgivarintyg_vanlig_heltid.pdf");
}
This is my Index.cshtml file
#{
ViewBag.Title = "Home Page";
}
<div class="row">
<div class="col-md-12">
<h2>Request employement certificate</h2>
<input type="button" onclick="BtnClick()" value="Click me" />
</div>
</div>
<script>
function BtnClick() {
$ajax({
url: "/Home/Test",
method: "POST",
success: function () {
alert("ok");
},
error: function () {
alert("not ok")
}
})
}
</script>
Really happy for any help
Well there can be several reasons why your code is not working.
First Make sure you are actually able to make a call to a function, Just simply add simple alert message before calling the ajax and see if the alert triggers.
The second thing is to validate url replace the hardcoded url and add url using URL helper.
I would recommend you to make a function as JsonResult Instead of Void, because an exception can happen when creating pdf. [This change is optional but I do recommend it]
So after all the changes your code would look something like this
#{
ViewBag.Title = "Home Page";
}
<div class="row">
<div class="col-md-12">
<h2>Request employement certificate</h2>
<input type="button" onclick="BtnClick()" value="Click me" />
</div>
</div>
<script>
function BtnClick() {
$ajax({
alert("Funciton is working"); // change confirm function is working
url: "#Url.Action("Test","Home")", // change using Url Helper to create valid URL
method: "POST",
success: function (data) {
if (data == true)
{
alert("pdf created sucessfully ok");
}
else
{
alert("exception happend when creating pdf not ok");
}
},
error: function () {
alert("not ok")
}
})
}
</script>
Your Back End would look something like this
[HttpPost]
public JsonResult Test()
{
try {
string dok = System.IO.File.ReadAllText("C:\\Users\\axel\\Desktop\\Repo\\Cert\\employee_regular.html");
var Renderer = new IronPdf.HtmlToPdf();
var HtmlTemplate = dok;
var Pdf = Renderer.RenderHtmlAsPdf(HtmlTemplate);
Pdf.SaveAs("C:\\Users\\axel\\Desktop\\Repo\\Cert\\Arbetsgivarintyg_vanlig_heltid.pdf");
return Json(true, JsonRequestBehavior.AllowGet);
}
catch(Exception ex) {
return Json(false, JsonRequestBehavior.AllowGet);
}
}

Is it possible to populate the input bar in webchat with an onclick method

I'm attempting to display a list of popular questions to the user, when they click them I want them to populate the input bar and/or send the message to the bot via the directline connection.
I've attempted using the ReactDOM.getRootNode() and tracking down the input node and setting the .value attribute, but this does not populate the field. I assume there is some sort of form validation that prevents this.
Also, if I console log the input node then save it as a global variable in the console screen I can change the value that way, but then the message will not actually be able to be sent, hitting enter or the send arrow does nothing. While it may seem that the suggestedActions option would work well for this particular application, I CANNOT use it for this use case.
const [chosenOption, setChosenOption] = useState(null);
const getRootNode = (componentRoot) =>{
let root = ReactDom.findDOMNode(componentRoot)
let inputBar = root.lastChild.lastChild.firstChild.firstChild
console.log('Initial Console log ',inputBar)
setInputBar(inputBar)
}
//in render method
{(inputBar && chosenOption) && (inputBar.value = chosenOption)}
this is the function I tried to use to find the node, the chosen option works as intended, but I cannot change the value in a usable way.
I would like the user to click on a <p> element which changes the chosenOption value and for that choice to populate the input bar and/or send a that message to the bot over directline connection.What I'm trying to accomplish
You can use Web Chat's store to dispatch events to set the send box (WEB_CHAT/SET_SEND_BOX) or send a message (WEB_CHAT/SEND_MESSAGE) when an item gets clicked. Take a look at the code snippet below.
Simple HTML
<body>
<div class="container">
<div class="details">
<p>Hello World!</p>
<p>My name is TJ</p>
<p>I am from Denver</p>
</div>
<div class="wrapper">
<div id="webchat" class="webchat" role="main"></div>
</div>
</div>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
// Initialize Web Chat store
const store = window.WebChat.createStore();
// Get all paragraph elements and add on click listener
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
paragraph.addEventListener('click', ({ target: { textContent: text }}) => {
// Dispatch set send box event
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
});
}
(async function () {
const res = await fetch('/directline/token', { method: 'POST' });
const { token } = await res.json();
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
</script>
</body>
React Version
import React, { useState, useEffect } from 'react';
import ReactWebChat, { createDirectLine, createStore } from 'botframework-webchat';
const WebChat = props => {
const [directLine, setDirectLine] = useState();
useEffect(() => {
const initializeDirectLine = async () => {
const res = await fetch('http://localhost:3978/directline/token', { method: 'POST' });
const { token } = await res.json();
setDirectLine(createDirectLine({ token }));
};
initializeDirectLine();
}, []);
return directLine
? <ReactWebChat directLine={directLine} {...props} />
: "Connecting..."
}
export default () => {
const [store] = useState(createStore());
const items = ["Hello World!", "My name is TJ.", "I am from Denver."]
const click = ({target: { textContent: text }}) => {
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
}
return (
<div>
<div>
{ items.map((item, index) => <p key={index} onClick={click}>{ item }</p>) }
</div>
<WebChat store={store} />
</div>
)
};
Screenshot
For more details, take a look at the Programmatic Post as Activity Web Chat sample.
Hope this helps!

Angular2: Very strange/sudden issue when using ngSubmit to pass a String to a function

I am busy with a Angular2 web app and running into a very strange issue... I have a model driven form that uses ngSubmit to pass a barcode value to a findProduct() function. Everything worked fine about an hour ago, but now when I click the Scan button the page reloads and I don't get any output in the google dev tools console. I've tried to console.log the barcode in the findProduct() function but still no output in the console, the page just reloads whenever the button is clicked. Any idea what I'm doing wrong/How I can debug this kind of issue?
my code:
html template:
<form role="form" [formGroup]="productForm" (ngSubmit)="findProduct(productForm.value.barcode)">
<input class="form-control" type="number" placeholder="Enter Barcode" [formControl]="productForm.controls['barcode']">
<small [hidden]="productForm.controls['barcode'].valid || (productForm.controls['barcode'].pristine && !productForm.submitted)">barcode required</small>
<button class="btn btn-submit pull-right" [disabled]="!productForm.valid">Scan</button>
</form>
controller:
findProduct(barcode: string) {
console.log(barcode);
this.product.GoodsReceivedID = this.GRNIDKey;
this.restService.findCaptureItem(this.GRNIDKey, barcode)
.subscribe(
(res) => {
console.log(res);
this.product = res;
},
(res) => {
console.log(res);
});
}
Service:
findCaptureItem(GRNIDKey: string, barcode: string) : Observable<any> {
return this.http.get(this.API_URL + 'PurchaseOrder/FindPurchaseOrderItem/' + GRNIDKey + '/' + barcode)
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'server error'));
}
It turns out the issue occurred on the line:
this.product.GoodsReceivedID = this.GRNIDKey;
The issue was that this.product has been declared but not yet initialized... I think it's really strange that there was no Warning/Error in either the Google developer console or angular-cli (Windows command prompt)...

Kendo UI Grid - ClientTemplate calling MVC Url.Action calls (incorrectly) two different actions

I have some data that loads into a Kendo grid via the Ajax binding.
Within one of the columns there's a ClientTemplate that calls a javascript method (showAll).
This method will call an action and get the details of the data, putting it into a json response, and then open a jquery-ui dialog to show the details.
When the user clicks on the link in the grid the HttpGet is triggered for the GetDetails action BUT, the problem is, it is also triggered for the entire page's action (Index).
The question, I guess, is what is causing the Index action to be triggered? Because, the dialog will show, the detailed data will populate, but once I close the dialog all the filter textboxes will be reset and the grid will reload and the data within it.
Shouldn't the only action called be the GetDetails?
Any hints will be greatly appreciated!
Code:
#(Html.Kendo().Grid<LogViewModel>()
.Name("LogGrid")
.Columns(column =>
{
column.Bound(x => x.StuffCount).Title("Stuff").Width(70)
.ClientTemplate("<a onclick=\"showAll('" + "#= Id #')\"" + " href=''>#= StuffCount #</a>");
})
.DataSource(dataBinding => dataBinding
.Ajax()
.PageSize(50)
.Read(read => read.Action("GetData", "Summary")
.Data("getSearchFilters"))
.Model(model => model.Id(o => o.Id)))
.Events(e => e
.DataBound("onGridItemsDatabound"))
.Pageable(paging => paging.Refresh(true))
)}
<div id="dialog-message" title="" style="display: none">
<p id="msg"></p>
</div>
<script type="text/javascript">
var showAll= function (id) {
var url = '#Url.Action("GetDetails", "Summary")' + "/" + id;
var sTitle = 'title text';
$.getJSON(url, null,
function (data) {
$("#dialog-message").dialog({ title: sTitle });
$("#msg").text(data.details);
showMessage();
});
};
var showMessage = function () {
$("#dialog-message").dialog({
modal: true,
draggable: false,
resizable: false,
buttons: {
Ok: function() {
$(this).dialog("close");
}
}
});
};
</script>
The controller methods (content removed for brevity
public ActionResult Index(...)
{
...
}
public ActionResult GetDetails(Guid id)
{
... (get data from repository)
return Json(data, JsonRequestBehavior.AllowGet);
}
I posted the same question on the Telerik forum. Their admin pointed me in the right direction:
http://www.kendoui.com/forums/mvc/grid/kendo-ui-grid---clienttemplate-calling-mvc-url-action-calls-(incorrectly)-two-different-actions.aspx
Which "href" value should I use for JavaScript links, "#" or "javascript:void(0)"?
Turns out I had to add the void to the href to call the javascript and stay on the page.
href="javascript:void(0)"