IIS 7.5 Windows + Anonymous Authentication works intermittently - asp.net-mvc-3

I have an MVC 3 site in which some controllers/action require Windows authentication, other do not, i.e. are anonymous.
The site runs fine for a while, I can access both the anonymous actions and authenticated ones (without prompting, credentials are passed automatically in Chrome/IE/FireFox) but then the authentication just stops working, I start getting prompted for credentials that are never accepted.
I have to either restart the whole server, or change the site physical path to another app, make an authenticated request, which works, when back to the original site. The situation is then rinse and repeat, but I can find no pattern to it. If I do nothing, the authentication stays broken.
I have IIS 7.5 configured as follows:
App Pool
.NET Framework Version - v4.0
Managed Pipeline Mode - Integrated
Application Pool Identity - ApplicationPoolIdentity
Site
Anonymous Authentication - Enabled
Anonymous user identity - IUSR [I believe this is the default]
ASP.NET Impersonation - Disabled
Forms Authentication - Disabled
Windows Authentication - Enabled
Windows Authentication Extended Protection - Off
Windows Authentication Enable Kernel-mode authentication - On
Windows Authentication Providers - Negotiate, NTLM
Controllers
// Anonymous Controller
public class HomeController : Controller
{
public ActionResult Index()
{
return this.View();
}
}
// Authenticated Controller
[Authorize]
public class AnotherController : Controller
{
public ActionResult Index()
{
var viewModel = // create view model;
return this.View(viewModel);
}
}
At all times, authentication working or not a GET to /home/index returns 200. Exactly as expected.
When authentication is working a GET request to /another/index looks like this:
> GET /another/index
Response: 401
Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate
> GET /another/index
Request Header: Authorization: Negotiate TlRMIVNDUAACAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==
Response: 401
Response Header: WWW-Authenticate: Negotiate TlRMTVNTUAACAAAACAAEADgAAAAVgoni2YSlwIHmCL4AAAAAAAAAAIgAiAA8AAAABgGxHQAAAA9GAFIAAgAEAEYAUgABABQARgBSAEkATgBUAFIAQQBOAEUAVAAEABQARgBSAC5ATgBEAFMALgBjAG8AbQADACoARgBSAKkATgBUAFIAQQBUAEUAVAAuAGCAcgAuAG4AZACzAC4AYvBvAG0ABQAOAE4ARABTAC6AYwBvAG0ABwAIAOST3lPK980BAAAAAA==
> GET /another/index
Request Headers: Authorization: Negotiate TlRMTVNTUAACAAAACAAEADgAAAAVgoni2YSlwIHmCL4AAAAAAAAAAIgAiAA8AAAABgGxHQAAAA9GAFIAAgAEAEYAUgABABQARgBSAEkATgBUAFIAQQBOAEUAVAAEABQARgBSAC5ATgBEAFMALgBjAG8AbQADACoARgBSAKkATgBUAFIAQQBUAEUAVAAuAGCAcgAuAG4AZACzAC4AYvBvAG0ABQAOAE4ARABTAC6AYwBvAG0ABwAIAOST3lPK980BAAAAAA==
Response: 200
When authentication breaks a GET request to /another/index looks like this:
> GET /another/index
Response: 401
Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate
> GET /another/index
Request Header: Authorization: Negotiate TlRMIVNDUAACAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==
Response: 401
Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate
At this point I am prompted for credentials, which I enter, the same request, response is resent:
> GET /another/index
Request Header: Authorization: Negotiate TlRMIVNDUAACAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw==
Response: 401
Response Headers: WWW-Authenticate: NTLM, WWW-Authenticate:Negotiate
Does anyone know if I have something misconfigured (I'm fairly sure I don't or I wouldn't expect it to work at all), why the authentication breaks or how I can stop it?
Many thanks all.

So it turns out this was as a result of the bad usage of the httpErrors section of the web.config
<httpErrors errorMode="Custom">
<remove statusCode="401" subStatusCode="-1"/>
<error statusCode="401" path="/Unauthorized" responseMode="ExecuteURL"/>
<remove statusCode="403" subStatusCode="-1"/>
<error statusCode="403" path="/Unauthorized" responseMode="ExecuteURL"/>
<remove statusCode="404" subStatusCode="-1"/>
<error statusCode="404" path="/NotFound" responseMode="ExecuteURL"/>
<remove statusCode="500" subStatusCode="-1"/>
<error statusCode="500" path="/ServerError" responseMode="ExecuteURL"/>
</httpErrors>
This resulted in the NTLM requirement to resubmit the credentials not being sent from the server when authentication was required. i.e. What should happen Request, Challenge (please resubmit with credentials) Response. What actually happened Request, Response. The Challenge was never sent.
The correct approach is to remove this section completely and in my errors controller actions use the extension method TrySkipIisCustomErrors.

Related

Second NTLM Authorization Header

When performing a curl with --ntlm, what is happening between the WWW-Authenticate header being sent back, and then then the second NTLM Authorization header being sent to finally return a 200?
Authorization: NTLM xxxxxxxx
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: NTLM xxxxxxxx
Authorization: NTLM xxxxxxxxxx
< HTTP/1.1 200 OK
I want to be able to take the first NTLM header (this stays constant with the username/password I believe), and build it into a script, take the returned header, and send the second NTLM one back to authenticate. What I don't understand is how the challenge (WWW-Authenticate header?) is taken in, and then sent back as another NTLM header.
I have tried using the WWW-Auth header as the second NTLM-Auth header, I didnt expect it to work but tried.
NTLM authentication is a multi-step process, which is achieved over HTTP as follows:
Client makes an HTTP request (no authentication data provided).
Server responds with a 401 Unauthorized, and advertises that it supports NTLM authentication via the WWW-Authenticate: NTLM header.
The client generates its first authentication token using InitializeSecurityContext and sends it to the server in the Authorization: NTLM <base64 encoded client token #1> header.
The server takes the client's token and passes it to AcceptSecurityContext, which generates the server's token. The server again responds with 401 Unauthorized, but this time includes its token in the WWW-Authenticate: NTLM <base64 encoded server token> header.
The client takes the server's token, and passes it to InitializeSecurityContext to generate its second token that it sends back to the server in the Authorization: NTLM <base64 encoded client token #2> header.
The server takes the client's second token and passes it to AcceptSecurityContext, which (assuming the authentication is successful) completes the process, and the server returns the normal 200 OK response.
Both InitializeSecurityContext and AcceptSecurityContext return some additional data on the initial call that must be included in the subsequent calls, making the authentication process stateful, so all of the above steps must occur over the same connection to the server.
NTLM is actually a three-request handshake, where the client doesn't send any credentials the first time a resource is requested. This means that the first request is anonymous, even though credentials have been configured for the resource. When Windows authentication is enabled and anonymous authentication is disabled, this anonymous request results in an HTTP 401 status.
The second request will be an NTLM challenge, in which the client resends the original request with an additional "Authorization" header containing NTLM (Type-1 message). The server then sends an NTLM challenge (Type-2 message) back to the client with an HTTP 401 status.
The third request will be the original request that the client sends again by adding a challenge-response (NTLM Type-3 message) to the Authorization header. The server then authenticates the user and returns a response with an HTTP 200 status if successful.

How to develop test-automation using Postman when OAuth 2.0 authorization is required

I have an ASP.NET Web API 2 which is using OAuth 2.0 for authorization. And let's imagine I have a simple Web API method, like:
GET: http://host/api/profiles/user123 (requires OAuth 2.0 token)
So, with Postman, it is easy to test this Web API. I get an OAuth token for user123 from the Web API OAuthAuthorization method and then I use that token in the header of the HTTP request:
GET /api/profiles/user123 HTTP/1.1
Host: {host}
Authorization: Bearer {Token}
Content-Type: application/json
Cache-Control: no-cache
However, if I save my test and run it later (either by Postman itself or by Newman), the token will be expired at that time and it won't work.
How can I make Newman to get a new token automatically for user123 and use it in the HTTP request?
Note: I know how to use Postman's Authentication helpers to ask for a new token. But this scenario doesn't fit the test-automation. In test-automation, I want to remove any human interaction.
It's simple, get your access token at run time and save it into environment variable. Then use it in your next Get request.
In Get Token request, do this in Tests sections:
var body = JSON.parse(responseBody);
pm.environment.set('AccessToken', body.access_token);
In your main Get request, you can use the environment variable in Authorization header:
Authorization: Bearer {{AccessToken}}
Hope this helps.

Cross-Origin Request Blocked while authenticating

I am trying to design an OAuth2 authentication system that secures a variety of backend APIs. I started by downloading and installing the three interconnected Spring Boot / Cloud / OAuth2 apps in this github project.
But my project requires two major architectural changes:
1.) The main portal UI must be running in Node.js, so that users can
view a public site and also login using a Node.js-hosted app that
makes REST calls to a backend authentication server, without feeling
like they are being redirected anywhere for authentication.
2.) My app requires multi-factor authentication, so I need to create (or
at least customize) my own endpoints on the `authserver` app instead
of relying to the standard password authentication endpoint.
What specific changes need to be made so that my Node.js-hosted UI app can successfully interact with the authserver app and the resource app?
At the moment, adding AngularJS login code to either my own Node.js portal app OR to the ui app in the github sample results in the FireFox console showing the following error messages when the AngularJS code tries to call the authserver app running on port 9000:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://localhost:9000/login-form. (Reason: CORS
header 'Access-Control-Allow-Origin' missing). <unknown>
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://localhost:9000/login-form. (Reason: CORS
request failed).
The AngularJS code that calls the new /login-form endpoint that I added to the authserver endpoint is:
$scope.credentials = {};
$scope.loginError = false;
$scope.showLogin = false;
$scope.loginShow = function() {
$scope.showLogin = true;
console.log('filler to add break point for debugger.');
};
$scope.loginLocal = function() {
console.log('Just filler to add a break point in debugger.');
var funcJSON = { 'type': 'Message',
'content1': $scope.credentials.username,
'content2': $scope.credentials.password
};
console.log('filler to add break point.');
$http.post('http://localhost:9999/uaa/login-form', funcJSON).then(function(response) {
if(response.data.content1==='success'){
$scope.Oauthenticated = true;
console.log('filler to add a break point');
}else {
$scope.Oauthenticated = false;
$scope.loginError = true;
console.log('filler to add break point');
}
});
};
The FireFox debugger shows that the line of the above AngularJS code that throws the error in the FireFox console shown at top is:
$http.post('http://localhost:9999/uaa/login-form', funcJSON).then(function(response) {
I added a new /login-form end point to the AuthserverApplication.java file in the authserver app, and you can read my entire new AuthserverApplication.java file at a file sharing site by clicking on this link.
I am open to running the main portal UI app inside of Spring Boot. I have read that this would require using the #EnableSidecar annotation. However, I get the same error message above whether the new login form is run inside the Spring Cloud ui app from the github link above, or from my Node.js-hosted portal UI. So what do I need to change to set up a secure way of managing this authentication from my Node.js-hosted portal app?
ONGOING RESEARCH:
Per #Ulises' suggestion, I added code to override a method of AuthserverApplication.java. I also double checked the url and slightly altered the url for the $http.post(... call (which I changed in-line above in the OP to avoid confusion. The result is the same error in the FireFox console, plus an explicit log of the request in the Spring Boot log for the authserver app.
You can read my new AuthserverApplication.java class including #Ulises's suggestion at a file sharing site by clicking this link. The Node.js-hosted app that makes the call is running on port 7000.
And you can read the entire Spring Boot log for the request at a file sharing site by clicking on this link.
Similarly, when I change the suggested method to read:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/login-form").allowedOrigins("http://localhost*");
}
I get the Spring Boot error log that you can read at a file sharing site by clicking on this link. And the Network tab of the FireFox debugger gives a 401 error, with the following raw headers:
Request headers:
Host: localhost:9999
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://localhost:7000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-requested-with
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Response headers:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate, no-store
Content-Type: application/xhtml+xml;charset=UTF-8
Date: Wed, 13 Apr 2016 09:32:40 GMT
Expires: 0
Pragma: no-cache, no-cache
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
WWW-Authenticate: Bearer realm="null", error="unauthorized", error_description="Full authentication is required to access this resource"
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
x-content-type-options: nosniff
The same new error persists even when I add the following method to the LoginConfig inner class inside AuthserverApplication.java to try to get Spring Security to ignore the /login-form endpoint:
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("/login-form", "/error");
}
I am currently reading the Spring OAuth2 Developer's Guide at this link, which refers to sample apps on github at this link. However, the sample apps use JSP, which is obsolete, and does not address the use case described in this OP.
In your AuthServerApplication override method addCorsMapping(CorsRegistry) from WebMvcConfigurerAdapter like this:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/login-form").allowedOrigins("http://localhost:9000");
}
Or wherever origin you're calling it from. You can also use * for everything and/or add any fine-grained configuration

