Logoff with database registration and cookie deletion behind Session_end() in Global.asax

Asked

Viewed 906 times

2

I need help with a project I’ve been racking my brain about for two days. I’m using Microsoft C# MVC5 technology and framework 4.5, I also use Entity Framework 6 with Repository Pattern, Unit of Work and Unity to perform dependency injection.

I have a controller called AccountController which is responsible for logging in and logging out the user on the system, this controller receives the application’s Repository methods by dependency injection via the same constructor.

Accountcontroller

public class AccountController : BaseController
{
    private readonly IUsuarioApp _usuarioApp;
    private readonly IUnitOfWorkAsync _unitOfWorkAsync;

    public AccountController() { }

    public AccountController(IUsuarioApp usuarioApp, IUnitOfWorkAsync unitOfWorkAsync)
    {
        _unitOfWorkAsync = unitOfWorkAsync;
        _usuarioApp = usuarioApp;
    }

    // GET: Login
    [AllowAnonymous]
    public ActionResult Login()
    {
        return View();
    }

    //
    // POST: /Account/Login
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public ActionResult Login([Bind(Include = "Email, Password")]LoginViewModel model, string returnUrl)
    {
        try
        {
            if (!ModelState.IsValid) return View(model);

            var usuarioAutenticado = _usuarioApp.AutenticarUsuarioPor(model.Email, model.Password);

            var logDeAcesso = new LogDeAcesso { DataDeAcesso = DateTime.Now, UsuarioId = usuarioAutenticado.Id };

            usuarioAutenticado.DataDoUltimoAcesso = logDeAcesso.DataDeAcesso;

            _usuarioApp.Update(usuarioAutenticado);
            _usuarioApp.GetRepository<LogDeAcesso>().Insert(logDeAcesso);
            _unitOfWorkAsync.SaveChanges();

            SessionContext.SetAuthenticationToken(usuarioAutenticado.Id.ToString(), false, ConvertToUsuarioViewModel(usuarioAutenticado));
            
            return RedirectToAction("Index", "Home");
        }
        catch (Exception ex)
        {
            ModelState.AddModelError("", "Tentativa de login inválido.");
            return View(model);
        }
    }

    public ActionResult LogOff(int id)
    {
        try
        {
            var ultimoLogsDeAcessoCriado = _usuarioApp.GetRepository<LogDeAcesso>().Query(model => model.UsuarioId == id).OrderBy(model => model.OrderByDescending(c => c.DataDeAcesso)).Select().FirstOrDefault();

            if (ultimoLogsDeAcessoCriado == null || ultimoLogsDeAcessoCriado.DataDeSaida != DateTime.MinValue) throw new Exception("Erro ao tentar deslogar do sistema.");

            ultimoLogsDeAcessoCriado.DataDeSaida = DateTime.Now;
            _usuarioApp.GetRepository<LogDeAcesso>().Update(ultimoLogsDeAcessoCriado);
            _unitOfWorkAsync.SaveChanges();

            FormsAuthentication.SignOut();
            Session.Clear(); //Pode não ser necessário, mas não é problemático o uso como prevenção
            Session.Abandon();

            //Limpar o cookie de Autenticação
            var resetFormsCookie = new HttpCookie(FormsAuthentication.FormsCookieName, "");
            resetFormsCookie.Expires = DateTime.Now.AddYears(-1);
            Response.Cookies.Add(resetFormsCookie);

            //Limpar a session cookie
            var resetSessionCookie = new HttpCookie("ASP.NET_SessionId", "");
            resetSessionCookie.Expires = DateTime.Now.AddYears(-1);
            Response.Cookies.Add(resetSessionCookie);

            //Invalida o Cache no lado do Cliente
            Response.Cache.SetCacheability(HttpCacheability.NoCache);
            Response.Cache.SetNoStore();
            
        }
        catch (Exception ex)
        {
            Danger("Error: " + ex.Message);
        }

        return RedirectToAction("Login", "Account");
    }


    #region Helpers
    
    private UsuarioViewModel ConvertToUsuarioViewModel(Usuario usuario)
    {
        return new UsuarioViewModel
        {
            Id = usuario.Id,
            Nome = usuario.Nome,
            UltimoAcesso = usuario.DataDoUltimoAcesso
        };
    }

    #endregion
}

As it is seen, when logging in the user is authenticated through the email and password, an instance of the Access Log is created where we register the date of entry of the access and later the cookie is created where the cookie will be stored that will allow access to the pages of the Site.

After obtaining access, the user can click on the logoff button, which will trigger the method ActionResult LogOff which will obtain the last log of access created based on the user id, update with the data of the Date of exit of the system, clean the Session and Cookies, redirecting it to the Login page. From there he will only have access again to the other pages if he logs in again.

