Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusable way to allow an account to be used by a single person at a time

Tags:

asp.net-mvc

I made a functionality that prevents multiple-login for one username at the same time and I call it in Actions like this:

        int userId = (int)WebSecurity.CurrentUserId;

        if ((this.Session.SessionID != dba.getSessionId(userId)) || dba.getSessionId(userId) == null)
        {
            WebSecurity.Logout();
            return RedirectToAction("Index", "Home");
        }

So the point is that every time user logins I save his sessionID into database field. So if someone with same username logins over someone already logged in with same username it overwrites that database field with this new session. If sessionID in DB is not the same as current session ID of logined user it log him out.

Is there a possibility to put this part of code in 1 place or do I have to put it in every single Action in my application?

I tried in Global.asax:

void Application_BeginRequest(object sender, EventArgs e)
    {
        if (Session["ID"] != null)
        {
            int userId = Convert.ToInt32(Session["ID"]);
            if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
            {
                WebSecurity.Logout();
            }
        }
    }

But I can't use Session here nor WebSecurity class if I try like this:

    void Application_BeginRequest(object sender, EventArgs e)
    {
        int userId = (int)WebSecurity.CurrentUserId;

        if ((this.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
        {
            WebSecurity.Logout();
            Response.RedirectToRoute("Default");                
        }
    }

because I get null reference exception.

EDIT

I used this:

    void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
    {
        int userId = (int)WebSecurity.CurrentUserId;
        using (var db = new UsersContext())
        {
            string s = db.getSessionId(userId);

            if ((filterContext.HttpContext.Session.SessionID != db.getSessionId(userId)) || db.getSessionId(userId) == null)
            {
                WebSecurity.Logout();
                filterContext.Result = new RedirectResult("/Home/Index");
            }
        }
    }

I had to use using statement for context, otherwise db.getSessionId(userId) was returning old sessionId. Method is this:

    public string getSessionId(int userId)
    {
        string s = "";
        var get = this.UserProfiles.Single(x => x.UserId == userId);
        s = get.SessionId;
        return s;
    }

Very strange, will have to read about why that happened.

Everything works fine, except one thing. I have one JsonResult action in a controller, which returns Json, but since event(its textbox on enter event) can't trigger POST(I assume it's because it logs out before) redirect doesn't work. It can't even post to that Json action to receive callback and redirect. Any clues on that?

                        success: function (data) {
                        if (data.messageSaved) {
                            //data received - OK!
                        }
                        else {
                            // in case data was not received, something went wrong redirect out
                            window.location.href = urlhome;
                        }
                    }

Before I used ActionFilterAttribute I used code to check different sessions inside of POST and of course it could make callback and therefore redirect if didn't receive the data.. But now since it can't even POST and go into method it just stucks there and doesn't redirect :)

like image 940
sensei Avatar asked Aug 08 '13 22:08

sensei


3 Answers

I would derive from AuthorizeAttribute. No need to check this information if you don't need to authorize the request.

public class SingleLoginAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    { 
        bool isAuthorized = base.AuthorizeCore(httpContext);

        if (isAuthorized)
        {
            int userId = (int)WebSecurity.CurrentUserId;

            if ((filterContext.HttpContext.Session.SessionID != dba.getSessionId(userId)) 
                || dba.getSessionId(userId) == null)
            {
                WebSecurity.Logout();
                isAuthorized = false;
                filterContext.Result = new RedirectResult("/Home/Index");
            }
        }

        return isAuthorized;
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.Result = new JsonResult()
            {
                Data = FormsAuthentication.LoginUrl,
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

I'd also mention that this allows you to short circuit other ActionFilters because they run after OnAuthorization.

  1. Forward Order - OnAuthorization : AuthorizationFilter (Scope Controller)
  2. Forward Order - OnActionExecuting : ActionFilter1 (Scope Global)
  3. Forward Order - OnActionExecuting : ActionFilter2 (Scope Controller)
  4. Forward Order - OnActionExecuting : ActionFilter3 (Scope Action)

Then as Rob Lyndon mentioned, you could in the FilterConfig (MVC4)

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new SingleLoginAuthorizeAttribute());
    }
}

Then when you don't want to require any authorization, you can use the AllowAnonymouseAttribute on your ActionResult methods or Controller Class to allow anonymous access.

Update

I added a way for your ajax calls (Get or Post) to work with timeouts. You can do something like:

success: function (jsonResult)
{
  if (jsonResult.indexOf('http') == 0)
  {
    window.location = jsonResult;
  }

  // do other stuff with the Ajax Result
}

This isn't exactly the best way, but if you want more information on how to do this better I would ask another question instead of appending more questions on this one.

like image 195
Erik Philips Avatar answered Oct 13 '22 14:10

Erik Philips


The ActionFilterAttribute is the way to go.

like image 3
user2639740 Avatar answered Oct 13 '22 15:10

user2639740


We created an Action Filter called SeatCheck and decorate each controller like this:

[SeatCheck]
public class NoteController : BaseController
{

We use that to get a count of seats and other functions, but it makes it so much easier to control everywhere without thinking about it.

In the proejct ActionFilters folder we have the SeatCheck.cs file that looks like this:

namespace site.ActionFilters
{
  public class SeatCheckAttribute : ActionFilterAttribute
  {
     public override void OnActionExecuting(ActionExecutingContext filterContext)
     {

You can get the SessionID in the Action Filter like this

 filterContext.HttpContext.Session.SessionID
like image 2
davids Avatar answered Oct 13 '22 13:10

davids