Redirection to original URL having hash tag (#) broken in MVC4 - asp.net-mvc-4

I am developing an SPA application using AngularJS working with REST Web API, on top of a very small layer of ASP.NET MVC4. For reasons not important here, I am not using the default Account Controller of MVC4.
basically, I want to share "tasks" between users. My goal is to be able send the URL of a specific "task" entity to any user, via email. Clicking on that URL should launch the authentication. Following a successful authentication, I want to display the real task page info.
AngularJS causes my URLs to have # sign, or a URL of a page displaying the task "XYZ123" is:
http://hostname.com/#/tasks/XYZ123
ASP.NET redirects the unauthorized access to that URL to:
http://hostname.com/Home/Login?ReturnUrl=%2f#/tasks/XYZ123
This is OK, but the relevant controller method "cuts out" the path from #, so in:
public ActionResult Login(string returnUrl)
the value of 'returnUrl' will be just "/"
So, I am losing the path: I would like to build a "Connect with Facebook" link having the original URL, like:
http://hostname.com/Login/ExternalLogin?ReturnUrl=%2F#/tasks/XYZ123
but I cannot.
What is the right way to solve this issue?
I can think of creating my own redirection service URL without # tag, but this solution implies additional work, and covers only a case when the system is sending a message with task URL - humans will still try to copy/paste the location URL from the browser.
Thanks for any hint.
Max

Yes. A browser cuts '#/tasks/XYZ123' and requests page without that hash.
Although the hash itself apears on the logon page - it's the browser's work again.
Hash is not traveling to the server.
So when a browser loads the logon page with ?ReturnUrl=%2f#/tasks/XYZ123 we can rewrite Form action and encode the hash.
If the form looks like:
<form action="/Home/Login" method="post" >
...
</form>
The javascript code should look like:
<script src="~/js/jquery.js"></script>
<script type="text/javascript">
$(function() {
var search = $(location).attr('search') || '';
var hash = $(location).attr('hash') || '';
if (hash.length === 0) {
if (window.history.pushState) {
window.history.pushState('login', 'Login', '/Home/Login');
}
} else if (search === '?ReturnUrl=%2f') {
$('form').attr('action', '/Home/Login' + search + encodeURIComponent(hash) );
}
});
</script>
The part with window.history.pushState is required for the following:
If there is no hash, then for a SPA its URL (more likely) will be:
http://hostname.com/Home/Login?ReturnUrl=%2f
so here we try to replace URL (without page reload) with more accurate
http://hostname.com/Home/Login

You can use the properties of Request (like .Urlor .QueryString) to get the original url (and url parameters), instead of relying on the automatic binding of returnUrl parameter.

Replace # in the returnUrl with %23

Related

Azure B2C: how to Include domain hint in Sign Up/In links from asp.net core control

I'm using Azure B2C, I would like to use Google to sign in users. I would like to do something like this in my asp.net core page to achieve it:
<a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn" asp-route-domainHint="google.com">Google Sign Up/In</a>
So that the Google challenge page is used to sign in the user.
However, this code results in the non-social ("organic") sign in page being used, instead, because the domain_hint parameter is not included in the auth redirect.
Looking at the account controller code, here: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web.UI/Areas/MicrosoftIdentity/Controllers/AccountController.cs
The only way to pass a domain hint to the Account controller, is by using the Challenge action.
<a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="Challenge" asp-route-domainHint="google.com">Google Sign Up/In</a>
This almost works.
But it loops back again to Google challenge page, after you choose your google account, repeatedly.
Is there a way to pass a domain hint and use the SignIn action of the Account controller? Or some other approach for generating Sign In calls from an asp.net core page and include a domain hint?
I have seen this answer, but would prefer to not build up the entire href address from scratch: Azure B2C with domain hint instead of IdP buttons
Thanks very much
To redirect users to an external identity provider, do the following:
Check the domain name of your external identity provider. For more information, see Redirect sign-in to a social provider.
Complete the Support advanced scenarios procedure.
In the OnRedirectToIdentityProviderFunc function, add the following line of code to the OnRedirectToIdentityProvider function:
private async Task OnRedirectToIdentityProviderFunc(RedirectContext context)
{
context.ProtocolMessage.DomainHint = "facebook.com";
// More code
await Task.CompletedTask.ConfigureAwait(false);
}
https://learn.microsoft.com/en-us/azure/active-directory-b2c/enable-authentication-web-application-options#preselect-an-identity-provider
Jas Suri's answer helped quite a bit, but there are a few code-related steps that I did to complete the solution:
To get that domain_hint parameter into the authentication link, you add some code to the ConfigureServices() method of the Startup class of your ASP.NET Core project:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAdB2C", options); // Assuming an appsettings.json section named AzureAdB2C with config data
options.Events ??= new OpenIdConnectEvents();
options.Events.OnRedirectToIdentityProvider += OnRedirectToIdentityProviderFunc;
});
The key part was subscribing to the OnRedirectToIdentityProvider event. Add an event handler for it in Startup.cs, which could look like this:
private async Task OnRedirectToIdentityProviderFunc(RedirectContext context)
{
var domainHint = context.HttpContext.Request.Query["domainHint"];
context.ProtocolMessage.DomainHint = domainHint.Count > 0 ? domainHint[0] : null;
await Task.CompletedTask.ConfigureAwait(false);
}
The middle line above gets the domainHint from the query string. So, you must set its value in your razor view as follows. Take note of the asp-route-domainHint attribute, which ASP.NET Core adds to the rendered Href as a query string value.
<a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn" asp-route-domainHint="google.com">Google Sign Up/In</a>
In the final rendered HTML, this link becomes (note the domainHint querystring parameter):
Google Sign Up/In
When a user clicks on that link, the Account controller in Microsoft.Identity.Web will redirect the user to the authentication link with Microsoft (note the domain_hint parameter near the end) which results in the social auth provider's screen being full-screen:
https://your-ADB2C-intance/your-ADB2C-domain/b2c_1a_signup_signin/oauth2/v2.0/authorize?client_id=..etc..etc....&domain_hint=google.com&state=..etc..etc..