How to enable CORS for simple AJAX web application

I have searce a lot of solutions about AJAX call CORS, but I still can not get XML data from that other server.
This is console note:
XMLHttpRequest cannot load url.xml. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access
I have follow instructions from: http://www.html5rocks.com/en/tutorials/cors/ section: CORS from jQuery, this is code that I try:
$.ajax({
type:'GET',
url:'http://www.someurl.xml',
contentType:'text/plain',
xhrFields:{
withCredentials: false
},
headers: {
'Access-Control-Allow-Origin':'http://localhost:8080',
'Access-Control-Allow-Method':'GET',
'Access-Control-Allow-Headers':'Content-Type,x-requested-with,Authorization,Access-Control-Allow-Origin'
},
success: function(data){
var test = data;
}
});
I know that this question has been asked for many times, but no answer help to fix my problem.
For testing in localhost I am using IIS 8.5
The headers section in your $.ajax code adds headers to the request to the server, but CORS headers need to be present on responses from the server.
Working with IIS, you can add those headers with a few lines in the <system.webServer> section of your web.config. Adding this will get you started for GET requests:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
</system.webServer>
See this post for more information: http://encosia.com/using-cors-to-access-asp-net-services-across-domains/
The CORS headers must be sent by the server you are making the call to, you cannot provide them with the request itself.
When making a request across domains, your browser will perform a preflight request to get the relevant CORS headers before actually requesting data (I believe this uses the OPTION method).
If the CORS headers from preflight include the current origin or a wildcard matching it, then the browser will continue on with the real request and fetch some data.

