Where exactly does Forms Authentication exist in the Http Pipeline?
This is handled by an HTTP module, System.Web.Security.FormsAuthenticationModule. If you look at the system-wide web.config file, c:\Windows\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config, you can see where it's mentioned in the <httpModules> section. The site-specific web.config file will inherit the configuration in that file.
On each request, the module will look for an authentication cookie. If it's not present, the request is redirected to the login page. On a successful login, an authentication cookie is sent back to the browser. Then on subsequent requests, the browser will send the cookie, which will be validated by the module, and then the request is handled as usual.
Guess I should've thought of this first but it didn't dawn on me until I saw the answer from #Carl Raymond that I can just crack it open in reflector. So to answer my own question
public void Init(HttpApplication app)
{
if (!_fAuthChecked)
{
_fAuthRequired = AuthenticationConfig.Mode == AuthenticationMode.Forms;
_fAuthChecked = true;
}
if (_fAuthRequired)
{
FormsAuthentication.Initialize();
app.AuthenticateRequest += new EventHandler(this.OnEnter);
app.EndRequest += new EventHandler(this.OnLeave);
}
}
OnEnter calls the private method OnAuthenticate which passes in the application context and this is where it validates/writes out the Form Auth tickets.
In OnExit it checks the response for a Http Status Error Code 401 and if it finds it, that's when it redirects to the Login Url.
Related
Via ASP.NET I have created a startup file that will use Azure AD to log in a user
e.g.
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
ClientId = "42067b8d-b972-44e9-af86-ef60bc6d6fdb",
Authority = "https://login.windows.net/...com",
RedirectUri = "http://localhost:50560/content/story_html5.html",
PostLogoutRedirectUri = "http://localhost:50560/content/story_html5.html",
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken
});
}
And as you can see my RedirectUri in hitting a static file html file.
On my app registration in Azure portal my manifest for the replyUrls states
"replyUrls": [
"http://localhost:50560/content/story_html5.html"
],
So everything is working and connecting correctly.
(if I use a aspx for example the redirection would work)
However using the .html file I'm getting the error
HTTP Error 405.0 - Method Not Allowed
The page you are looking for cannot be displayed because an invalid
method (HTTP verb) is being used.
All I believe I need to do is add the html handler to Azure AD, does anyone know how to do this?
Thanks
This has nothing to do with Azure AD, but your configuration. Your end. Your Project. Your IIS config. Because sign-in response is a HTTP POST for security reasons. And static files handler in IIS does not accept anything beside GET for obvious reasons.
More information you will find here and there.
First, why would you want to redirect to a static page?! With the redirection after OIDC login, the IdP (Identity Provider, understand Azure AD in that case) sends valuable information which is needed by the OIDC middleware (understand the .UseOpenIdConnectAuthentication method) to be able to verify the token and initialize user session. By sending the sign-in response back to a static page you accomplish couple of things:
You cut out the OIDC middleware from the authentication - it is no longer able to process the response. Because it will not listen on static file requests. Static files are processed outside your OWIN authentication middleware.
Thus not able to verify authenticity of the user.
Thus not able to create secure cookie.
Thus not able to sign-in the user into your application.
Conclusion
Do not change the reply URL for your ASP.NET middleware, unless you explicitly and knowingly want to override the complete handling of sign-in responses.
We have an internal system where users can authenticate using Windows authentication but we want to include some custom content in the 401 repsonse that takes the user to a custom username/password authentication page if the cancel off the Windows authentication dialog.
When an unauthenticated user accesses a page, Windows authentication responds with a 401 Unauthorized response, with the headers
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
This prompts the browser to show a dialog asking for Windows credentials. If the user enters their credentials, there is another handshake request-response, and then the user is authenticated and the requested page is shown.
If the user cancels the dialog, the browser displays the content of the original 401 Unauthorized reponse.
We use Application_EndRequest in the Global.asax to return some custom content here which takes the user to our custom login system.
/// <summary>
/// If we are about to send the NTLM authenticate headers, include this content. Then, if the user cancels off the
/// windows auth dialog, they will be presented with this page and redirected to the username / password login screen
/// </summary>
void Application_EndRequest(object sender, EventArgs e)
{
Logger.DebugFormat("in Application_EndRequest. Response.StatusCode: {0}", Response.StatusCode);
if (Response.StatusCode == 401)
{
Response.ContentType = "text/html";
var redirectUrl = string.Format("https://mycompany.com/SSO/Login?encryptedSessionRequestId={1}",
HttpUtility.UrlEncode(Request.QueryString["encryptedSessionRequestId"]));
var text = File.ReadAllText(Server.MapPath("~/UnauthorizedError.html"));
text = text.Replace("[USERNAME_PASSWORD_LOGON_REDIRECT]", redirectUrl);
Response.Write(text);
Logger.Debug("Done writing response");
Response.End();
}
}
UnauthorizedError.html contains the following script which forwards the user on
<script language="javascript" type="text/javascript">
window.location = "[USERNAME_PASSWORD_LOGON_REDIRECT]";
</script>
When running locally in (Win7/IIS7.5), this works great, we use Response.Write() to send our content and the user is able to see it when they cancel the dialog.
But when accessing the site remotely, e.g. when it running in our development environment, although we can see from the logs that Application_EndRequest is being called and our content written to the response, it is overridden at some point and all that reaches the browser is the authentication headers and the text You do not have permission to view this directory or page.
1) How can we prevent this content being overwritten?
2) Where in the pipeline might this be happening?
3) Does anyone have any suggestions of another way to implement this behaviour, e.g. with an HTTP module?
Thanks!
I wasted a lot to solve this problem but there isn't direct solution.
It because win auth works on iis not site level and you can't control how to auth current request.
There is several hacky ways to use redirection on different login pages depending on ip.
I know this is an old post but I would like to share a solution to this problem.
When altering the response message for remote requests (read: non-localhost) you will need to add the following to your config file:
<system.webServer>
<httpErrors existingResponse="PassThrough"></httpErrors>
</system.webServer>
If you do not allow the response to "pass through" remote clients will get the default "You do not have permission to view this directory or page".
I got this info from: https://stackoverflow.com/a/17324195/3310441
I'm just reading about implementing my own HTTP handler for ASP.NET 4.0 and IIS7. This looks really cool. I want special processing for ZIP files and it seems like an HTTP handler is the perfect solution.
However, what's giving me trouble is that the handler must be in a separate assembly. So how can I access the rest of my application from this assembly?
Specifically, I'd like to determine if the user is authenticated and redirect them to the login page if they are not. But User.Identity.IsAuthenticated, etc. will not be available from my handler.
(Yes, I know there are ways to approach this without an HTTP handler but they don't seem appropriate for my specific needs.)
User.Identity.IsAuthenticated, etc. will not be available from my handler.
The ProcessRequest method gives you the current HTTP context from which you could determine if the user is authenticated:
public void ProcessRequest(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
// the user is not authenticated
}
...
}
I'm working on an app using ASP.Net's form authentication. The client also makes RESTfull calls to the server (ExtJS components on front end).
We are using a custom HttpHandler for the service calls.
My problem is that anytime the anytime the authentication cookie expires my HttpHandler 's ProcessRequest method isn't called in order for me to check for the cookie's absence and redirect the user to log in again.
An example would be a user leaves a page open then comes back in 20 mins and clicks on a dropdown that is loaded asynchronously. The app just hangs never getting to my handler.
Any thoughts?
Highly suggest reading the section entitled "The Pipeline Event Model" in this MSDN magazine article: Securely Implement Request Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET.
In a nutshell, authentication is performed well before the request is handed over to ProcessRequest() in your HttpHandler. If you need to handle these cases, you will need to hook into the pipeline events (such as BeginRequest or Authenticate Request) and add your own handlers, like so:
public class EnableWebServicesModule :
IHttpModule
{
public void Init(HttpApplication app)
{
// register event handler
app.BeginRequest += new EventHandler(this.OnBeginRequest);
}
public void OnBeginRequest(object obj, EventArgs ea)
{
// Check if security works here by looking for the cookie or
// the user context.
}
...
}
For further reading on this fascinating and exciting topic, check Rich Strahl's walkthrough: A low-level Look at the ASP.NET Architecture
We have an ASP.NET app protected by forms authentication. The app uses MS AJAX heavily to call its web-services.
When the forms authentication times out, and a GET-request happens - all is fine (the user is redirected to a login page).
BUT when the forms authentication times out and a POST-request happens (ajax) - no redirect happens, instead the app returns "401 unathorized" and the browser prompts for username and password (not a login form, but a browsers built-in dialog). Of course entering ANY username/password never helps.
How do I handle this?
UPDATE: After looking with firebug, I just found out that regular POST requests redirect to login fine, it's only web-service calls that throw "401 Unauthorizes".
The difference between a regular request and web-service is URL. Which is "page.aspx" for a regular post-request and "service.asmx/MethodName" for webservices...
Ok, answering my own questin.
After looking into this issue and researching a bit more I found that when a web-app is protected by Forms-Authentication and the user is not authenticated, this is what happens:
If it's a GET-request - the user is
redirected to the login page.
If it's a POST-request to a page - the user is
redirected to the login page.
If it's a POST-request to a web-service - the
user gets 401-unauthorized
Thats how ASP.NET works
And if a web-service is called by AJAX (xmlHttpRequest object) and returns 401 - of course the browser shows a pop-up login box.
Now, what should you do is add some code to Application_PostAuthenticateRequest that will prevent throwing 401 for webservices.
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (Request.RequestType == "POST" //if its POST
&& !User.Identity.IsAuthenticated //if user NOT authed
&& !HasAnonymousAccess(Context) //if it's not the login page
)
{
//lets get the auth type
Configuration config = WebConfigurationManager.OpenWebConfiguration("~");
SystemWebSectionGroup grp = (SystemWebSectionGroup)config.GetSectionGroup("system.web");
AuthenticationSection auth = grp.Authentication;
//if it FORMS auth
if(auth.Mode== AuthenticationMode.Forms)
{
//then redirect... this redirect won't work for AJAX cause xmlHttpRequest can't handle redirects, but anyway...
Response.Redirect(FormsAuthentication.LoginUrl, true);
Response.End();
}
}
}
public static bool HasAnonymousAccess(HttpContext context)
{
return UrlAuthorizationModule.CheckUrlAccessForPrincipal(
context.Request.Path,
new GenericPrincipal(new GenericIdentity(string.Empty), null),
context.Request.HttpMethod);
}
I see two solutions:
(1) "Heart beat" mechanism. On each page include a script that will "ping" the server by some dummy ajax request, like:
<script>
setInterval(ping, 60000); // based on comment by John
function ping()
{
$.get('/do/nothing');
}
</script>
This way the session shouldn't expire as long as the browser window is open.
(2) On each ajax request check the status of the response. If the response has "401 unauthorized" code (or any other code different that 200), that means that the session expired and instead of loading the response into some dialog box in the page redirect the user to login page.
Conclusion based on comments:
The best solution would be to combine the two above mechanisms. Heartbeat mechanism will help to keep the session alive as long as the page is displayed in the browser. But in doesn't guarantee that for sure. The connection to the server can be broke and reopened when the session is expired. So you should check the response status anyway.