Web API + CORS + Basic NTLM: 401 with Firefox + Chrome - asp.net

So I've been trolling every post I could find on this topic and still haven't figured it out. Hopefully you can help:
Web API 2 with CORS enabled:
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
web.config:
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
<authentication mode="Windows" />
</system.web>
jQuery
<script>
$(document).ready(function()
{
$("button").click(function()
{
var endpoint = "http://localhost:82/api/test";
var base64Credentials = window.btoa("domain\credentials:password");
$.ajax({
url: endpoint,
beforeSend: function(xhr) {
xhr.withCredentials = true;
xhr.setRequestHeader("Authorization", "Basic " + base64Credentials);
},
success: function(result) {
alert(result);
}
});
});
});
</script>
IIS has Basic Authentication enabled.
Basic Auth works in IE. Firefox and Chrome is subject to the preflight OPTIONS call.
Using Fiddler, I can see that if I set IIS to allow both Anonymous and Basic Authentication then the OPTIONS call will result in a 200 and then the subsequent GET with Basic Auth kicks in and all is right in the world. If I turn off Anonymous, then I get 401 on the OPTIONS call.
Question:
Is it a requirement to allow both Anonymous and Basic authentication to support this use case?

I've partially solved this on one of my projects. It always works in IE, but in Chrome I still get random 401 replies on requests but when I examine the response web api is sending the JSON data back so it gets past the OPTIONS.
The IIS site config for Web API is:
Windows Auth, anon disabled;
And under Windows Auth providers (option on the right), move NTLM to the top
In your web.config:
<httpProtocol>
<customHeaders>
<!-- Allows auth in CORS AJAX requests -->
<add name="Access-Control-Allow-Credentials" value="true" />
<!-- May only ever be one value - a domain or "*". "*" is disallowed when Access-Control-Allow-Credentials is true -->
<add name="Access-Control-Allow-Origin" value="http://localhost:63415" />
<add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Headers, Access-Control-Request-Method" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, HEAD, OPTIONS" />
</customHeaders>
</httpProtocol>
and in Global.asax.cs
protected void Application_BeginRequest()
{
// When Chrome sends the OPTIONS it breaks on windows auth, so this fixes it.
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
Response.StatusCode = (int)HttpStatusCode.OK;
Response.Flush();
}
}
All of that is without using the CORS NuGet package. I tried that but I still had the OPTIONS problems.
EDIT: An update on this, it all seems to be working perfectly well for me in Chrome now.

Related

How to enable content security policy for identity server?

I have a micro front-end which is connected to my identity server. In order for a user to access the micro front-end, the user needs to be authenticated via this ID server. The micro front-end needs to be embedded in an iframe in my main application but since this micro front-end requires the ID server, I am getting an error on the console. Everything works fine before embedding this micro front-end but I get this problem as soon it gets into the iframe.
Refused to frame 'https://localhost:44300/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'none'".
I tried including this in my identity server's web.config file which resulted to the above error on the console.
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Content-Security-Policy" value="frame-ancestors ''" />
</customHeaders>
</httpProtocol>
</system.webServer>
It seems ID server does not allow its content to be embedded in an iframe. This perhaps might not be a good practice but per my requirements, i will like to know how to enable this.
UPDATE
Here is where the csp is defined
public override void OnResultExecuting(ResultExecutingContext context)
{
var result = context.Result;
if (result is ViewResult)
{
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options"))
{
context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff");
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options"))
{
context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';";
// also consider adding upgrade-insecure-requests once you have HTTPS in place for production
//csp += "upgrade-insecure-requests;";
// also an example if you need client images to be displayed from twitter
// csp += "img-src 'self' https://pbs.twimg.com;";
// once for standards compliant browsers
if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy"))
{
context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp);
}
// and once again for IE
if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy"))
{
context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp);
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
var referrer_policy = "no-referrer";
if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy"))
{
context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy);
}
}
}
It is really a bad idea to try to iframe IdentityServer, because then its hard for the user to know where he is actually logging in to.
If you still want to change it you need to look at the SecurityHeadersAttribute.cs file in the QuickStart folder, that one defines the CSP.
In this file you can tweak the security headers that are sent to the browser. You need to look at the CSP and the X-Frame-Options header.

What should I use instead to Authenticated user in my .cshtml with Forms Authentication

