By default, .Net applications offer a prebuilt authentication system that you can use to give some privacy to your website in case its a small one. But, in case you pretend to use your existing DB with its existing users table and your existing conditions you may want to build your own authentication system.
Most of these can be built using a standard .Net format which Im going to show:
Web.config
Let’s start talking about the webconfig file, since as its name indicates, its the file were the site configuration resides and then the best place to configure the site authentication. You can of course build your own manual system just setting something at the master page or at any individual page, but you can save some time just learning to use the web config.
Web.config Authentication tag
First, let’s just say we can add four types of authentication to our website. One based in windows (so, in IIS and its OS users), another based in Microsoft Passport service and finally the one based in web forms. Normally, and as most of webpages in the net, you will use a typical web form based authentication (the one with the user/password boxes and the login button).
So, that’s what we add to webconfig under system.web tag:
<authentication mode="Forms"><forms loginUrl="~/login.aspx" timeout="2880"/></authentication>
Notice the “loginUrl” property at the forms tag, you will set on there the location of the login page in case the user tries to access a forbidden page. Pages that require authentication will redirect the user here automatically, no need to code anything.
Web.config Authorization
Now, we need to set the authorization level for the whole page. This means if we want to make it an intranet or a public website (with maybe some private areas).
For doing so, we’ll use the authentication tag under the system.web tag and set the level of authentication needed to request the website in general. We can use multiple options but most probably you will need to know only these ones:
<system.web> <compilation debug="true" targetFramework="4.0"></compilation> <authorization> <deny users="*"/> <!-- Denies permission to anyone, website closed. --> <deny users="?"/> <!-- Denies permission to non-authenticated users. Closed to anonymous users. You use this for an intranet. --> <allow users="*"/> <!-- Website open to everybody. You use this for a public website. --> </authorization> <authentication mode="Forms"><forms loginUrl="~/login.aspx" timeout="2880"/></authentication> <pages theme="Default"/> </system.web>
You have to add only one of these deny/allow tags, the one you think fits better.
Web.config locations
After doing that, you can add individual auth. rights for different folders or files, like login.aspx or scripts/ folder. In case your website is open to public most of its folders are too, but if you set it as an intranet have in mind that, if you don’t open to public some files and folders the ones that are not authenticated will not be able to authenticate due to forbidden access to login.aspx, and they could not see the styles or run the scripts if they have no access to the needed folders. So, you will need to add something like this under the configuration tag:
<configuration> <location path="login.aspx"><system.web><authorization><allow users="*"/></authorization></system.web></location> <location path="scripts"><system.web><authorization><allow users="?"/></authorization></system.web></location> <location path="imgs"><system.web><authorization><allow users="?"/></authorization></system.web></location> <location path="App_Themes"><system.web><authorization><allow users="?"/></authorization></system.web></location> </configuration>
If your site is open to public you may use something like this instead:
<configuration> <location path="admin"><system.web><authorization><deny users="?"/></authorization></system.web> <location path="privateArea"><system.web><authorization><deny users="?"/></authorization></system.web></location> </configuration>
And that’s all you need to know about webconfig. You can search more info in Google if you need or just continue forward.
User class
Now you should build a basic User class to manage your website users. I mean, if you are managing authorization and private areas in the website you sure have some way of storing those users data and, normally, that is done in a database using a Users table or similar. Here is a posible idea of how that basic User class could look like:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data; using System.Data.SqlClient; namespace MyNameSpace { public class User { //Basic public int ID { get; set; } public String Username { get; set; } public String PasswordInMD5 { get; set; } public MyNameSpace.PermissionRol Permissions { get; set; } //Details public String Email { get; set; } public String FirstName { get; set; } public String LastName { get; set; } public String Phone { get; set; } public User() { Initialize(); } public User(Boolean loadFromCookie) { Initialize(); if (loadFromCookie) { this.LoadUser(MyNameSpace.AuthCookie.GetUser()); //getting basic params from cookie this.LoadUser(getUser(Username, PasswordInMD5)); // getting details from db } } private void Initialize() { ID = 0; Username = ""; PasswordInMD5 = ""; Permissions = new MyNameSpace.PermissionRol(); FirstName = ""; LastName = ""; Email = ""; Phone = ""; } public static User getUser(String username, String pass) { List<SqlParameter> pl = new List<SqlParameter>(); pl.Add(new SqlParameter("userName", username)); pl.Add(new SqlParameter("MD5password", pass)); User u = new User(); IDataReader dr = MyNameSpace.db.getReader("sp_getUserDetails", pl); if (dr != null && dr.Read()) { u.ID = (int)dr["userId"]; u.Username = dr["userName"].ToString(); u.PasswordInMD5 = dr["userPassword"].ToString(); u.FirstName = dr["firstName"].ToString(); u.LastName = dr["lastName"].ToString(); u.Phone = dr["contactNumber"].ToString(); u.Email = dr["contactEmail"].ToString(); u.Permissions.Load(MyNameSpace.PermissionRol.getRol((int)dr["RoleId"])); } return u; } /// <summary> /// Loads user details from DB having its id. /// </summary> public void LoadUser(MyNameSpace.User newUser) { this.ID = newUser.ID; this.PasswordInMD5 = newUser.PasswordInMD5; this.Username = newUser.Username; this.FirstName = newUser.FirstName; this.LastName = newUser.LastName; this.Email = newUser.Email; this.Phone = newUser.Phone; } } }
Let’s not discuss its properties, its just an example. You can copypaste and adapt it to your needs. It also includes a reference to a PermissionRoles class where I have the different user rights and options (so, I do not only give access to a page, but also to do different actions or visualize different items). I give you an idea of how to implement it:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data; using System.Data.SqlClient; using System.Web.Caching; namespace MyNameSpace { public class PermissionRol { //Basic public int ID { get; set; } public String Name { get; set; } //Permissions public Boolean UserView; public Boolean UserAdd; public Boolean UserEdit; public Boolean UserRemove; public Boolean UnitView; public Boolean UnitAdd; public Boolean UnitEdit; public Boolean UnitRemove; public Boolean UnitAssignation; public Boolean AdminRights; public Boolean EnableEditMode; public PermissionRol() { ID = 0; Name = ""; UserView = false; UserAdd = false; UserEdit = false; UserRemove = false; UnitView = false; UnitAdd = false; UnitEdit = false; UnitRemove = false; UnitAssignation = false; EnableEditMode = false; AdminRights = false; } public void Load(PermissionRol newRol) { this.ID = newRol.ID; this.Name = newRol.Name; this.UserView = newRol.UserView; this.UserAdd = newRol.UserAdd; this.UserEdit = newRol.UserEdit; this.UserRemove = newRol.UserRemove; this.UnitView = newRol.UnitView; this.UnitAdd = newRol.UnitAdd; this.UnitEdit = newRol.UnitRemove; this.UnitRemove = newRol.UnitRemove; this.UnitAssignation = newRol.UnitAssignation; this.EnableEditMode = newRol.EnableEditMode; this.AdminRights = newRol.AdminRights; } public static List<PermissionRol> getRoles() { // Try to get from cache: List<PermissionRol> lstRoles = (List<PermissionRol>)HttpRuntime.Cache.Get(MyNameSpace.Config.CACHE_PERMISSIONROLES); if (lstRoles == null) { lstRoles = new List<PermissionRol>(); IDataReader dr = MyNameSpace.db.getReader("sp_getRoles"); if (dr != null) { while (dr.Read()) { PermissionRol rol = new PermissionRol(); rol.ID = (int)dr["ID"]; rol.Name = dr["Name"].ToString(); rol.EnableEditMode = (Boolean)dr["EnableEditMode"]; rol.UnitAdd = (Boolean)dr["UnitAdd"]; rol.UnitAssignation = (Boolean)dr["UnitAssignation"]; rol.UnitEdit = (Boolean)dr["UnitEdit"]; rol.UnitRemove = (Boolean)dr["UnitRemove"]; rol.UnitView = (Boolean)dr["UnitView"]; rol.UserAdd = (Boolean)dr["UserAdd"]; rol.UserEdit = (Boolean)dr["UserEdit"]; rol.UserRemove = (Boolean)dr["UserRemove"]; rol.UserView = (Boolean)dr["UserView"]; rol.AdminRights = (Boolean)dr["AdminRights"]; lstRoles.Add(rol); } } //Add to cache HttpRuntime.Cache.Remove(MyNameSpace.Config.CACHE_PERMISSIONROLES); HttpRuntime.Cache.Add(MyNameSpace.Config.CACHE_PERMISSIONROLES, lstRoles, null, DateTime.Now.AddMinutes(15), Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.High, null); } return lstRoles; } public static PermissionRol getRol(int pID) { IEnumerable<PermissionRol> rol = from roles in getRoles() where roles.ID == pID select roles; return rol.ToList<PermissionRol>()[0]; } } }
Authentication Cookie
Now its time to get to next stage and build our authentication cookie. This is a special type of cookie that .Net uses to authenticate the user and make sure this user is authenticated. Which will be the difference between an anonymous user and a known one. Be care with this then since not any cookie will do. You may create your own encrypted cookie and use to it authenticate but .Net will still not recognize that user as authenticated and due to your webconfig confs the user will have no access.
So, let’c build our own Authentication cookie adapted to our needs in our own class:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; namespace MyNameSpace { public class AuthCookie { public AuthCookie() { // // TODO: Add constructor logic here // } public static MyNameSpace.User GetUser() { MyNameSpace.User u = new MyNameSpace.User(); HttpCookie au = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName); if (au != null) { FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(au.Value); if (!ticket.Expired) { String data = ticket.UserData; u.Username = data.Split('|')[0]; u.PasswordInMD5 = data.Split('|')[1]; } else { LogOut(); } } return u; } public static void LogOut() { FormsAuthentication.SignOut(); HttpContext.Current.Session.Abandon(); HttpContext.Current.Response.Redirect("~/login.aspx"); } /// <summary> /// Creates the authentication cookie. /// </summary> public static void Save(MyNameSpace.User u) { Save(u, false); } /// <summary> /// Creates the authentication cookie. /// </summary> /// <param name="u">User</param> /// <param name="isPersistent">Default: false.</param> public static void Save(MyNameSpace.User u, Boolean isPersistent) { DateTime expirationDate = DateTime.MaxValue; if (isPersistent) { expirationDate = DateTime.Now.AddMonths(3); } String userData = u.Username + "|" + u.PasswordInMD5; FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, "CSC", DateTime.Now, expirationDate, isPersistent, userData, FormsAuthentication.FormsCookiePath); // Encrypt the ticket. string encTicket = FormsAuthentication.Encrypt(ticket); HttpContext.Current.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)); } } }
This one is pretty simple, it has a method to Save the Cookie sending a User object (which we did before) and another to get it. Also, we can use it to LogOut so all the Login/Logout things are kept here. What I save in the Cookie is the username and password in MD5, I decided to go through this type of authentication for this test project, but of course you can use different ways of doing it (in fact, I tend to use different combinations).
Login Page
Finally, we have to set the code for the Login Page. Whatever the layout you use you will most probably end up using some user/pass combination. So this is it:
public partial class Login : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { lbErrorMessage.Text = ""; } protected void Page_PreRender(object sender, EventArgs e) { pnlErrorMessage.Visible = (lbErrorMessage.Text.Length > 0); } protected void BTNLogin_Click(object sender, EventArgs e) { MyNameSpace.User u = MyNameSpace.User.getUser(TXTUser.Text, getInMD5(TXTPass.Text)); if (u.ID > 0) { MyNameSpace.AuthCookie.Save(u); Redirect(); } else { lbErrorMessage.Text = "Invalid Username or Password."; } } private void Redirect() { if (Request.QueryString["ReturnUrl"] != null && Request.QueryString["ReturnUrl"].ToString().Length > 2) { Response.Redirect(Request.QueryString["ReturnUrl"]); } else { Response.Redirect("~/default.aspx"); } } public static String getInMD5(String inputString) { byte[] input = Encoding.UTF8.GetBytes(inputString); byte[] output = MD5.Create().ComputeHash(input); StringBuilder sb = new StringBuilder(output.Length); for (int i = 0; i < output.Length; i++) { sb.Append(output[i].ToString("X2")); } return sb.ToString(); } }
Accesing the User from any Page
We can then create this User object at our Master Page so that it loads on any request (in case we need this to be done) and access it from our pages. If you don’t know how to do this, you can check how to access a MasterPage object from any page with more detail.
Master Page Example:
public MyNameSpace.User User; protected void Page_Init(object sender, EventArgs e) { User = new MyNameSpace.User(true); } protected void Page_Load(object sender, EventArgs e) { lbWelcomeMsg.Text = "You are logged in as " + User.Username; }
Default page example:
public partial class Default1 : System.Web.UI.Page { MyNameSpace.User User; protected void Page_Load(object sender, EventArgs e) { User = ((MyNameSpace.MyMasterPage)Page.Master).User; lbUserName.Text = User.FirstName; } }