Computers And Industry

Session Variables in AspNetCore


This post provides a generic class that allows easy implementation and usage of Session Variables in an AspNetCore web application. Session Variables in AspNetCore web applications are somewhat cryptic when compared to other programming languages like PHP. This class attempts to alleviate the complexities by providing a very simple object that manages a website user's session meanwhile allowing the programmer to concentrate on the data model representing what he or she wants to store on the server side.

Due to the complexity of this topic, there is a sample project at http://github.com/sirpenski/XSession for reference.

To use this class:

1. Define a class that will hold all your session data. It can consist of any properties, lists, etc. The constraint is that the class is serializable and that it has a constructor. For example.

[Serializable]
public class UserSessionVariables 
{
    public bool IsAuthorized {get; set;} = false;
    public string UserID {get; set;} = "";
    public string UserIPAddress {get; set;} = "";
    public int UserPort {get; set; } = 0;

    public UserSessionVariables() {}


}


2. In each controller, include the namespaces:


using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Http;



3. In each controller, define a controller level variable to hold the session data.

PFSXSession session;


4. In the OnActionExecuting function, instantiate a new instance, load it, set, and test session properties as necessary.

public override void OnActionExecuting(ActionExecutingContext context)
{

    string action = this.ControllerContext.ActionDescriptor.ActionName.ToUpper();

    // Define new session object and pass in the context in the constructor
    session = new PFSXSession(context.HttpContext);


    // load the user session.
    usr.Load(AutoInitialize: true);

    // generic login, login submit, and logout filtering
    if (string.Compare(action, "LOGIN") != 0 && string.Compare(action, "LOGINSUBMIT") != 0 &&
        string.Compare(action, "LOGOUT") != 0)
    { 
        // if the session is expired or the session is not authenticated, then redirect 
        // to login page.
        if (session.IsExpired || !session.SessionVariables.IsAuthenticated)
        {                 
            context.Result = new RedirectResult(APP.LOGIN_URL);
        }

    }

    base.OnActionExecuting(context);
}


5. In startup.cs, in the ConfigureServices method, add the following:


  services.AddDistributedMemoryCache();

  services.AddSession(options => {
      options.IdleTimeout = TimeSpan.FromMinutes(30);   
  });


6. In startup.cs, in the Configure method, add the following before the UseMvc() statement.


app.UseSession();



7. Using the class and referencing the session variables in code

public IActionResult LoginSubmit(string uid, string pwd)
{
    IActionResult rslt = new RedirectResult("/Admin/Login");
    if (string.Compare(uid, USER_ID) == 0) && string.Compare(pwd, PASSWORD) == 0)
    {

        session.SessionVariables.IsAuthenticated = true;
        session.UserID = uid;
        session.Save();
        rslt = new RedirectResult("/Admin/Index");
    }

    return rslt;
}


XSession Class Source Code

Here is the code for the class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.AspNetCore.Http;


namespace XSession.Models
{
    
    public class PFSXSession:IDisposable where T: new()
    {

        private const string  DEFAULT_SESSION_VARIABLE_NAME = "USR";

        private const int DEFAULT_SESSION_EXPIRATION_INCREMENT_HOURS = 0;

        private const int DEFAULT_SESSION_EXPIRATION_INCREMENT_MINUTES = 20;

        private const int DEFAULT_SESSION_EXPIRATION_INCREMENT_SECONDS = 0;


        // this is the xsession data. this is a combination of the user session variables T and 
        // the internal session variables of the class.
        private XSessionData xSessionData = null;


        // creates a new hash generator
        private XHash256Generator xHash = null;

        // random number generator
        private Random xRND = null;

        // private session corrupt flag
        private bool boolSessionIsCorrupt = false;


        /// Ctx is the current http context.  It must be set prior to 
        /// loading or saving 
        public HttpContext Context { get; set; } = null;


        // name of the session variable
        public string Name { get; set; } = DEFAULT_SESSION_VARIABLE_NAME;


        /// Gives access to the serializable session variables
        public T SessionVariables
        {
            get { return xSessionData.SessionVariables; }
            set { xSessionData.SessionVariables = value; }
        }


        /// Determines if the session is initialized
        public bool SessionInitialized
        {
            get { return xSessionData.SessionInitialized; }
            set { xSessionData.SessionInitialized = value; }
        }

        /// Session Start Time
        public DateTime SessionStart
        {
            get { return xSessionData.SessionStart; }
            set { xSessionData.SessionStart = value; }
        }


        /// Session  Expiration Time
        public DateTime SessionExpires
        {
            get { return xSessionData.SessionExpires; }
            set { xSessionData.SessionExpires = value; }
        }

