19

I'm using MySQL Connector/.NET, all its providers with FormsAuthentication.

I need all users to log out at some moment. The method FormsAuthentication.SignOut() does not work like I want.

How can I make a logout of all site users?

George Stocker
  • 57,289
  • 29
  • 176
  • 237
Jean Louis
  • 1,485
  • 8
  • 18
  • 33
  • 1
    Are your trying to bring the application off-line? Then look at using the 'app_offline.htm' file. http://weblogs.asp.net/scottgu/archive/2005/10/06/426755.aspx – rcravens Mar 30 '11 at 15:38
  • 1
    I am not sure if there is a simple one liner to accomplish this but what if you ignore all session cookies before a certain date and store that date in a database? – Joe Mar 30 '11 at 15:39
  • @rcravens: No, I want to reset auth of all users only. – Jean Louis Mar 30 '11 at 15:41
  • @Joe: Ok, it's good idea. But how can I set this cookie filter to ignore? I use FormsAuthentication. It works automatic. – Jean Louis Mar 30 '11 at 15:45
  • 1
    I have not had to write an ASP .net application in almost a year but there are override points in the authentication process where you can read the cookie and determine if it is valid. I have done this myself but it has been quite a while I am looking for a good example. – Joe Mar 30 '11 at 16:41

2 Answers2

19

As Joe suggests, you could write an HttpModule to invalidate any cookies present before a given DateTime. If you put this in the config file, you could add / remove it when necessary. For example,

Web.config:

<appSettings>
  <add key="forcedLogout" value="30-Mar-2011 5:00 pm" />
</appSettings>

<httpModules>
  <add name="LogoutModule" type="MyAssembly.Security.LogoutModule, MyAssembly"/>
</httpModules>

HttpModule in MyAssembly.dll:

public class LogoutModule: IHttpModule
{
    #region IHttpModule Members
    void IHttpModule.Dispose() { }
    void IHttpModule.Init(HttpApplication context)
    {
        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
    }
    #endregion


    /// <summary>
    /// Handle the authentication request and force logouts according to web.config
    /// </summary>
    /// <remarks>See "How To Implement IPrincipal" in MSDN</remarks>
    private void context_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpApplication a = (HttpApplication)sender;
        HttpContext context = a.Context;

        // Extract the forms authentication cookie
        string cookieName = FormsAuthentication.FormsCookieName;
        HttpCookie authCookie = context.Request.Cookies[cookieName];
        DateTime? logoutTime = ConfigurationManager.AppSettings["forcedLogout"] as DateTime?;
        if (authCookie != null && logoutTime != null && authCookie.Expires < logoutTime.Value)
        {
            // Delete the auth cookie and let them start over.
            authCookie.Expires = DateTime.Now.AddDays(-1);
            context.Response.Cookies.Add(authCookie);
            context.Response.Redirect(FormsAuthentication.LoginUrl);
            context.Response.End();
        }
    }
}
Brett
  • 8,575
  • 5
  • 38
  • 51
  • 3
    Bearing in mind that changes to web.config will cause an app restart, you should put the forcedLogout somewhere else. Possibly provide a site admin page for setting this. – ScottTx Mar 30 '11 at 21:14
  • Agreed. It's a convenient example. There are certainly better alternatives: store forcedLogout in the db with an admin UI; use a time window (forcedLogoutStart, forcedLogoutEnd) to avoid multiple web.config releases; drop a text file in app_data... – Brett Mar 31 '11 at 01:42
  • 1
    why authCookie.Expires will be less than logoutTime value ? because my website login may valid for next 30 days and in this kind of situation how to handle the situation? – Mou Apr 07 '16 at 19:09
  • @Mou I think you're trying to tell me there is a bug and I should be checking authcookie.Expires *greater than* logoutTime? – Brett Apr 08 '16 at 09:10
  • @Brett Basically authcookie.Expires date and time could be more or less than logoutTime ? so i read code and not sure does it work in any circumstances. please rectify the code here if possible. do not compare authcookie.Expires with logoutTime. – Mou Apr 08 '16 at 09:41
  • @Mou The comparison is the whole point. After logout time has passed, you want to invalidate the cookie. Otherwise, you don't want to touch the cookie. I guess you could check DateTime.Now, but it would have the same effect. I think you need to write another question and reference this one. – Brett Apr 08 '16 at 10:06
  • suppose i login to web site from 3 pc and so guess auth cookie will expire after 30 days. when user select logout from all device from pc1 and when try to acces the site from pc2 then authCookie.Expires will be never less than logoutTime. hope u understand what i am trying to say. please tell me how your code would work if authCookie.Expires after 30 days? – Mou Apr 08 '16 at 11:00
  • I don't understand the idea of this code. If the authCookie.Expires < logoutTime.Value, then when the logoutTime.Value comes, that authCookie's already expired, why do you need to invalid an expired cookie anyway? Remember that the inpersistent cookie has the expire date equal 1/1/1 which means it's the smallest datetime value, so no user can login with this code. – Dinh Tran May 22 '17 at 03:27
  • @ĐịnhTrần The point of this code is to force all users to hit the login page unless they have already visited the login page and have a new cookie. So, lets say you want users to logout today at 9:00 am. Then you should pick a forcedLogout time that is 30 days from today. Every user who hits the site will then have their cookie expired and they will be forced to login again. Those users who are forced to logout will then get a cookie whose expiration date is greater than 30 days from today and they will proceed into the application. I suppose I should compare logoutTime + 30 days. – Brett May 23 '17 at 12:41
  • im curious if this was actually tried and tested. after 2 hours of debugging, it seems the request should NOT have the expiration for the cookie. in order to pass the expiration you can store that in the value of the cookie. see: https://stackoverflow.com/questions/198295/asp-net-cookie-expiration-time-is-always-1-1-0001-1200-am – Heriberto Lugo Feb 04 '21 at 01:14
  • in addition, it would probably be easier to manage the force logout in the config, if the config used the date you want to force logout, instead of adding 30 days to it. adding FormsAuthentication.Timeout to the expire in context_AuthenticateRequest would be a clean approach. -- however this code is a great idea!! thanks for sharing! – Heriberto Lugo Feb 04 '21 at 01:17