Blazor : How to get HTML of SSRS from controller?

I'm trying to get a report from SSRS Rest API. I can see it when I navigate to the URL
https://myPC:443/ReportService?%2fSSRS%2fPatientèle&rs:Command=Embed&rc:LinkTarget=main&Hospital=CHRU%20Strasbourg
in chrome
When I navigate there in the browser I can see my report.
So I've tried to get the HTML from a controller:
[HttpGet]
public async Task<string> GetReportAsHTML()
{
using (var client = new HttpClient())
{
using (var result = await client.GetAsync("http://myPC:80/ReportService?%2fSSRS%2fPatientèle&rs:Command=Embed&rc:LinkTarget=main&Hospital=CHRU%20Strasbourg"))
{
if (result.IsSuccessStatusCode)
{
return await result.Content.ReadAsStringAsync();
}
}
}
return "";
}
It's returning 401 unauthorized, and the statement in the if is never reached.
Can someone please explain how I can resolve this problem so I get the correct response?
EDIT
I tried both, I mean in http & https, and both return the report without authentification. Http access (http://localhost:80/...) was even better because charts aren't displayed, only tables. With https access, I get the following picture instead of the charts:
If you need to embed reports, you can also consider doing so in an <iframe> - just point its src to the report URL.
<iframe src="https://myPC:443/ReportService?%2fSSRS%2fPatientèle&rs:Command=Embed&rc:LinkTarget=main&Hospital=CHRU%20Strasbourg "></iframe>
The added benefits are:
iframes usually share cookies with their parent, so if the report server needs such authentication it may work immediately
if the report is somehow interactive (say, it actually returns an html page with filters, dropodwns and the like) - your end user would get that too instead of static HTML that might even break when taken out of its context
You may also want to look into ready-made Blazor report viewer components - perhaps your reporting solution vendor already has one.
It seems like url in your code do not match the one you use through Chrome? Could this be the issue?

Node.js Response From Image Upload Without Refreshing Client Page

Problem Set: Client .posts image from form action='/pages/contact/image/something' to node.js and I .putItem to AWS S3. On the success response I would like to send the image url back to the client without refreshing the screen and update the location they wanted to add the image with the new src url.
If the page refreshes I lose the location where they wanted to upload the image. Open to any suggestions, I have looked at res.send, res.end, res.json, res.jsonp, res.send(callback): all of which overwrite(refresh) the client webpage with the array, text or context in general I am passing back to the client . Code below:
myrouter.route('/Pages/:Page/Image/:Purpose')
.post(function (req, res) {
controller.addImageToS3(req, res)
.then(function(imgurl){
//res.json({imgurl : imgurl});
//res.send(imgurl);
//res.end(imgurl);
//res.send(req.query.callback(imgUploadResponse(imgurl)))
<response mechanism here>
console.log('Image Upload Complete');
}, function (err){
res.render('Admin/EditPages', {
apiData : apiData,
PageId : PageId
});
});
});
Ideally there could be a passed parameter to a javascript function that I could then use: Example:
function imgUploadResponse(imgurl){
// Do something with the url
}
You, as a developer, have full control over the s3 url format. It follows a straightforward convention:
s3-region.amazonaws.com/your-bucket-name/your-object-name.
For example:
https://s3-us-west-2.amazonaws.com/some-random-bucket-name/image.jpg
While I would recommend keeping those details in the back-end, if you really want to avoid using res.send, you can basically make the front-end aware of the url formatting convention and present the url to the user, even before the image was actually uploaded (just need to append the name of the image to the s3-region.amazonaws.com/your-bucket-name)
Also, I'm not sure why your page would refresh. There are ways to refresh content of your page without refreshing the whole page, the most basic being AJAX. Frameworks like angular provide you with promises that allow you to do back-end calls from the front-end.
Hope this helps!

How do I set push-state in durandaljs 2.0 the works on refresh?

I'm using durandaljs 2.0. I've installed the durandal starter-kit as suggested and explained here. In the shell I'm returning router.activate({ pushState: true } ); as explained in the relevant documentation (see the bottom of the page).
Happily, the URL is indeed in a 'push state' format, e.g. http://localhost:61285/flickr - the problem is that when I refresh the page I get a 404 error telling me "the resource cannot be found". If I set push-state to false ({pushState: false} above) I get a hashed URL, e.g. http://localhost:61285/#flickr - and now a refresh does work. So, how do I set up a push state mode in durandaljs 2.0 that will work with refresh?
Thanks,
Elior
Maybe to late...but
just change the routes config.
simple as this :
routes.MapRoute(
name: "Default",
url: "{*url}",
defaults: new { controller = "Home", action = "Index" }
);
When you refresh the page, the browser will make a request to the server with that URL http://localhost:61285/flickr.
What's probably happening is that if you are using ASP.NET MVC, the server is trying to locate a Controller called flickr and it throws an exception because obviously there isn't any resource with that name.
In order to get rid of this exception you should configure the server to serve the same HTML of the APP but for unknown URL's. This can be achieved using IIS URL Rewrite in ASP.NET.
So after setting up properly the server, by requesting an unknown URL it would return the initial view for the app plus whatever you pass in the query string parameters so the router can do its job at client side.
In this blog post you will find more information about how to configure ASP.NET to handle this scenarios. In the article the author uses AngularJS, however it will be the same for Durandal.
RainerAtSpirit and margabit, you're both right, thank you. Here is how I implemented the server side:
First I should note that all the interaction with the server is done via WebApi controllers.
so, for example, if the URL is:
http://localhost:61285/home/category2/subCategory22 (for a localhost), the server tries to look for a controller called 'home' and an action in it called 'category2'. Since there's no such action, I get a 404 error.
What I wanted is that the server WILL call the 'home' controller, but send the rest of the URL as parameters to the client. My solution was to add a hash after the controller's name, so that the URL will look like this: http://localhost:61285/home/#/category2/subCategory22. If this would happen then the client will take care of the hashed part with no 404 error.
For this to happen:
I added the following to 'web.config':
<customErrors mode="On" defaultRedirect="Error">
<error statusCode="404" redirect="Error" />
</customErrors>
Then I create a controller named 'ErrorController' with the following class in it:
public class ErrorController : ApiController
{
[HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH"), AllowAnonymous]
public HttpResponseMessage Handle404()
{
string [] parts = Request.RequestUri.OriginalString.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries);
string parameters = parts[ 1 ].Replace("aspxerrorpath=","");
var response = Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Location = new Uri(parts[0].Replace("Error","") + string.Format("#{0}", parameters));
return response;
}
}
what happens is that when the server get a URL with no relevant action as I mentioned above, it redirects it to this controller in the following format: http://localhost:61285/Error?aspxerrorpath=home/category2/subCategory22
as you can see, I manipulate this to add the hash and remove the unnecessary info: http://localhost:61285/home/#/category2/subCategory22 and the redirect the server to the 'home' controller.
You might wonder why I do all of this - the reason is that Durandal, a wonderful platform, enables me to use push state, but in order for that to happen I have to work-around the server getting a non-hashed URL and pass it to the client despite the fact there's no relevant controller/action; Durandal get's a hashed URL but removes the hash automatically and the user eventually sees a hash-less URL while Durandal provides all the necessary push state functionality.
Elior

closing a window, Redirecting to old page after a login [client side prog]

I am working with youtube api so using the Oauth 2.0 for client side authorization.
https://developers.google.com/youtube/2.0/developers_guide_protocol_oauth2#OAuth2_Client_Side_Web_Applications_Flow
when clicking login opens a new window for authorization with google oauth 2.0, and gets redirected, it opens the redirected Url in new window (that was used for authorization), how do after login/whether successful or failure, i close the login window and go back to original site with the token and results.
If you are gonna suggest a session variable to implement this, i think with client side programming approach this wont be possible.
I need a similar login/authorization approach as here:
http://gdata-samples.googlecode.com/svn/trunk/gdata/youtube_upload_cors.html
I tried to decipher the code, but it dint work out.
I would prefer a client side approach as the thing i plan to work on is purely client driven (JS)
Thanks
The best way to do this is probably HTML5 postMessage.
However, I have a workable method using standard JavaScript and window.opener in my Getting Started with OAuth 2.0 book (published by O'Reilly). This works fine in recent browsers:
I have a page called oauth2callback.html. This page is your redirect_uri. It parses the access token out of the # hash fragment in the URL, calls a JS function in the window.opener to set the access token and then closes itself:
<html>
<body>
<script type="text/javascript">
var oauthParams = {};
// parse the query string
// from http://oauthssodemo.appspot.com/step/2
var params = {}, queryString = location.hash.substring(1),
regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(queryString)) {
oauthParams[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
window.opener.setOauthParams(oauthParams);
window.opener.callApi();
window.close();
</script>
</body>
</html>
The parent page (which kicks off the entire process by opening up the OAuth flow in a popup) has a simple JavaScript function called setOauthParams which accepts the params and stores them in a global variable:
function setOauthParams(oauthParamsPassed) {
this.oauthParams = oauthParamsPassed;
}
I have other code which kicks off the OAuth flow and uses jQuery to call the API (Google Contacts in my case), but it doesn't sound like you need that.