        public DateTime SessionCurrentTime
        {
            get { return xSessionData.SessionCurrentTime; }
            set { xSessionData.SessionCurrentTime = value; }
        }

        /// The amount of time the session expiration date is incremented each call to extend
        public TimeSpan SessionExpirationIncrement { get; set; }


        /// This is a unique session id generated in initialize
        public byte[] SessionID
        {
            get { return xSessionData.SessionID; }
            set { xSessionData.SessionID = value; }
        }
  

        /// Gets the session id as a hexadecimal string
        public string SessionIDHex
        {
            get { return ByteArrayToHex(xSessionData.SessionID); }
        }


        /// Session Hash Value
        public byte[] SessionHashValue
        {
            get { return xSessionData.SessionHashValue; }
            set { xSessionData.SessionHashValue = value; }
        }

        /// Session Hash Value as a Hexadecimal string
        public string SessionHashValueHex
        {
            get { return ByteArrayToHex(SessionHashValue); }
        }


        /// Session Random number generator.  This might be used instead of using the 
        /// internal one.
        public Random SessionRandomNumberGenerator
        {
            get { return xRND; }
            set { xRND = value; }
        }


        /// Is Session Expired
        public bool IsExpired
        {
            get {
                    bool rt = false;
                    if (DateTime.Now > xSessionData.SessionExpires)
                    {
                        rt = true;
                    }
                    return rt;
                }
        }

        /// This flag is set if the session is corrupt, ie the hash value is not correct.
        public bool IsCorrupt
        {
            get { return boolSessionIsCorrupt; }
        }



        /// Session is error
        public bool IsError
        {
            get { return IsExpired || IsCorrupt; }
        }


        /// Read only variant of SessionInitialized
        public bool IsInitialized
        {
            get { return SessionInitialized; }
        }


        // --------------------------------------------------------------------------
        // BEGIN CONSTRUCTORS
        // --------------------------------------------------------------------------

        /// Base Constructor
        public PFSXSession()
        {
            ConstructorCommon();
        }

        /// Constructor that accepts httpcontext
        public PFSXSession(HttpContext ctx)
        {
            Context = ctx;
            ConstructorCommon();
        }


        /// Constructor
        public PFSXSession(string SessionVariableName, HttpContext ctx)
        {
            Context = ctx;
            Name = SessionVariableName;
            ConstructorCommon();
        }

        // constructor
        public PFSXSession(HttpContext ctx, Random xr)
        {
            Context = ctx;
            xRND = xr;
            ConstructorCommon();
        }


        // constructor
        public PFSXSession (string SessionVariableName, HttpContext ctx, Random xr )
        {
            Name = SessionVariableName;
            Context = ctx;
            xRND = xr;
        }


        /// Common Constructor Code
        private void ConstructorCommon()
        {

            bool b = Attribute.IsDefined(typeof(T), typeof(SerializableAttribute));
            if (!b)
            {
                throw new Exception("Object Is Not Serializable");
            }

            SessionExpirationIncrement = new TimeSpan(DEFAULT_SESSION_EXPIRATION_INCREMENT_HOURS,
                                                      DEFAULT_SESSION_EXPIRATION_INCREMENT_MINUTES,
                                                      DEFAULT_SESSION_EXPIRATION_INCREMENT_SECONDS);

            xSessionData = new XSessionData();
            xSessionData.SessionExpirationIncrement = SessionExpirationIncrement;


            xHash = new XHash256Generator();

        }

        // --------------------------------------------------------------------------
        /// Increments the Session Expiration Time
        // --------------------------------------------------------------------------
        public void IncrementSessionExpiration()
        {

            DateTime cNow = DateTime.Now;

            xSessionData.SessionCurrentTime = cNow;

            xSessionData.SessionExpires = cNow.Add(xSessionData.SessionExpirationIncrement);

        }


        // --------------------------------------------------------------------------
        /// Initializes the session
        // --------------------------------------------------------------------------
        public void Initialize()
        {

            // define new session data object
            xSessionData = new XSessionData();


            // set the session start time
            xSessionData.SessionStart = DateTime.Now;

            // create a unique session id
            xSessionData.SessionID = CreateNewSessionID();

            // create a new user variable
            xSessionData.SessionVariables = new T();


            // set the session expiration increment
            xSessionData.SessionExpirationIncrement = SessionExpirationIncrement;


            // increment the session expiration date time
            IncrementSessionExpiration();

            // init the session
            SessionInitialized = true;
            
            Save();

            Debug.WriteLine("**EXIT INITIALIZE SESSION");
        }


