SignalR and ASP.NET Identity ExpireTimeSpan - signalr

I am using ASP.NET Identity with cookie based authentication. I am setting the ExpireTimeSpan on the CookieAuthenticationOptions class to control how much time of inactivity is allowed before the user has to log in again.
This all works fine, but when I add SignalR to the application the user no longer has to log-in after a period of inactivity. SignalR does a "ping" request periodically and I presume it is this that causes the cookie expiry to be extended.
I am looking for a way to not renew the cookie expiry for the SignalR URLs.
I have looked into some of the code in Microsoft.Owin.Security.Cookies and the CookieAuthenticationHandler class in particular. There is logic in the AuthenticateCoreAsync method to decide if to renew the cookie. However, the CookieAuthenticationHandler class in internal so I can't override this method.
Any ideas if there is a hook I can use to do this?

We solved at my company by removing the cookies from the signalr response, using an HttpModule.
public class NoFormsAuthenticationModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += OnPreSendRequestHeaders;
}
protected void OnPreSendRequestHeaders(object sender, EventArgs e)
{
var httpContext = ((HttpApplication)sender).Context;
var path = httpContext.Request.Path;
var noAuthentUrls = new string[] { "/signalr/" };
foreach (var url in noAuthentUrls)
{
var noAuthentication = path.IndexOf(url, StringComparison.OrdinalIgnoreCase) > -1;
if (noAuthentication)
httpContext.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
}
}
}
Hope it helps you.
Dont forget to add the entries on the web.config:
< system.web>
< httpModules>
< add name="NoFormsAuthenticationModule" type="Site.Components.HttpModules.NoFormsAuthenticationModule"/>
< system.webServer>
< modules runAllManagedModulesForAllRequests="true">
< add name="NoFormsAuthenticationModule" type="Site.Components.HttpModules.NoFormsAuthenticationModule"/>
...

Related

How to give Owin the user identity?

