IIS site for both intranet and anonymous users - asp.net

I'm working on an application that should be available to intranet users running under their own account (Windows authentication). This is easily configured and works.
Now if users are not logged in to the domain (because they are off site or on a device that is not logged in) they should still be able to use the application, minus some personalized functionality.
So to sum that up, this is what I would like to happen:
User opens the application. If windows credentials are available the browser sents them to IIS.
If the users credentials are recieved, the application runs under these credentials (I've got that covered).
If the users credentials are not recieved, the application runs under the IIS anonymous account and personalized functionality is turned off (I've got that covered as well).
What I can't get to work is to optionally send the credentials. If I turn on windows authentication, I'll be logged in, which is fine. However if I try to access the site without sending credentials I'll get a 401, which makes sense. So I turn on anonymous authentication and now credentials are never sent.
This actually makes sense, because IIS never requests for authentication from the browser. The question is, how do I make this scenario work?

You are right on your analysis.
HTTP authentication schemes are :
Anonymous access
Challenge-Response authentications
Negotiate (Kerberos)
NTLM
Digest
Basic authentication
On IIS (and most HTTP servers), the default authentication process respect the order above. ie. If anonymous access succeeded (will not get into details here) others authentication providers are ignored even if they are enabled.
HTTP 401 Challenge
If you want to manage multiple authentication methods and providers you have to use a mechanism which refuse the credentials when you consider them invalid. You can achieve this by sending 401 Responses. This is called HTTP 401 Challenge.
The idea is to tell to the client (browser) that the credentials used for the requested resource are refused.
Depending on the scenario and the client configuration, the client may handle an authentication. And in this case the authentication process may vary : Challenge-Response providers needs a certain number of exchanges to valid the credentials.
Anyway, in your case, with anonymous access enabled, the first 401 response will be interpreted by the browser as "This request requires an authentication". The server automatically include the supported authentication providers in the response header if they are enabled on the server side.
HTTP/1.1 401 Unauthorized
Server: Microsoft-IIS/7.5
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="+Upgraded+v1b3a89710edce01754fd608...",charset=utf-8,realm="Digest"
WWW-Authenticate: Basic realm="host"
X-Powered-By: ASP.NET
Content-Length: 0
Proxy-Support: Session-Based-Authentication
If your browser is correctly configured to send the credentials for the zone of your web application (you said so), it'll automatically use the first authentication provider it knows (NTLM for example) and re-process the request with the credentials it knows (Windows credentials in your case).
GET http://host/yourpage.aspx HTTP/1.1
Accept-Encoding: gzip, deflate
Authorization: NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAFASgKAAAADw==
Connection: Keep-Alive
When the authentication process fail, the server automatically send a 403 Forbidden response to the client to avoid too much traffic. A 403 response stops the challenge. The good news is it takes some challenge to happen : more than 4.
And usually, an HTTP authentication challenge needs near to 3 Challenge-Response maximum to succeed (3 for NTLM and 2 for Negotiate -Kerberos-).
Since you allow anonymous access, the server will not block the client requests and your pages will be called with anonymous credentials. You can still interact with your client in your page by setting yourself the HTTP Response Code. As already said, its only works if you enable another authentication provider in addition to Anonymous.
So the trick is to handle it with a counter on your server side and say "if my auth session/cookie counter is more than 3, my client can't authenticate with the server. Let's say he is anonymous".
Some Code
I didn't do exactly what you need but you can adapt my code :
int i = 3;
int j = 0;
HttpContext httpContext = HttpContext.Current;
// Record authentication process
HttpCookie httpCookie2 = httpContext.Request.Cookies["loginAttemptCount"];
if (httpCookie2 == null || httpCookie2.Value == String.Empty)
{
httpCookie2 = new HttpCookie("loginAttemptCount");
}
else
{
j = Int32.Parse(httpCookie2.Value, System.Globalization.CultureInfo.InvariantCulture);
}
j = (j + 1) % i;
string user = Request.ServerVariables["LOGON_USER"];
// Send 401 responses to authenticate the user
if (j != 0 && user == String.Empty)
{
httpCookie2.Value = j.ToString(System.Globalization.CultureInfo.InvariantCulture);
httpContext.Response.Cookies.Add(httpCookie2);
Response.StatusCode = 401;
return;
}
httpCookie2.Value = String.Empty;
httpContext.Response.Cookies.Add(httpCookie2);
If needed, you can check the authorization provider in the Authorization header.
Request.Headers["Authorization"]
You can use Fiddler to trace your HTTP headers.
Hope it's clear enough.

Related

Http basic authentication mechanism

After the user requests a protected resource X the server responds
with code 401.
The browser prompts the user to inser user-name and
password and automatically re-send the request to the server with
those authentication information
My question is : is this process repeated over and over for each protected resource ?
Look at RFC 2617. There is stated for basic-athentication :
Upon receipt of an unauthorized request for a URI within the
protection space, the origin server MAY respond with a challenge ...
and also
A client SHOULD assume that all paths at or deeper than the depth of
the last symbolic element in the path field of the Request-URI also
are within the protection space specified by the Basic realm value of
the current challenge. A client MAY preemptively send the
corresponding Authorization header with requests for resources in
that space without receipt of another challenge from the server.
Similarly, when a client sends a request to a proxy, it may reuse a
userid and password in the Proxy-Authorization header field without
receiving another challenge from the proxy server.
So, from the server side this may occur at any request the the server deems unauthenticated. If resource Y does not share the prefix that had been yuthenticated with resource X then the server will re-request authentication.
For avoiding this the authentication scheme e.g. could request authentication for a common prefix of the related resources , such that authentication for prefix of resource X also covers resource Y as a prefix. This will allow the client to send the authentication header and cause the server to detect the call as already being authenticated.
Once the user input the password, the browser will remember it.
each time the client request the resource at the same website, the browser will send the authentication header automatically.

ASP.NET MVC Website Partial SSL Authentication cookie not submitted in request

I'm trying to make a POC of which is possible to have a website that uses http and https. So i have a control in my master page that needs info if the user is authenticated or not. For this I want to use HttpContext.Current.User.Identity.IsAuthenticated. If is authenticated shows info for authenticated users, if not appear the login control.
To authenticate the control make an AJAX POST request to the Login action that has the [RequireHttps] attribute. The URL used in the AJAX request is:
$.ajax({
type: 'POST',
url: '#Url.Action("ModalLogIn", "Authentication", null, "https", Request.Url.Host + ":44300")',
By the way I'm using VS2013 IIS express with SSL enabled.
As you can see in my AJAX request i'm using the HTTPS in action url.
The request is made to the server using SSL and the response is made with success.
The problem is that in the subsequent requests the ASPXAUTH cookie is not passed in the request header. So the server does not get the user authentication info. The subsequent requests are made with no SSL, are simple HTTP requests.
I know that in security terms the authentication is still insecure because i'm expecting to pass the ASPXAUTH through HTTP, but like I said is a POC and I want to see if it is possible to make a simple authentication request using HTTPS and all the others using HTTP.
As requested this is the Response Headers:
Access-Control-Allow-Orig... *
Cache-Control private
Content-Length 15
Content-Type application/json; charset=utf-8
Date Sat, 26 Oct 2013 18:57:55 GMT
Server Microsoft-IIS/8.0
Set-Cookie ASP.NET_SessionId=j2a53htev0fjp1qq4bnoeo0l; path=/; HttpOnly
ASP.NET_SessionId=j2a53htev0fjp1qq4bnoeo0l; path=/; HttpOnly
IAC.CurrentLanguage=en; expires=Sun, 26-Oct-2014 19:57:55 GMT; path=/
.ASPXAUTH=730DEDBFD2DF873A5F2BD581AA0E25B685CAD12C26AEA63AD82484C932E26B617687A05BB403216CC5EFCF799970810059F9CA2CF829F953580AF81FF48102003C0129AB04424F0D011A733CAAF1DE00688E5A4C93DEA97338DD2B5E7EE752F3761A470D52449BEBCA74098912DE37AA8C1E293B1C5D44EB1F9E9384DAAEF289; path=/; HttpOnly
X-AspNet-Version 4.0.30319
X-AspNetMvc-Version 3.0
X-Powered-By ASP.NET
X-SourceFiles =?UTF-8?B?QzpcTXkgRGF0YVxCaXRidWNrZXRcaWFjLXdlYnNpdGVcaW1wbGVtZW50YXRpb25cZG90bmV0XElBQy5XZWJcQXV0aGVudGljYXRpb25cTW9kYWxMb2dJbg==?=
It might be that when you set the auth cookie, it is marked as "Secure".
Using the Chrome Developer Tools, click on 'Resources', then cookies. Under the 'Secure' column check if the cookie is marked. If it is, then this means that the browser will not send the auth cookie using a non-secure connection.
Just a shot in the dark, but try setting the ASPXAUTH cookie with an expiration date.
It's possible that the browser, upon receiving a session cookie, will only present the session cookie on connections using the same protocol (https) as when it was set. I know for sure that persistent cookies do not have this limitation.
Also, investigate whether port could be the issue. If your AJAX goes over 44300 and your web goes over 80 or 443, it's possible the cookie is lost because the browser considers secure cookies to be port-specific. The W3C spec doesn't say whether cookies are private with respect to port; browsers vary.
All things work perfect like that ajax request in HTTPS manner by JS. Related respond works correctly too. But it seems that you have not prepared Login page in SSL too! My meaning is :
[RequireHttps]
public ActionResult Login()
{
return View();
}
Then Send request to HttpPost enabled Action. I believe that will work correctly. Unless you had some lack of requirements like MicrosoftMvcAjax.js and MicrosoftAjax.js in situations that you are using formal Microsoft ajax form by your ViewEngine (Perhaps by Razor). I think studying this Article can help you more.
Good Luck.

Inconsistent security behaviour on IIS

We have a secured WCF service with WsHttpBinding, Client & Server certificates & transport security with Claim Based Authorization.
Everything works fine 50 % of the time. When we do request to a secure endpoint, we get the correct response. But if we send the same request again immidiatly after the first we get the following response:
The HTTP request was forbidden with client authentication scheme 'Anonymous'.
If we then send a request again, we get normal behaviour. So the odd requests work and the even not.
But after some more investigation in the problem. We noticed that if there is at least 1:40 minutes between the previous request, we don't get the error response.
What we can confirm from the debugger and logging. The client sends the credentials to the service. We don't enter System.ServiceModel if we have the Authentication response. In the IIS trace logs we get this on a good request:
Authentication: SSL/PCT
User from token: Domain\CertifacteUserName
and this on a bad request:
Authentication: NOT_AVAILABLE
User from token:
Also in the IIS Trace logging. If we send the second request, we see that the ConnId and RawConnId are the same on both requests. And if we make multiple successful request (By allowing some time between requests) they are different for each request.
It seems to me IIS is not getting the Credentials we send, when they are there. Is this due to caching? Or something else? Does anyone has a solution.

RESTful Login Failure: Return 401 or Custom Response

This is a conceptual question.
I have a client (mobile) application which needs to support a login action against a RESTful web service. Because the web service is RESTful, this amounts to the client accepting a username/password from the user, verifying that username/password with the service, and then just remembering to send that username/password with all subsequent requests.
All other responses in this web service are provided in a JSON format.
The question is, when I query the web service simply to find out whether a given username/password are valid, should the web service always respond with JSON data telling me its successful or unsuccessful, or should it return HTTP 200 on good credentials and HTTP 401 on bad credentials.
The reason I ask is that some other RESTful services use 401 for bad credentials even when you're just asking if the credentials are valid. However, my understanding of 401 responses are that they represent a resource that you are not supposed to have access to without valid credentials. But the login resource SHOULD be accessible to anyone because the entire purpose of the login resource is to tell you if your credentials are valid.
Put another way, it seems to me that a request like:
myservice.com/this/is/a/user/action
should return 401 if bad credentials are provided. But a request like:
myservice.com/are/these/credentials/valid
should never return 401 because that particular URL (request) is authorized with or without valid credentials.
I'd like to hear some justified opinions one way or the other on this. What is the standard way of handling this, and is the standard way of handling this logically appropriate?
First off. 401 is the proper response code to send when a failed login has happened.
401 Unauthorized
Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.
Your confusion about, myservice.com/are/these/credentials/valid sending back 401 when you just do a check, I think is based on the fact that doing boolean requests in REST often is wrong by the RESTful constraints. Every request should return a resource. Doing boolean questions in a RESTful service is a slippery sloop down to RPC.
Now I don't know how the services that you looked on are behaving. But a good way of solving this is to have something like an Account object, that you try to GET. If your credentials are correct, you will get the Account object, if you don't want to waste bandwidth just to do a "check" you can do a HEAD on the same resource.
An Account Object is also a nice place to store all those pesky boolean values that otherwise would be tricky to create individual resources for.
401 should be sent only when the request needs authorization header field and authorization fails. Since the Login API doesn't require authorization, hence 401 is the wrong error code in my opinion
As per the standard here https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*10.4.2 401 Unauthorized
The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information. HTTP access authentication is explained in "HTTP Authentication: Basic and Digest Access Authentication" [43].*
If the 401 response code is misleading for user authentication, the API can send HTTP status code 200 OK for both successful and failed authentication, but set a custom header on the authentication successful response and omit that header on failed logins.
The client can check if the header exists or not and decide the action.
Example: SpringBoot API Response
The call to OK when login is successful sets the header "gotyouin" with a value (anything). Call to failed does not add the header and client can treat this as a failed login attempt.
public class LoginResponseEntityHelper {
public static ResponseEntity<?> ok(String token) {
return ResponseEntity.status(HttpStatus.OK).header("gotyouin", token).body(null);
}
public static ResponseEntity<?> failed() {
return ResponseEntity.status(HttpStatus.OK).body(null);
}}
It is logical to use 401 http status code when access to a resource is denied because the request lacks or has incorrect credentials. And when correct credentials are provided, the request should complete successfully granting access to the protected resource.
Applicable for your case: myservice.com/this/is/a/user/action.
Maybe we should be clear about this credentials
In most secure applications, credentials are needed to access protected resource(s). These credentials can be sent along every request via HTTP header. Specifically the Authorization Header.
Therefore the Authorization header contains credentials to allow a user to access protected resource(s).
Remember that a user who was verified successfully as an authorized user(successful login), is the one given this priviledged credentials to allow for access on protected resources on the server.
From the server point of view, a HTTP request targeting a protected resource yet it is lacking credentials or containing invalid credentials may cause to validly send back 401 response code.
Therefore the Login API should not send a 401 response code because of a failed login attempt. It is misleading. Reasoning out that you are requesting to the server application via the Login API to grant you with credentials required to access resources that are protected. By itself, the Login API is not a protected resource.
So what is the correct response status?
Well I will opine towards sending back a 400 response code because a failed login attempt is a client error who is at fault for not providing the correct username or password.
According to RFC standards about 400 code:
The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing)
Again I will reiterate that failed login is a client error due to providing details that are incorrect. Sending back a 400 doesn't have to mean that the request syntax is malformed. But a malformed syntax is one of the reasons.
Sending a 401 would really be misleading as the user doesn't need to provide authentication information to access the login API.

Why are my ASP.NET pages returning `200 OK` without any authentication headers being sent?

Note:
This question has broadened in scope from previous revisions. I have tried to simplify the issue so it can be easily reproduced by anyone.
Using Fiddler, I can replay an arbitrary request to my default page after erasing my Authorization header from the HTTP request, and I am able to get a response of 200 OK with valid data.
Bounty Update
Here are the steps to reproduce this exact behavior:
1. Create a "New Website" in ASP.NET, feel free to name it "InsecureWebsite"
2. Edit web.config to deny all unauthenticated users:
<authentication mode="Windows" />
<authorization>
<deny users="?"/>
<allow users="*"/>
</authorization>
3. Publish the website to an arbitrary directory on a DEV server and create a virtual directory for the application
4. Ensure the application has script access (.ASP) and Integrated Windows Authentication enabled
5. Open Fiddler to capture the traffic
6. Load the page in your favorite browser and look at the "Inspectors" tab within Fiddler, you'll see a request similar to:
GET /InsecureWebsite/ HTTP/1.1
Host: dev.subdomain.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5 (.NET CLR 3.5.30729)
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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Authorization: NTLM
{Base64-encoded authentication data}
The initial request to Default.aspx will return a 401 Unauthorized, will go into negotiation, and then finally return a 200 OK.
In Fiddler I can then erase the Authorization header directly from a replayed request to Default.aspx and still get a 200 OK. How is that possible?
Solution
It turns out that Fiddler uses the same underlying connection when making the requests, so once the connection is authenticated, any request on the same connection will also be authenticated as the same user as the initial request. You can turn this feature off in Fiddler here:
Screenshot of Fiddler options http://john.cognitivedelay.com/images/fiddler-options.gif
Once this has been unchecked, any replayed requests from within Fiddler will return a 401 Unauthorized as I would expect.
Thanks to all who offered their time to respond!
Edit: per updated question:
Are you doing the replay in Fiddler itself, or by making a direct connection to the webserver? It might be that Fiddler is reusing an existing HTTP connection (which it can do, as a proxy)... I think IWA might mark the whole connnection as authenticated, not just the current request, which means that any future requests on the same connection re-use the authorization and authentication from the first negotiation...
Original answer:
Try
[WebMethod(EnableSession=true)]
[PrincipalPermission(SecurityAction.Demand, Authenticated=true)]
and see if that helps?
(Possibly [PrincipalPermission(SecurityAction.Demand, Role="myDevRole")] if that's more appropriate for you...)
The Ajax call is done on a new thread for an existing authenticated session, That's why you don't see any authentication information in the headers. The session is already authenticated.
You can get the authenticated user's identity, and then pass that on to any role management routines, by referencing System.Threading.Thread.CurrentPrincipal.Identity.Name:
[WebMethod(EnableSession = true)]
public static string WhoAmI()
{
// Return the identity of the authenticated windows user.
Return System.Threading.Thread.CurrentPrincipal.Identity.Name;
}
You could potentially add authentication information to the header of the message, then authenticate yourself in the webmethod.
or you could try something like this or this
Add this attribute to the web method
[PrincipalPermissionAttribute( SecurityAction.Demand, Role = "myDevRole" )].
Then on Global.asax event Application_AuthenticateRequest you can make sure that current thread user is authenticated correctly - i.e. do what is necessary to avoid fraud cookies or sessions.
Using Windows authentication on your local development machine, every request is going to be from an authenticated user. So, deny users="?" will never deny any requests locally.
If you were hitting this on a remote IIS machine you aren't authenticated with or were to use Forms Authentication, it would require authentication before you could successfully request either Default.aspx or the page method.

Resources