        // --------------------------------------------------------------------------
        /// Converts the byte array to a hex string of the form aaaa:bbbb:cccc
        // --------------------------------------------------------------------------
        public string ByteArrayToHex(byte[] b, string default_sep = "")
        {
            string rt = "";
            string sep = "";
            for (int i = 0; i < b.Length; i++)
            {
                rt += sep + b[i].ToString("X2");
                if ((i % 2) == 1)
                {
                    sep = default_sep;
                }
                else
                {
                    sep = "";
                }
               
            }
            return rt;
        }


        // --------------------------------------------------------------------------
        /// ObjectToByteArray serializes an object to a byte array.  
        /// This can then be saved to a session variable
        // --------------------------------------------------------------------------
        private byte[] ObjectToByteArray(Object obj)
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (var ms = new MemoryStream())
            {
                bf.Serialize(ms, obj);
                return ms.ToArray();
            }
        }


        // --------------------------------------------------------------------------
        /// ByteArrayToObject unserializes an object
        // --------------------------------------------------------------------------
        private object ByteArrayToObject(byte[] arrBytes)
        {
            using (var memStream = new MemoryStream())
            {
                var binForm = new BinaryFormatter();
                memStream.Write(arrBytes, 0, arrBytes.Length);
                memStream.Seek(0, SeekOrigin.Begin);
                Object obj = (Object) binForm.Deserialize(memStream);
                return obj;
            }
        }

        // --------------------------------------------------------------------------
        /// Load loads the session variables from the HttpContext.Session object, 
        /// unserializes it and makes it available.
        // --------------------------------------------------------------------------
        public void Load(bool AutoInitialize = false)
        {

            byte[] b = null;
            
            Debug.WriteLine("");
            Debug.WriteLine("**BEGIN SESSION LOAD");

            // try and get the session variable from the http context

            try
            {
                b = Context.Session.Get(Name);

            }
            catch (Exception) 
            {
                b = null;
            }


            // if found, then load the session data.
            if (b != null)
            {

                // load the data from the byte array
                xSessionData = (XSessionData) ByteArrayToObject(b);

                // now we want to verify the data is valid, ie it hasn't been tampered with
                byte[] CurrentHashValue = ComputeHash(xSessionData);

                // determine if the two byte arrays are equal.
                if (CurrentHashValue.SequenceEqual(xSessionData.SessionHashValue))
                {

                    // check if session expired.  If not, then 
                    // increment expiration time.
                    if (!IsExpired)
                    {
                    
                        // increment the expiration data
                        IncrementSessionExpiration();

                        // Saving Session
                        Save();

                    }

                }
  
              
                // hashes do not match, set session corrup value
                else
                {

                    boolSessionIsCorrupt = true;
                }
            }


            // b was null, session not found, initialize
            else
            {
                
                // If session not found, create a new and initialize
                if (AutoInitialize)
                {
                    Initialize();
                }
            }

        }


  
        // --------------------------------------------------------------------------
        /// Saves the current session variables to the HttpContext.Session object
        // --------------------------------------------------------------------------
        public void Save()
        {
            // if the HttpContext is not null then proceed
            if (Context != null)
            {
               
                // compute a SHA256 hash of the data
                xSessionData.SessionHashValue = ComputeHash(xSessionData);

                // convert the x_session_data to an byte array
                byte[] b = ObjectToByteArray(xSessionData);

                // set the session variable to the byte array
                Context.Session.Set(Name, b);
            }
        }


        // --------------------------------------------------------------------------
        /// Destroys the Session
        // --------------------------------------------------------------------------
        public void Kill()
        {
            if (Context != null)
            {
                if (Context.Session != null)
                {
                    try
                    {
                        Context.Session.Remove(Name);
                        Dispose();
                    }
                    catch (Exception) { }
                }
            }
        }



        // ----------------------------------------------------------
        // destroys session variables and reinitializes system
        // ----------------------------------------------------------
        public void Reset()
        {

            // save the session increment
            TimeSpan tmpSessionIncrement = SessionExpirationIncrement;

            // reinstantiate system.
            ConstructorCommon();

            // now rename the session incremet
            SessionExpirationIncrement = tmpSessionIncrement;




            // reinitialize system.  note that save is called in 
            // initialize so we don't have to call it again
            Initialize();
        }