tl;dr: What is the Owin equivalent of the HttpApplication.AuthenticateRequest event?
Background
When running an ASP.net site on IIS, the global System.Web.HttpApplication object raises an AuthenticateRequest event during each request.
Various http modules (such as the built-in FormsAuthentication) can attach to the event. The event handlers are called in the order in which they are registered. The first handler to set HttpContext.Current.User is the authentication used.
The job of the modules that are subscribed to this event is to set HttpContext.Current.User to to some Principal:
IIdentity identity = new GenericIdentity("MBurns", "ContosoAuthentcation");
IPrincipal principal = new GenericPrincipal(identity, null);
HttpContext.Current.User = principal;
Once HttpContext.Current.User is assigned, ASP.net knows that the user has been authenticated. (And once a user has been authenticated, they are no longer anonymous).
Any Module Can Do It
Anyone can use web.config to register their own IHttpModule with ASP.net:
web.config
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="MySuperCoolAuthenticationModule" type="ContosoAuthModule" />
</modules>
</system.webServer>
The module is easy enough to write. You implement the lone Init method of the IHttpModule interface. For us, we add ourself as an AuthenticateRequest event handler:
public class ContosoAuthModule : IHttpModule
{
public void Init(HttpApplication httpApplication)
{
// Register event handlers
httpApplication.AuthenticateRequest += OnApplicationAuthenticateRequest;
}
}
And then you can do what is needed to authenticate the user, and if they are a valid user, set the HttpContext.Current.User:
private void OnApplicationAuthenticateRequest(object sender, EventArgs e)
{
var request = HttpContext.Current.Request;
String username = SomeStuffToFigureOutWhoIsMakingTheRequest(request);
if (String.IsNullOrWhiteSpace(username))
{
//I don't know who they are :(
return;
}
//I know who they are, they are [username]!
IIdentity identity = new GenericIdentity(username, "ContosoSuperDuperAuthentication");
HttpContext.Current.User = new GenericPrincipal(identity, null);
}
That's all HttpApplication
MSDN documents the various events that are thrown by HttpApplication, and in what order:
ASP.NET Application Life Cycle Overview for IIS 7.0 (archive.is)
Validate the request, which examines the information sent by the browser and determines whether it contains potentially malicious markup. For more information, see ValidateRequesta and Script Exploits Overviewa.
Perform URL mapping, if any URLs have been configured in the UrlMappingsSectiona section of the Web.config file.
Raise the BeginRequest event.
Raise the AuthenticateRequesta event.
Raise the PostAuthenticateRequest event.
Raise the AuthorizeRequest event.
Raise the PostAuthorizeRequest event.
Raise the ResolveRequestCache event.
And that's all great when it's ASP.net and HttpApplication. Everything's well understood, easy enough to explain (in the half-screenful above), and works.
But HttpApplication is old and busted.
Owin is the new hotness
Everything is supposed to be Owin now. HttpApplication lives in System.Web. People want to be isolated from System.Web. They want this thing called Owin to be in charge now.
To further that goal, they (i.e. any new ASP.net MCV, web-forms, or SignalR web-site) disables the authentication system of ASP.net completely:
<system.web>
<authentication mode="None" />
</system.web>
So no more HttpApplication.AuthenticateRequest event. :(
What is the Owin equivalent?
What is the Owin equivalent of HttpApplication.AuthenticateRequest?
It's safe to say that no matter where my code is called from, my job is still to set HttpContext.Current.User to an identity.
Is it safe to say that no matter where my code is called form, my job is still to set HttpContext.Current.User to an identity?
What is the Owin equivalent of HttpApplication.AuthenticateRequest?
Attempt that doesn't work
Nothing of it is ever called:
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using System.Web;
using System.IO;
using Microsoft.Owin.Extensions;
using System.Security.Claims;
using System.Security.Principal;
[assembly: OwinStartup("AnyStringAsLongAsItsNotBlank", typeof(BasicAuthOwin))]
public class BasicAuthOwin
{
public void Configuration(IAppBuilder app)
{
app.Use((context, next) =>
{
System.Diagnostics.Trace.WriteLine("They did their best, shoddily-iddly-iddly-diddly");
OnAuthenticateRequest(context);
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
app.Run(context =>
{
return context.Response.WriteAsync("Hello world");
});
}
private void OnAuthenticateRequest(IOwinContext context)
{
var request = context.Request;
String username = SomeStuffToFigureOutWhoIsMakingTheRequest(request);
if (String.IsNullOrWhiteSpace(username))
{
//I don't know who they are :(
return;
}
//I know who they are, they are [username]!
IIdentity identity = new GenericIdentity(username, "ContosoSuperDuperOwinAuthentication");
context.Authentication.User = new ClaimsPrincipal(identity);
}
private string SomeStuffToFigureOutWhoIsMakingTheRequest(IOwinRequest request)
{
//if ((System.Diagnostics.Stopwatch.GetTimestamp % 3) == 0)
// return "";
return "MBurns";
}
}
Check out the blog post from this website Jwt Authentication in ASP.NET WEB API AND MVC. It explains how to solve to issue of "Authorization has been denied for this request" using OWIN.
The JWTHandler class
public static void OnAuthenticateRequest(IOwinContext context)
{
var requestHeader = context.Request.Headers.Get("Authorization");
int userId = Convert.ToInt32(JwtDecoder.GetUserIdFromToken(requestHeader).ToString());
var identity = new GenericIdentity(userId.ToString(), "StakersClubOwinAuthentication");
//context.Authentication.User = new ClaimsPrincipal(identity);
var token = requestHeader.StartsWith("Bearer ") ? requestHeader.Substring(7) : requestHeader;
var secret = WebConfigurationManager.AppSettings.Get("jwtKey");
Thread.CurrentPrincipal = ValidateToken(
token,
secret,
true
);
context.Authentication.User = (ClaimsPrincipal) Thread.CurrentPrincipal;
//if (HttpContext.Current != null)
//{
// HttpContext.Current.User = Thread.CurrentPrincipal;
//}
}
The Startup class
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
app.Use((context, next) =>
{
JwtAuthHandler.OnAuthenticateRequest(context); //the new method
return next.Invoke();
});
app.UseStageMarker(PipelineStage.Authenticate);
WebApiConfig.Register(config);//Remove or comment the config.MessageHandlers.Add(new JwtAuthHandler()) section it would not be triggered on execution.
app.UseWebApi(config);
}
}

Using OWIN to authorize classic ASP pages

I have an ancient Classic ASP website that used to use Windows authentication for access control but this is no longer appropriate. I thought I might try and wrap this website in an ASP.NET MVC 5 app, so I:
created a new website complete with ASP.NET Identity
created a user
copied the Classic ASP website into the root
viewed the classic ASP website
Now what I need to do is require authorization for all .asp pages so that only authorized users can see them. What's the best way of doing this? Maybe I could do something with OWIN?
Crispin
Following this example I created an HttpModule that came out like this:
public class ClassicAspAuthorization : IHttpModule
{
private MyEventHandler _eventHandler = null;
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(OnBeginRequest);
}
public delegate void MyEventHandler(Object s, EventArgs e);
public event MyEventHandler MyEvent
{
add { _eventHandler += value; }
remove { _eventHandler -= value; }
}
public void OnBeginRequest(Object s, EventArgs e)
{
HttpApplication app = s as HttpApplication;
if (app.Request.CurrentExecutionFilePathExtension.EndsWith(".asp") == true && blnIsAuthenticated() == false)
{
app.Context.Response.Redirect("/Account/Login");
}
if (_eventHandler != null)
{
_eventHandler(this, null);
}
}
and the boolean (blnIsAuthenticated) method that determined whether or not the user was authenticated was derived from a Stackoverflow answer where I removed the lines:
var identity = new ClaimsIdentity(claims, authenticationType, ClaimTypes.Name, ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
System.Threading.Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
and replaced this with my own claims checking to establish if the user was authenticated. An appropriate boolean value was returned.

Nancy and SignalR Authentication

I think I am missing something here because this shouldn't be this difficult.
I have a nancy IIS hosted application that uses authentication which works (Sets a cookie _nca). Then I have a signalR hub that needs to get the username of the logged in person so I can then pull the "Group" that the user belongs to.
I thought I would just grab the username from Context.User.Identity.Name but that is empty and the hub appears to think I am not authorized. I am guessing that there is something else I need to do to tell signalR how to authenticate.
I thought that maybe setting HttpContext.Current.User would help but that apparently didn't do anything either.
I did this by the following
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
pipelines.AfterRequest.AddItemToStartOfPipeline(SetUpContext);
//pipelines.AfterRequest.AddItemToEndOfPipeline(SetUpContext);
CookieBasedSessions.Enable(pipelines);
RouteTable.Routes.MapHubs();
}
private void SetUpContext(NancyContext ctx)
{
if (ctx.CurrentUser == null)
return;
string[] Roles = { "Recipient" };
var userIdent = new UserIdent();
userIdent.IsAuthenticated = true;
userIdent.Name = ctx.CurrentUser.UserName;
GenericPrincipal principal = new GenericPrincipal(userIdent, Roles);
IPrincipal Identity = (IPrincipal)principal;
HttpContext.Current.User = Identity;
}
I also tried pipelines.BeforeRequest.AddItemToEndOfPipeline. Still not having any luck.
Here is a sample using SignalR 2.0 and Cookie Authentication

Session expiration issue in ASP.NET MVC

Hi I have some issues with ASP.NET MVC session state which is not expiring after I implement the following piece of code and put the attributes over the methods.
public sealed class SessionActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
//Check if session is supported
if (ctx.Session != null)
{
//Check if the session is new
if (ctx.Session.IsNewSession)
{
//If it says it is a new session but an existing cookie exists
//then it must have timed out
string sessionCookie = filterContext.HttpContext.Request.Headers["Cookie"];
if ((sessionCookie != null) && (sessionCookie.IndexOf("ASP.NET_SessionId", StringComparison.OrdinalIgnoreCase) >= 0))
{
//Redirect to the login page
ctx.Response.Redirect("~/Home/Index", true);
ctx.Response.End();
}
}
}
base.OnActionExecuting(filterContext);
}
}
The issue is that the Redirection request is not executing and the Action which has SessionActionFilter Attribute executes. This method uses session variables which are expired and results in error.
Can anybody tell what I am missing?
Thanks a lot in advance!!
We are storing some data which is used in our views!! One more update I was able to run this piece of code which is now working fine. However, I am a bit skeptical about the use of cookies and need to transform this code to work for cookieless sessions also. How is this possible?