I have setup a simple login with Forms Authentication that works fine on my localhost in an ASP.NET MVC app
I can access the login form an post it in live app.
But when I published the website into a live application on the web the Forms Authentication doesn't work. What am I doing wrong?
Here is my code:
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Home/Login" timeout="30" />
</authentication>
</system.web>
Controller:
public ActionResult LoginUser(string username, string password)
{
if (ConfigurationManager.AppSettings["UserName"] == username && ConfigurationManager.AppSettings["Password"] == password)
{
FormsAuthentication.SetAuthCookie(username, false);
return RedirectToAction("Index");
}
return RedirectToAction("Index");
}
Javascript:
$("body").on("click", "#loginbutton", function (e) {
$("#loginbutton").attr("href", "/loginuser?username=" + $('[user]').attr('user') + "&password=" + $('[pass]').attr('pass'));
});
.cshtml:
#if (User.Identity.IsAuthenticated)
{
<div class="row">
<div class="col-xs-12">
<i class="fa fa-cloud-upload" id=""></i>
</div>
</div>
}
Why does this not work in a live-application?
UPDATE:
I have foud now that it doesn't work on localhost also. The problem is #if (User.Identity.IsAuthenticated)
What should I use instead to Authenticated user in my .cshtml files?
Maybe a silly answer but you do have the keys in your web.release.config and not only web.debug.config?
Can you see in the browsers devtools if you get an 500 or maybe 400 error back when trying to login? Could also be a routing problem.

ASP.NET - Uploadify works in IE, not in FF (Auth Cookies

i have a WebForms application, and am trying to use the uploadify jquery library.
It works fine in IE8, but doesn't in FF7, FF10, or FF3. The break point i put in Upload.ashx is not hit.
I did quite the search and found that it has to does with cookies, something like ASPXAUTH. I tried adding it to 'scriptData', but no success.
Any ideas?
Page code:
<script type="text/javascript">
$(document).ready(function () {
alert($(".hidcook").val());
// <![CDATA[
var id = "55";
var theString = "asdf";
$('#fileInput').uploadify({
'uploader': 'uploadify/uploadify.swf',
'script': 'Upload.ashx',
'scriptData': { 'id': id, 'foo': theString },
'cancelImg': 'uploadify/cancel.png',
'auto': true,
'multi': true,
'fileDesc': 'All Files',
'queueSizeLimit': 90,
'buttonText': 'Importar Planilha',
'folder': '/uploads',
'onAllComplete': function (event, queueID, fileObj, response, data) {
}
});
});
// ]]></script>
Upload.ashx:
public class Upload : IHttpHandler, IRequiresSessionState{
public void ProcessRequest(HttpContext context)
{
try
{
HttpPostedFile file = context.Request.Files["Filedata"]; //breakpoint
int id = (Int32.Parse(context.Request["id"]));
string foo = context.Request["foo"];
file.SaveAs("C:\\" + id.ToString() + foo + file.FileName);
context.Response.Write("1");
}
catch (Exception ex)
{
context.Response.Write("0");
}
}
If your website content is not public, add to web.config authorization access to the Handler.
<location path="Upload.ashx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
There are some differences in how browsers implement file upload through flash component.
IE uses the same session. FF opens a new connection, so the server sees an un-authenticaded user that is trying to access a protected page.
I had an issue using MVC where uploadify didn't post to the controller action.
It was due to an authentication issue. Flash for some reason creates it's own separate cookie to the browser, so if the user has already been authenticated with the browser (ASPXAUTH) cookie, and then the swf file makes a separate request using it's own flash cookie (which has not been authenticated.
Run fiddler to see whats happening, you may find that the server request that the uploadify is making is being redirected to the login.aspx page.
Although I don't know why this would work in just IE?

ASP.Net Authentication if ReturnURL null UserData comes empty

Hi there is a problem in my login page.
The scenario is,
For example i go to www.mydomain.com/admin/ its redirecting me to the login page with ReturnURL parameter like this. www.mydomain.com/login.aspx?ReturnURL=%2fAdmin%2f.
I am logging in with admin account and everything works fine.
But if i go to Login.aspx directly which means there isn't ReturnURL QueryString field.
I log in with same admin account but when i try to go www.mydomain.com/admin/ after i logged in its redirecting me back to the login page.
I'm doing navigates like this. What i am missing?
//The code block that is logging in admin.
//check if there is a ReturnURL
if (QueryStringTool.IsExistAndNotNull("ReturnURL"))
{
Session["UserType"] = UserTypes.UserType.Admin;
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text.Trim(), false));
}
//ReturnURL doesn't exists.
else
{
FormsAuthentication.SetAuthCookie(txtUserName.Text, cbUserRememberMe.Checked);
Response.Redirect("/Admin/Default.aspx");
}
Now try this. Replace your code
//check if there is a ReturnURL
if (QueryStringTool.IsExistAndNotNull("ReturnURL"))
{
Session["UserType"] = UserTypes.UserType.Admin;
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text.Trim(), false));
}
//ReturnURL doesn't exists.
else
{
FormsAuthentication.SetAuthCookie(txtUserName.Text, cbUserRememberMe.Checked);
Response.Redirect("/Admin/Default.aspx");
}
with this one
if("Check if User Is Authentic")
{
Session["UserType"] = UserTypes.UserType.Admin;
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, cbUserRememberMe.Checked);
}
This will work for your whole code. Redirects an authenticated user back to the originally requested URL or the default URL.
Check on Default page Load event Session["UserType"] if user is Admin then redirect him to admin page
This is a sample web.config
<configuration>
<system.web>
<authentication mode="Forms">
<forms
name="401kApp"
loginUrl="/login.aspx"
cookieless="AutoDetect"
defaultUrl="myCustomLogin.aspx">
<credentials passwordFormat = "SHA1">
<user name="UserName"
password="07B7F3EE06F278DB966BE960E7CBBD103DF30CA6"/>
</credentials>
</forms>
</authentication>
</system.web>
</configuration><br/>
set defaultUrl="yourdefaultpageURL" in web.config
OR
you can use FormsAuthentication.RedirectFromLoginPage Method (String, Boolean)