        // --------------------------------------------------------------------------
        /// Creates a new 32 byte unique session id.  Note, this uses a random number generator.  
        /// The best idea is to use one that is active in the main application.  However, if one 
        /// isn't present, we need to create one.
        // --------------------------------------------------------------------------
        private byte[] CreateNewSessionID()
        {
            byte[] rt = new byte[32];

            // if the random number generator is null, then we need to create one.  
            // we try and compute a unique seed so that other sessions that may be occuring 
            // simultaneously do not get an identical number generator. 
            if (xRND == null)
            {

                // what we are trying to do here is compute a random number between 
                // o and 
                xRND = new Random();

                UInt32 a = (UInt32)xRND.Next();

                // get the lowest byte.
                a = (a & 0xFF);


                // get the next random integer
                UInt32 b = (UInt32)xRND.Next();

                // recompute a byte value
                b = (b & 0xFF000000) >> 24;

                // compute the seed
                int c = (int)Math.Abs(a ^ b);

                xRND = new Random(c);


            }

            // now we loop 32 bytes
            for (int i = 0; i < 32; i++)
            {
                // get a random integer and cast to UInt32
                UInt32 RandomUInt = (UInt32) xRND.Next();
                rt[i] = (byte)(RandomUInt & 0x000000FF);
            }

            return rt;


        }



        // --------------------------------------------------------------------------
        /// Computes the session hash of the current state of data
        // --------------------------------------------------------------------------
        public byte[] ComputeHash()
        {
            return ComputeHash(xSessionData);
        }


        // --------------------------------------------------------------------------
        /// Computes the hash of the session variables
        // --------------------------------------------------------------------------
        private byte[] ComputeHash(XSessionData xsCurrent)
        {
            // create a new xession data
            XSessionData xsNew = new XSessionData();

            // copy over the session variables. Note, we won't be 
            // copying over the session hash value.  we are going to hash a 
            // byte[0] for that value.  Otherwise, we'd be recursively hashing.
            xsNew.SessionExpirationIncrement = xsCurrent.SessionExpirationIncrement;
            xsNew.SessionExpires = xsCurrent.SessionExpires;
            xsNew.SessionCurrentTime = xsCurrent.SessionCurrentTime;
            xsNew.SessionInitialized = xsCurrent.SessionInitialized;
            xsNew.SessionStart = xsCurrent.SessionStart;

            // these are the user session variables, we are going to 
            // set a reference only, no deep copy
            xsNew.SessionVariables = xsCurrent.SessionVariables;


            // now, create a byte array by serializing the xsnew object
            byte[] byteArrayToHash = ObjectToByteArray(xsNew);

            // now, get the sha256 hash of this value.
            byte[] hashValue = xHash.Create(byteArrayToHash);

            // return the hash value
            return hashValue;
        }


        // --------------------------------------------------------------------------
        /// Destroys the session.  Called By Logout
        // --------------------------------------------------------------------------
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // --------------------------------------------------------------------------
        /// Dispose method
        // --------------------------------------------------------------------------
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (Context != null)
                {
                    try
                    {
                        SessionVariables = default(T);
                        Context = null;
                        xSessionData = null;
                        xHash.Dispose();
                    }
                    catch (Exception) { }
                }
            }
        }


        // *************************************************************************
        /// XSessionData is a class that contains both the data for the session and 
        /// the user session variables
        // *************************************************************************
        [Serializable]
        private class XSessionData
        {

            // unique session id
            public byte[] SessionID { get; set; } = new byte[32];

            // session initializion flag
            public bool SessionInitialized { get; set; } = false;

            // session start
            public DateTime SessionStart { get; set; } = DateTime.MinValue;

            // session expires
            public DateTime SessionExpires { get; set; } = DateTime.MaxValue;


            // session Current Time 
            public DateTime SessionCurrentTime { get; set; } = DateTime.MinValue;

            // session expiration increment.
            public TimeSpan SessionExpirationIncrement { get; set; } = new TimeSpan(0, 20, 0);

            // session data hash value.  
            public byte[] SessionHashValue { get; set; } = new byte[0];

            // set a new session varialbe
            public T SessionVariables { get; set; } = new T();

        }



        // **************************************************
        /// A Private Hash Generator.
        // **************************************************
        private class XHash256Generator: IDisposable
        {
            private SHA256 algorithm = null;


            public XHash256Generator()
            {
                algorithm = SHA256.Create();
            }


            public byte[] Create(byte[] b)
            {
                return algorithm.ComputeHash(b);
            }

            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            /// Dispose method
            protected virtual void Dispose(bool disposing)
            {
                if (disposing)
                {
                    try
                    {
                        algorithm = null;   
                    }
                    catch (Exception) { }
                }
            }


        }   // End class XHash256Generator

    }  // End PFSXSession class

}   // End namespace

Open Source

Paul F. Sirpenski
Personal Open Source Directory Of Paul F. Sirpenski

ASP.NET Core
Open Source directory Of the Microsoft Asp.Net Core project.

Developed By Paul F. Sirpenski. Copyright 2021.