It is dynamic works very well, but there is a problem, what if the user does not click the logoff button, and instead resolve to close the tab or the browser? As it was built, it will remain authenticated in the 20 min standards q the IIS standardizes for downtime, but the expiration time of the cookie, not to mention that in this way I will not be able to register the Exit Date in the Access Log.

Thinking about it I set up on Web.config a Session timeout time

Web.Config


&ltconfiguration>
  &ltsystem.web>
    &ltcompilation debug="true" targetFramework="4.5" />
    &lthttpRuntime targetFramework="4.5" />
    &ltglobalization culture="pt-BR" uiCulture="pt-BR" />
    &ltauthentication mode="Forms">
      &ltforms loginUrl="~/Account/Login" timeout="2880" slidingExpiration="true" />
    </authentication>
    &ltsessionState
       mode="InProc"
       stateConnectionString="tcpip=127.0.0.1:42424"
       stateNetworkTimeout="20"
       sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
       sqlCommandTimeout="20"
       customProvider=""
       cookieless="UseCookies"
       cookieName="ASP.NET_SessionId"
       timeout="1"
       allowCustomSqlDatabase="false"
       regenerateExpiredSessionId="true"
       partitionResolverType=""
       useHostingIdentity="true">
      &ltproviders>
        &ltclear />
      </providers>
    </sessionState>
    &ltmachineKey validationKey="466AFE06F664B2E3662F97B81D30E87907F9921E51C95C618A670B396403AD98DD032BCE7610EEAE1FB1DA7B3ED7ACE56537E66FD6DF20E701351697E57C3D9C" decryptionKey="CD10ABC11246E6998AB7B9A8CC142CDD6C8AEF7FB12D15CF12158BEAD647C603" validation="SHA1" decryption="AES" /> 
  </system.web>
</configuration>

Only when the timeout happens and I enter the Global.asax in the method Session_End(), I cannot access the repositories nor the FormsAuthentication.

I wonder what I have to do to repeat inside the Session_End() exactly what I do by clicking the logoff button. Could anyone help?

EDIT

Well, I think I can better clarify what I’m looking for. I seek to develop a way to register in the database and destroy the authentication ticket, Session and Cookie at the time of Timeout of Downtime.

2 answers

1


After much research, I’ve come to the conclusion that it’s impossible to accomplish logoff the way I perform on the button through the Session_End(). The way I cleaned up cookie after the event of timeout, was performing this in the Session_Start()


    protected void Session_Start()
        {
            try
            {
                var usuario = new SessionContext().GetUserData();

                if (usuario == null) return;

                Session.Clear(); //Pode não ser necessário, mas não é problemático o uso como prevenção
                Session.Abandon();

                //Limpar o cookie de Autenticação
                var resetFormsCookie = new HttpCookie(FormsAuthentication.FormsCookieName, "");
                resetFormsCookie.Expires = DateTime.Now.AddYears(-1);
                Response.Cookies.Add(resetFormsCookie);

                //Limpar a session cookie
                var resetSessionCookie = new HttpCookie("ASP.NET_SessionId", "");
                resetSessionCookie.Expires = DateTime.Now.AddYears(-1);
                Response.Cookies.Add(resetSessionCookie);

                //Invalida o Cache no lado do Cliente
                Response.Cache.SetCacheability(HttpCacheability.NoCache);
                Response.Cache.SetNoStore();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                Response.RedirectToRoute("Default");
            }
        }

And I left the following code in Session_end() as prevention.


    protected void Session_End()
    {
        Session.Clear();
        Session.Abandon();
    }

1

You can redirect the request to a route that has access to what you need:

protected void Session_End()
{
    Response.Clear(); 
    RouteData routeData = new RouteData(); 

    routeData.Values.Add("controller", "Account"); 
    routeData.Values.Add("action", "Login"); 

    IController AccountMainController = new AccountController(); 
    AccountMainController.Execute(new RequestContext( 
            new HttpContextWrapper(HttpContext.Current), routeData));
}
  • Hello Gypsy. Well I tried to implement the code you passed only I received an error the moment I pass through Response.Clear(), Httpexception: Answer is not available in this context. Also when analyzing the code I think I will have problems instantiating the controller since it receives by injection two parameters (usuarioApp and unitOfWorkAsync), which has no way to get here, at least I do not know how. Thank you for your help.

  • Comment on this line and see if the rest works.

  • In the Accountmaincontroller.Execute() line he gave the following exception: Argumentnullexception: Value cannot be null. Parameter name: httpContext

  • @Alanbessa I updated the answer.

  • Kept the same exception in the same place. Argumentnullexception: "Value cannot be null. Parameter name: httpContext"

  • There is no application lifecycle event at the end of the Timeout Session where I can call my methods before closing the session, no?

  • @Alanbessa The event I know is this one. Another alternative would be to schedule an event that checks out the expired sessions.

Show 2 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.