How to remove unwanted WWW-Authenticate headers

From an MVC app, I'm sourcing an iCal subscription with authentication following the answer to this SO question:
Serving an iCalendar file in ASPNET MVC with authentication
The iCal stream is being created dynamically from events in the DB using the DDay.iCal library.
This solution works fine on the local development server: both OSX Calendar and Outlook can subscribe to and receive updates from the app.
However, on the shared server at my web host, the authentication fails for both Calendar and Outlook. That is, they both keep asking me for user & password after the (correct) ones fail.
EDIT: If I point a browser at the calendar URL it also fails authentication.
EDIT: Getting weirder—Firefox authenticates and gets the iCal file. Safari, Chrome and IE fail authentication.
If I point curl at the calendar URL with the same credentials I'm successful (i.e. I get the desired iCal file). And, of course, the same credentials can be used to login to the MVC app.
EDIT — I think I know what's going on, but I don't know how to fix it. In my OnAuthorization() I add only WWW-Authentication Basic but with Fiddler I can see that three types of authentication are offered:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Secure Calendar"
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
... etc ...
At this point only Firefox responds with Basic Authorization, which succeeds.
GET <<URL>> HTTP/1.1
...
Authorization: Basic <<encoded credentials>>
IE responds with Negotiate, which fails
GET <<URL>> HTTP/1.1
...
Authorization Negotiate <<encoded stuff>>
Who is adding the other two and how can I make it stop? Here's more detail from the server response:
HTTP/1.1 401 Unauthorized
Cache-Control: private
Transfer-Encoding: chunked
Content-Type: text/html
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
WWW-Authenticate: Basic realm="Secure Calendar"
X-AspNet-Version: 4.0.30319
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
X-Powered-By-Plesk: PleskWin
Date: Tue, 23 Oct 2012 13:27:48 GMT
Thanks,
Eric
Ha ha, the answer lay in IIS configuration.
I asked the admins at my host to turn off the other authentications, which broke everything but the iCal feed.
Now they've turned a couple back on again and the MVC site works as well as the calendar feed with authentication... whew! Very, very big smile.
Here's the IIS configuration we ended up with:
Name Status Response Type
Anonymous Authentication Enabled
ASP.NET Impersonation Disabled
Basic Authentication Disabled HTTP 401 Challenge
Digest Authentication Disabled HTTP 401 Challenge
Forms Authentication Enabled HTTP 302 Login/Redirect
Windows Authentication Enabled HTTP 401 Challenge
I'm not sure why this works—or what else might break—but today I'm happy.
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
are used by Windows authentication. Since you finally enabled anonymous authentication, all WWW-Authenticate headers will not appear.
Easy way :
If you want this "X-Powered-By-Plesk" Header to be removed from EVERY NEWLY created domains, you can create a default web.config file within the "httpdocs" folder of the "Default Host Template".
This default website template is usually located under : "C:\inetpub\vhosts.skel\0\httpdocs".
That web.config file will be used by default when you create a new website.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="X-Powered-By-Plesk" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
TIP 1 : You can use this method to remove any unwanted Custom header (In order to not tell too much to bad guys about your server) :
<remove name="X-Powered-By"/>
<remove name="X-Powered-By-Plesk"/>
<remove name="X-AspNet-Version"/>
<remove name="X-AspNetMvc-Version"/>
TIP 2 : If you want to remove any Dynamic header (like the famous "Server" header), you will need to operate with outboundRules :
<configuration>
<system.webServer>
<rewrite>
<outboundRules>
<rule name="StripHeader_Server" patternSyntax="Wildcard">
<match serverVariable="RESPONSE_SERVER" pattern="*"/>
<action type="Rewrite" value=""></action>
</rule>
<rule name="StripHeader_ETag">
<match serverVariable="RESPONSE_ETag" pattern=".+" />
<action type="Rewrite" value="" />
</rule>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>
TIP 3 : Additionally, you can use this default web.config file to set all configuration parameters you want to use for every new website (in example : to define a list of default documents for your websites, as explained on this Plesk Help article : https://support.plesk.com/hc/en-us/articles/213364049-How-to-configure-global-default-document-settings-in-Parallels-Plesk )
As a belated answer to this, you could also handle this by creating a custom message handler.
The message handler would be inheriting from DelegatingHandler and has to be added to the HttpConfiguration its MessageHandlers
A way this could look would be the following:
public class EnsureNoAuthenticationHeaderHandler : DelegatingHandler
{
async protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken )
{
var response = await base.SendAsync( request, cancellationToken );
if ( response.StatusCode == System.Net.HttpStatusCode.Unauthorized )
{
response.Headers.Remove( "WWW-Authenticate" );
}
return response;
}
}
And then register it in the HttpConfiguration somewhat like the following
private void Register( HttpConfiguration configuration )
{
configuration.MessageHandlers.Add( new EnsureNoAuthenticationHeaderHandler() );
}
Which you would probably call from your global configuration. A message handler can also be attached to a route directly, so if you don't want it to be available everywhere, just have a looked at the linked article on MSDN for more explanation
I had the same problem.
The response included 3 WWW-Authenticate headers and only Firefox worked correctly. Chrome, Bing and IE prompted for username and password but after that they did not send the Authenticate Header to the server.
I just changed IIS Authentication settings and it was solved:
Anonymous Authentication Enabled
ASP.NET Impersonation Disabled
Basic Authentication Disabled HTTP 401 Challenge
Forms Authentication Disabled HTTP 302 Login/Redirect
Windows Authentication Disabled HTTP 401 Challenge

Resources