ASP.NET Session State Providers - asp.net

Our ASP.NET app sends multiple Ajax requests in parallel on the same session. We also read/write to HttpSessionState during some of those requests. What I WANT is for all the concurrent requests to execute in parallel for performance reasons. What I get is they are serialized by ASP.NET. I experimented with configuring enableSessionState="ReadOnly", but this breaks our Forms Authentication.
Is there a way to get both session state AND concurrency in one session? Do I need to use a custom SessionState or Provider? Any samples of this out there?
PS I'm not worried about thread safety when accessing the SessionState - I can do that programmatically.

From MSDN (link):
However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished.
So at least for those AJAX calls that require write access to the Session you are out of luck with the default providers.
Not sure if you could get around this using a custom provider.
You can achieve parallell execution for those AJAX calls that do not need access to the session by blocking the ASP.NET_SessionId cookie in an HttpModule. See my answer to this question.
Edit: In order to make this answer more self reliant, I add a slightly modified version of the HttpModule and a bit of discussion (further down). Here's the module code that you can use to prevent Session state from serializing your Ajax calls:
using System;
using System.Web;
namespace TestModule
{
public class TestPreventCookie : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication application)
{
application.BeginRequest +=
(new EventHandler(this.Application_BeginRequest));
application.PostAcquireRequestState +=
(new EventHandler(this.Application_PostAcquireRequestState));
}
private void Application_BeginRequest(Object source, EventArgs e)
{
//prevent session cookie from reaching the service
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (BlockCookie(context))
{
context.Request.Cookies.Remove("ASP.NET_SessionId");
}
}
private void Application_PostAcquireRequestState(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
if (BlockCookie(context))
{
var s = context.Session;
if (s != null)
s.Abandon();
}
}
private bool BlockCookie(HttpContext context)
{
// put code here that determines if the session cookie should be blocked
// this could be based on the path of the current request for instance
// only block the cookie when you *know* that access to the Session is not needed
}
}
}
The idea behind this module is that using some criteria based on the project requirements, we remove the ASP.NET_SessionId cookie from the current context (note, we don't expire it on the client).
This means that further on in the request pipeline, the server will create a new session. In order to prevent this newly created session from destroying the existing ASP.NET_SessionId cookie on the client, we abandon it immediately after it has been created.
The end result is that each request that is "intercepted" by the module will execute as if it had no session.

Related

Securing SignalR Calls

I'm using the SignalR Javascript client and ASP.NET ServiceHost. I need the SignalR hubs and callbacks to only be accessible to logged in users. I also need to be able to get the identity of the currently logged in user from the Hub using the FormsIdentity from HttpContext.Current.User.
How do I secure the hub's so that only authenticated users can use SignalR?
How do I get the identity of the currently logged in user from the Hub?
You should use the this.Context.User.Identity that is available from the Hub. See a related question
EDIT: To stop unauthenticated users:
public void ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
this.Clients.SendUnauthenticatedMessage("You don't have the correct permissions for this action.");
return;
}
// user is authenticated continue
}
EDIT #2:
This might be better, just return a message
public string ThisMethodRequiresAuthentication()
{
if(!this.Context.User.Identity.IsAuthenticated)
{
// possible send a message back to the client (and show the result to the user)
return "You don't have the correct permissions for this action.");
// EDIT: or throw the 403 exception (like in the answer from Jared Kells (+1 from me for his answer), which I actually like better than the string)
throw new HttpException(403, "Forbidden");
}
// user is authenticated continue
return "success";
}
You can lock down the SignalR URL's using the PostAuthenticateRequest event on your HttpApplication. Add the following to your Global.asax.cs
This will block requests that don't use "https" or aren't authenticated.
public override void Init()
{
PostAuthenticateRequest += OnPostAuthenticateRequest;
}
private void OnPostAuthenticateRequest(object sender, EventArgs eventArgs)
{
if (Context.Request.Path.StartsWith("/signalr", StringComparison.OrdinalIgnoreCase))
{
if(Context.Request.Url.Scheme != "https")
{
throw new HttpException(403, "Forbidden");
}
if (!Context.User.Identity.IsAuthenticated)
{
throw new HttpException(403, "Forbidden");
}
}
}
Inside your hub you can access the current user through the Context object.
Context.User.Identity.Name
For part 1. of your question you could use annotations like below (This worked with SignalR 1.1):
[Authorize]
public class MyHub : Hub
{
public void MarkFilled(int id)
{
Clients.All.Filled(id);
}
public void MarkUnFilled(int id)
{
Clients.All.UnFilled(id);
}
}
Something missing from the other answers is the ability to use SignalR's built in custom auth classes. The actual SignalR documentation on the topic is terrible, but I left a comment at the bottom of the page detailing how to actually do it (Authentication and Authorization for SignalR Hubs).
Basically you override the Provided SignalR AuthorizeAttribute class
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
Then you decorate your hubs with [CustomAuth] above the class declaration. You can then override the following methods to handle auth:
bool AuthorizeHubConnection(HubDescriptor hubDesc, IRequest request);
bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod);
Since I'm on IIS servers and have a custom auth scheme, I simply return true from the AuthorizeHubConnection method, because in my Auth HttpModule I already authenicate the /signalr/connect and /signalr/reconnect calls and save user data in an HttpContext item. So the module handles authenticating on the initial SignalR connection call (a standard HTTP call that initiates the web socket connection).
To authorize calls on specific hub methods I check method names against permissions saved in the HttpContext (it is the same HttpContext saved from the initial connect request) and return true or false based on whether the user has permission to call a certain method.
In your case you might be able to actually use the AuthorizeHubConnection method and decorate your hub methods with specific roles, because it looks like you are using a standardized identity system, but if something isn't working right you can always revert to brute force with HttpModule (or OWIN) middle-ware and looking up context data in on subsequent websocket calls with AuthorizeHubMethodInvocation.

