Session and cookies

This topic is related to the OWIN version of DotVVM only. It is relevant only if the application needs to interact with ASP.NET session, or if you have some code that modifies cookies through System.Web.HttpContext.

Synchronization OWIN and ASP.NET cookies

OWIN offers its own extensible way of working with cookies. By default, the ChunkingCookieManager class is used.

However, when the application interacts with cookies using through System.Web.HttpContext (the classic ASP.NET way), a conflict occurs and the changes made by the ChunkingCookieManager will be lost.

DotVVM needs to store CSRF token in a cookie to provide a secure way of executing postbacks. When the browser makes the first request to a DotVVM web application, it stores the CSRF token in the cookie. If some code uses the ASP.NET session in the request, or the HttpContext.Current.Response.Cookies collection is changed, the changes to the cookie made from DotVVM are overwritten by the HttpContext, and thus the CSRF token will be lost. When a postback occurs, DotVVM will throw the following exception:

System.Security.SecurityException: SessionID cookie is missing, so can't verify CSRF token.

The preferred solution to this problem would be using a different way to store session-related data, or replace the default ChunkingCookieManager class by another implementation which will use HttpContext to interact with cookies.

Enabling session in OWIN

In order to use session in OWIN, the following code should be placed in Startup.cs before any middleware is registered. Otherwise, HttpContext.Current.Session would be null.

app.Use((context, next) =>
{
    var httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
    httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
    return next();
});
app.UseStageMarker(PipelineStage.MapHandler);

To prevent conflicts between OWIN and ASP.NET cookies, replace the default cookie manager with the following implementation:

public class SystemWebCookieManager : ICookieManager
{
    public string GetRequestCookie(IOwinContext context, string key)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
        var cookie = webContext.Request.Cookies[key];
        return cookie == null ? null : cookie.Value;
    }

    public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);

        bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
        bool pathHasValue = !string.IsNullOrEmpty(options.Path);
        bool expiresHasValue = options.Expires.HasValue;

        var cookie = new HttpCookie(key, value);
        if (domainHasValue)
        {
            cookie.Domain = options.Domain;
        }
        if (pathHasValue)
        {
            cookie.Path = options.Path;
        }
        if (expiresHasValue)
        {
            cookie.Expires = options.Expires.Value;
        }
        if (options.Secure)
        {
            cookie.Secure = true;
        }
        if (options.HttpOnly)
        {
            cookie.HttpOnly = true;
        }

        webContext.Response.AppendCookie(cookie);
    }

    public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        if (options == null)
        {
            throw new ArgumentNullException("options");
        }

        AppendResponseCookie(
            context,
            key,
            string.Empty,
            new CookieOptions
            {
                Path = options.Path,
                Domain = options.Domain,
                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
            });
    }
}

The SystemWebCookieManager can be registered in DotvvmStartup.cs using the ConfigureServices method:

public void ConfigureServices(IDotvvmServiceCollection services)
{
    ...
    services.Services.AddSingleton<ICookieManager, SystemWebCookieManager>();
}

See also