Access ASP.NET authentication ticket on Client (via javascript)

I have an ASP.NET website that uses Forms authentication
<authentication mode="Forms">
<forms name="NewsCoreAuthentication" loginUrl="~/Default.aspx" defaultUrl="~/Default.aspx" protection="Validation" timeout="300" domain="someRootDomain.com" />
</authentication>
I need to identify if user is authenticated on web page after it was rendered to client.
To accomplish this I thought that I can read document.cookie and check if ".ASPXAUTH" is there.
But the problem is that even if I am signed in this value is empty.
How can I check that user is authenticated?
Why document.cookie is empty?
Thank you for answers. blowdart helped me to understand why authentication ticket is not accessible from client script.
The reason it's blank is because the cookie is protected by being marked as HttpOnly. This means it cannot be accessed via script. Turning this off is a very very bad idea, as XSS vulnerabilities in your site could expose it to cookie theft, so I'm not going to tell you how you can do it.
As others have said, the auth ticket is and SHOULD be httponly.
The best way to do this is to use ApplicationServices. The JSON authentication endpoint exposes IsLoggedIn and I have noticed your concern regarding server load. The overhead of a call to a static endpoint that simply checks the cookie for you is negligible. Really.
So, If you are using MsAjax, just enable application services and call Sys.Services.AuthenticationService.IsLoggedIn.
If you want to do this from raw javascript here is the codez ;-)
Add this segment to you config file
<system.web>
------------
</system.web>
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled ="true" requireSSL="false"/>
</webServices>
</scripting>
</system.web.extensions>
The page....
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
function createXHR() {
// a memoizing XMLHttpRequest factory.
var xhr;
var factories = [
function() { return new XMLHttpRequest(); },
function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
function() { return new ActiveXObject("Msxml3.XMLHTTP"); },
function() { return new ActiveXObject("Microsoft.XMLHTTP"); } ];
for (var i = 0; i < factories.length; i++) {
try {
xhr = factories[i]();
// memoize the factory so we don't have to look for it again.
createXHR = factories[i];
return xhr;
} catch (e) { }
}
}
function isLoggedIn() {
var xhr = createXHR();
xhr.open("POST", "/Authentication_JSON_AppService.axd/IsLoggedIn", true);
xhr.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status != 200) {
alert(xhr.statusText);
} else {
alert("IsLoggedIn = " + xhr.responseText);
}
xhr = null;
}
};
xhr.setRequestHeader("content-type", "application/json");
xhr.send(null);
}
</script>
</head>
<body>
<input type="button" value="IsLoggedIn?" onclick="isLoggedIn()" />
</body>
</html>
Number one... this is a bad idea. There is absolutely no security in checking if a user is authorized on the client side. None.
But if you really want to do this... do the check in code behind, and push a value to the client that can be read via Javascript. Something akin to:
RegisterClientScript("isvalidated", "var isUserAuthenticated = " + UserAuthenticated);
You see the problem now? You could do the same thing in AJAX... but it has the same problem.
OK, I can see doing this as a simple convenience for the user... showing certain links if they are authorized for instance. But it is not secure in any way shape or form. Just do yourself a favor and handle this in code-behind.

Resources