HttpModule is breaking PostBack events

I'm trying to setup a simple HttpModule to handle authentication between my single sign on server. I've included code for the module below. The module is hitting my SSO and properly authenticating; however, on pages with forms the postback events are not occurring properly (e.g. isPostBack value is always false even though a POST occurred, button click events don't get hit, etc.).
public sealed class MyAuthenticationModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
}
public void Dispose()
{
}
public static void OnAuthenticateRequest(object sender, EventArgs e)
{
FormsAuthentication.Initialize();
HttpContext context = HttpContext.Current;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
// Validate the ticket coming back from the authentication server
if (!string.IsNullOrEmpty(request["ticket"]))
{
// I can include code for this if you want, but it appears to be
// working correct as whenever I get a ticket from my SSO it is processed
// correctly. I only get a ticket after coming from the SSO server and
// then it is removed from the URL so this only gets hit once.
MyAuthentication.ProcessTicketValidation();
}
if (!request.IsAuthenticated)
{
// redirect to the login server
response.Redirect("https://sso.example.com/login.aspx" + "?" + "service=" +
HttpUtility.UrlEncode(context.Request.Url.AbsoluteUri), false);
}
}
}
EDIT
I would also like to note that if I change the line:
if (!string.IsNullOrEmpty(request["ticket"]))
to:
if (!string.IsNullOrEmpty(request.QueryString["ticket"]))
the problem goes away.
Is it possible that your postbacks have a duplicate form data variable, "ticket"? That would seem to explain the behavior to me.
Aside from that, this line is suspicous:
FormsAuthentication.Initialize();
The FormsAuthentication class uses the "Provider" pattern, which means it's a singleton. You should not re-initialize. From the msdn documentation:
The Initialize method is called when the FormsAuthenticationModule
creates an instance of the FormsAuthentication class. This method is
not intended to be called from your code.

Get last activity after timeout in ASP.NET WebForms