Forms authentication: disable redirect to the login page

I have an application that uses ASP.NET Forms Authentication. For the most part, it's working great, but I'm trying to add support for a simple API via an .ashx file. I want the ashx file to have optional authentication (i.e. if you don't supply an Authentication header, then it just works anonymously). But, depending on what you do, I want to require authentication under certain conditions.
I thought it would be a simple matter of responding with status code 401 if the required authentication was not supplied, but it seems like the Forms Authentcation module is intercepting that and responding with a redirect to the login page instead. What I mean is, if my ProcessRequest method looks like this:
public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
}
Then instead of getting a 401 error code on the client, like I expect, I'm actually getting a 302 redirect to the login page.
For nornal HTTP traffic, I can see how that would be useful, but for my API page, I want the 401 to go through unmodified so that the client-side caller can respond to it programmatically instead.
Is there any way to do that?
ASP.NET 4.5 added the Boolean HttpResponse.SuppressFormsAuthenticationRedirect property.
public void ProcessRequest(HttpContext context)
{
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.SuppressFormsAuthenticationRedirect = true;
}
After a bit of investigation, it looks like the FormsAuthenticationModule adds a handler for the HttpApplicationContext.EndRequest event. In it's handler, it checks for a 401 status code and basically does a Response.Redirect(loginUrl) instead. As far as I can tell, there's no way to override this behaviour if want to use FormsAuthenticationModule.
The way I ended up getting around it was by disabling the FormsAuthenticationModule in the web.config like so:
<authentication mode="None" />
And then implementing the Application_AuthenticateEvent myself:
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (Context.User == null)
{
var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName);
if (oldTicket != null && !oldTicket.Expired)
{
var ticket = oldTicket;
if (FormsAuthentication.SlidingExpiration)
{
ticket = FormsAuthentication.RenewTicketIfOld(oldTicket);
if (ticket == null)
return;
}
Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);
if (ticket != oldTicket)
{
// update the cookie since we've refreshed the ticket
string cookieValue = FormsAuthentication.Encrypt(ticket);
var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ??
new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath };
if (ticket.IsPersistent)
cookie.Expires = ticket.Expiration;
cookie.Value = cookieValue;
cookie.Secure = FormsAuthentication.RequireSSL;
cookie.HttpOnly = true;
if (FormsAuthentication.CookieDomain != null)
cookie.Domain = FormsAuthentication.CookieDomain;
Context.Response.Cookies.Remove(cookie.Name);
Context.Response.Cookies.Add(cookie);
}
}
}
}
private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name)
{
FormsAuthenticationTicket ticket = null;
string encryptedTicket = null;
var cookie = context.Request.Cookies[name];
if (cookie != null)
{
encryptedTicket = cookie.Value;
}
if (!string.IsNullOrEmpty(encryptedTicket))
{
try
{
ticket = FormsAuthentication.Decrypt(encryptedTicket);
}
catch
{
context.Request.Cookies.Remove(name);
}
if (ticket != null && !ticket.Expired)
{
return ticket;
}
// if the ticket is expired then remove it
context.Request.Cookies.Remove(name);
return null;
}
}
It's actually slightly more complicated than that, but I basically got the code by looking at the implementation of FormsAuthenticationModule in reflector. My implementation is different to the built-in FormsAuthenticationModule in that it doesn't do anything if you respond with a 401 - no redirecting to the login page at all. I guess if that ever becomes a requirement, I can put an item in the context to disable the auto-redirect or something.
I'm not sure if this will work for everyone, but in IIS7 you can call Response.End() after you've set the status code and description. This way, that #&$^##*! FormsAuthenticationModule won't do a redirect.
public void ProcessRequest(HttpContext context) {
Response.StatusCode = 401;
Response.StatusDescription = "Authentication required";
Response.End();
}
To build on zacharydl's answer slightly, I used this to solve my woes. On every request, at the beginning, if it's AJAX, immediately suppress the behavior.
protected void Application_BeginRequest()
{
HttpRequestBase request = new HttpRequestWrapper(Context.Request);
if (request.IsAjaxRequest())
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
}
I don't know how that Response.End() worked for you. I tried it with no joy, then looked at MSDN for Response.End(): 'stops execution of the page, and raises the EndRequest event'.
For what it's worth my hack was:
_response.StatusCode = 401;
_context.Items["401Override"] = true;
_response.End();
Then in Global.cs add an EndRequest handler (which will get called after Authentication HTTPModule):
protected void Application_EndRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Items["401Override"] != null)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.StatusCode = 401;
}
}
what you've found out is correct about the forms auth intercepting the 401 and doing a redirect but we also can do that to reverse that.
Basically what you need is an http module to intercept the 302 redirect to the login page and reverse it to a 401.
Steps on doing that is explained in here
The given link is about a WCF service but it is the same in all the forms auth scenarios.
As explained in the above link you need to clear the http headers as well but remember to put the cookie header back to the response if the original response (i.e. before intercepting) contained any cookies.
I know there is already an answer with tick but while trying to solve similar problem I found this (http://blog.inedo.com/2010/10/12/http-418-im-a-teapot-finally-a-%e2%80%9clegitimate%e2%80%9d-use/) as an alternative.
Basically you return your own HTTP status code (e.g. 418) in your code. In my case a WCF data service.
throw new DataServiceException(418, "401 Unauthorized");
Then use a HTTP module to handle it at the EndRequest event to rewrite the code back to 401.
HttpApplication app = (HttpApplication)sender;
if (app.Context.Response.StatusCode == 418)
{
app.Context.Response.StatusCode = 401;
}
The browser / client will receive the correct content and status code, it works great for me :)
If you are interested to learn more about HTTP status code 418 see this question & answer.
That's a known issue, and there's a NuGet Package for that and/or the source code available.
Funny hack if you use.NET Framework >= v4.0 but < v4.5. It uses reflection to set value of inaccessible SuppressFormsAuthenticationRedirect property:
// Set property to "true" using reflection
Response
.GetType()
.GetProperty("SuppressFormsAuthenticationRedirect")
.SetValue(Response, true, null);
You do not set the WWW-Authenticate header in the code you show, so the client cannot do HTTP authentication instead of forms authentication. If this is the case, you should use 403 instead of 401, which will not be intercepted by the FormsAuthenticaitonModule.
I had the problem that I wanted to avoid not only the redirect but also the forms authentication itself in order to make a web api work. Entries in web.config with a location tag for the api didn't help.
Thus I used SuppressFormAuthenticationRedirect and HttpContext.Current.SkipAuthorization to suppress the authentication in general.
In order to identify the sender I used e.g. the UserAgent in the Header but it is of course recommendable to implement further authentification steps, e.g. check against the sending IP or send another key with the request.
Below is inserted in the Global.asax.cs.
protected void Application_BeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Request.UserAgent == "SECRET-AGENT")
{
AppLog.Log("Redirect suppressed");
HttpApplication context = (HttpApplication)sender;
context.Response.SuppressFormsAuthenticationRedirect = true;
HttpContext.Current.SkipAuthorization = true;
}
}
In order to redirect the user to Unauthorize Page rather than to the login page, the Hack is to implement Application_EndRequest in Global and check for Response Status Code 302, which is a temporary redirect from the current called to action.
protected void Application_EndRequest(object sender, EventArgs e)
{
if(HttpContext.Current.Response.StatusCode == 302 && User.Identity.IsAuthenticated)
{
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.Redirect("/UnauthorizedPageUrl");
}
}
Look inside your Web.config file in configuration\authentication.
If there is a forms subelement there with a loginUrl attribute, remove it and try again.

Resources