1

Based off of @Bret's great answer. I handled some scenarios which allows the forced logout to occur after the user has logged in (on or after) the date specified in the config file (no need to calculate future expiration dates). I believe using his answer will result in user continuously being logged out if the cookies expiration is before the forced logout date. In addition, I am not sure how he was able to retrieve the cookie expiration using the cookie in the request. In my attempts I always received DateTime default value, as explained here: ASP.NET cookie expiration time is always 1/1/0001 12:00 AM

So the idea here is you would need to add the cookie creation DateTime to the principal (NOTE: the type should be nullable DateTime. This will allow handling if this change is made to a site already in use - as principal prior to this did not contain this extra field).

You setup the web.config as he did:

<configuration>
  <appSettings>
    <add key="forcedLogout" value="03-Feb-2021 10:46 pm" />
  </appSettings>
</configuration>

for adding the module to the web.config, you need to determine if you are using application pool integrated mode vs application pool classic mode:

  <system.webServer><!--for integrated mode-->
    <modules>
      <add name="ForceLogoutModule" type="AssemblyName.NameSpace.ForceLogoutModule", AssemblyName />
    </modules>
  </system.webServer>

  <system.web><!--for classic mode-->
     <httpModules>
      <add name="ForceLogoutModule" type="AssemblyName.NameSpace.ForceLogoutModule", AssemblyName />
    </httpModules>
  </system.web>

or you can add the module programmatically as so (in global.asax):

public static IHttpModule Module = new ForceLogoutModule();
public override void Init()
{
    base.Init();
    Module.Init(this);
}

Then your ForceLogout module (can be in same project):

public class ForceLogoutModule : IHttpModule
{
    #region IHttpModule Members
    void IHttpModule.Dispose() { }
    void IHttpModule.Init(HttpApplication context)
    {
        context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
    }
    #endregion


    /// <summary>
    /// Handle the authentication request and force logouts according to web.config
    /// </summary>
    /// <remarks>See "How To Implement IPrincipal" in MSDN</remarks>
    private void context_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpContext context = ((HttpApplication)sender as HttpApplication).Context;
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        string cookieName = FormsAuthentication.FormsCookieName;
        HttpCookie authCookie = context.Request.Cookies[cookieName];
        if (authCookie == null)
            return;
        FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
        CustomPrincipalSerializeModel principal = serializer.Deserialize<CustomPrincipalSerializeModel>(ticket.UserData);
        DateTime logoutTime;
        DateTime cookieCreation = ticket.IssueDate;

        if (!this.TryParseNullableToDate(ConfigurationManager.AppSettings["forcedLogout"], out logoutTime))
            return;


        if (!principal.Expiration.HasValue
            || (principal.Expiration.Value.ToLocalTime() > logoutTime && cookieCreation < logoutTime
            && DateTime.Now >= logoutTime.Date))
        {
            authCookie.Expires = DateTime.Now.AddDays(-1);
            context.Response.Cookies.Add(authCookie);
            context.Response.Redirect(FormsAuthentication.LoginUrl);
            context.Response.End();
        }
    }

    private bool TryParseNullableToDate(string value, out DateTime dateTime)
    {
        DateTime temp;
        if (string.IsNullOrWhiteSpace(value) || !DateTime.TryParse(value, out temp))
        {
            dateTime = DateTime.MinValue;
            return false;
        }

        dateTime = temp;
        return true;
    }
}
Heriberto Lugo
  • 589
  • 7
  • 19