I have an ASP.NET WebForms page with forms authentication. When users create a login, I use 'remember me' to create the authentication cookie.
What I now want to do is check the time of their last access. But LastLogin time is updated only when the user uses the login control (which they don't need to use when they have the authentication cookie on their machine), and LastActivity control is updated before any of my code runs.
It looks like the only way I can do this is to hook into the application event Application_AuthenticateRequest - right? Or is there some better way to do this?
Thanks!
Yes you will want to hook the FormsAuthenticationModule.Authenticate event. You can do this by adding a module to your web application. See the following sample module code.
public class BasicAuthenticateModule : IHttpModule
{
public BasicAuthenticateModule()
{
}
public void Dispose()
{
}
public void Init(HttpApplication context)
{
foreach (string name in context.Modules.Keys)
{
if (name == ApplicaionModules.FormsAuthentication)
{
FormsAuthenticationModule module = (FormsAuthenticationModule)context.Modules[name];
module.Authenticate += new FormsAuthenticationEventHandler(module_Authenticate);
break;
}
}
}
private void module_Authenticate(object sender, FormsAuthenticationEventArgs e)
{
}
}
Enjoy!
Instead I used the session_start event in Global.asax.
In there I've stored the current and previous session start DateTime's against the user in the DB (moving the current to the previous each time). This gets me the time of a user's previous session.
It might be better to use session_end - but that's not the time the user left the page, it's [timeout] time after their last activity - so this is a fairly good solution.

Session variable getting lost?

Given this Global.asax.cs:
using System;
using System.Web;
namespace Foo.Web {
public class Global : HttpApplication {
private const string IntroductionPageShownSessionKey = "IntroductionPageShownSessionKey";
protected void Application_AcquireRequestState(object sender, EventArgs e) {
ShowIntroductionIfNotYetShown();
}
private void ShowIntroductionIfNotYetShown() {
if (HttpContext.Current.Session != null) {
var introductionPageShown = Convert.ToBoolean(Session[IntroductionPageShownSessionKey]);
if (!introductionPageShown) {
if (Request.Path.EndsWith("/Introduction.aspx")) {
Session[IntroductionPageShownSessionKey] = true;
}
else {
Response.Redirect("~/Introduction.aspx" + Request.Url.Query);
}
}
}
}
}
}
User hits webapp and is shown Introduction.aspx
User continues using webapp for a few minutes (ASP.NET_SessionId: ublbhu45ji31e055ywqu0555)
User falls idle (doesn't perform any postbacks) for a few minutes
User performs postback
User is shown Introduction.aspx
Second inspection of user's ASP.NET_SessionId cookie still shows ublbhu45ji31e055ywqu0555
Why is the user shown Introduction.apsx the second time inside the same ASP.NET Session? I'm familiar w/ the risk in setting session variables just before a redirect in the same postback, but that doesn't apply here, right?
Keep in mind that the Session itself likely has a shorter lifetime than the session cookie being sent to the browser and the ID value set in that cookie. In fact, the browser can continue to submit an old session ID, and the server will accept it and create a new session from it, if the old was expired.
The implications being two possibilities:
1) The session is timing out due to the timeout config value (I know not the case in your particular instance)
2) What we've figured out since in your case via the comments to this question: the AppDomain was shutting down or being recycled.

How can I utilize or mimic Application OnStart in an HttpModule?

We are trying to remove the global.asax from our many web applications in favor of HttpModules that are in a common code base. This works really well for many application events such as BeginRequest and PostAuthentication, but there is no Application Start event exposed in the HttpModule.
I can think of a couple of smelly ways to overcome this deficit. For example, I can probably do this:
protected virtual void BeginRequest(object sender, EventArgs e)
{
Log.Debug("Entered BeginRequest...");
var app = HttpContext.Current.Application;
var hasBeenSet app["HasBeenExecuted"] == null ? false : true;
if(!hasBeenSet)
{
app.Lock();
// ... do app level code
app.Add("HasBeenExecuted", true);
app.Unlock();
}
// do regular begin request stuff ...
}
But this just doesn't smell well to me.
What is the best way to invoke some application begin logic without having a global.asax?
Just keep a static bool in the HttpModule:
private static bool _hasApplicationStarted = false;
private static object _locker = new object();
private void EnsureStarted()
{
if (_hasApplicationStarted) return;
lock (_locker)
{
if (_hasApplicationStarted) return;
// perform application startup here
_hasApplicationStarted = true;
}
}
Then have any method that needs the application to have started just call EnsureStarted.
HttpModules and HttpHandlers will execute on every single request, while the Global.asax App Start event is when the application starts, thus only once.
You could make a general global.asax which will load all assemblies with a specific interface, and then drop in the dll's you want executed for that specific application. Or even register them in your web.config, and have your general global.asax read the keys, and then load and execute the code you want.
I think this is better than putting app once code in a module and checking on a state variable.

Resources