asp.net custom user authentication with master page - c#

I am working on a small Inventory management system. I have almost created it on my local machine. It works as it is designed. I just moved it to my hosting account and now I am facing some problems.
1, After login, when user goes to different pages, open and closes forms, after some time it redirects to login page. I don't know why. Seems like some sort of exception was occurred or the session had gone empty. How to handle this condition?
2, Is this the right way to keep a check on the user login using a check in Page_Load event of master page?
My site uses a master page that has separate layout for top navigation menu and bottom body area. When the first time user lands on the site it login into the system and upon successful login I store his information in session. I am heavily using Session in all pages for ADD, DELETE, UPDATE purposes. When an add record is performed, I pass success of failure message in session to show after post back. Code behind of login page is given below:
protected void loginForm_OnAuthenticate(object sender, AuthenticateEventArgs e)
{
string error = "";
sMethodName = "loginForm_OnAuthenticate";
_objLoginBLL = new LoginBLL();
int iRetVal = _objLoginBLL.ValidateUser(loginForm.UserName, loginForm.Password, ref error);
if (iRetVal >= 0)
{
Session.Clear(); //Remove all stored Session variables.
Session[Constant.Session.LOGGED_IN_DATETIME] = DateTime.Now.ToString("yyyyMMddHHmmssfff");
Session[Constant.Session.LOGIN_USERNAME] = loginForm.UserName;
Session[Constant.Session.LOGIN_USER_ID] = iRetVal;
Session[Constant.Session.LOGIN_COMPANY] = ddlCompanies.SelectedValue;
Session[Constant.Session.LOGIN_FISCAL_YEAR] = ddlFiscalYear.SelectedValue;
Session[Constant.Session.IS_DIRECT_ACCESS] = "NO";
FormsAuthentication.RedirectFromLoginPage(loginForm.UserName, loginForm.RememberMeSet);
}
else
{
Logger.Log("User validation failed.", sClassName, sMethodName, DEBUG);
switch (iRetVal)
{
case -1:
loginForm.FailureText = Constant.Messages.INCORRECT_USER_OR_PASSWORD;
loginForm.Focus();
break;
case -2:
loginForm.FailureText = Constant.Messages.ACCOUNT_LOCKED;
loginForm.Focus();
break;
//case -3:
//TODO: Account doesn't exists
default:
var randToken = new Random().Next(1000);
Session[Constant.Session.TOKEN] = randToken;
var myHashtable = new Hashtable
{
{Constant.Session.TOKEN, randToken},
{Constant.Fields.ERROR_KEY, iRetVal}
};
Response.Redirect(WebFunctions.CreateQueryString(Constant.Urls.Error, myHashtable));
break;
}
}
}
I am continuously checking if the session doesn't contain any user id then redirect it to the login page. The code behind of my master page is given below:
protected void Page_Load(object sender, EventArgs e)
{
if (Session[Constant.Session.LOGIN_USER_ID] == null)
{
FormsAuthentication.RedirectToLoginPage();
return;
}
CheckDBConnection();
Initialize();
}
Any help or tips will be appreciated.
You can view the site here: www.paracha.net (I can share guest account credentials in private message if anyone is interested)

First of all, keep in mind that the session cookie is not encrypted, so you should not be using the session to store any confidential information.
Secondly, you should not be checking the authentication on every Page_Load. Instead, you should configure the page access in web.config:
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
This will protect all your pages so only authenticated (i.e. logged in) users will see the page, while all others will be redirected to the login page.
If you have some pages (e.g. a splash page) or folders (e.g. the images folder) that you want them to be accessible to all users, then add a section for each page or folder:
<configuration>
<location path="splash.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
<location path="images">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
</configuration>
In order for this to work, you should be using forms authentication. Here are the settings in web.config:
<configuration>
<system.web>
<authentication mode="Forms">
<forms
name=".YOURNAME_AUTH"
loginUrl="login"
defaultUrl="/"
protection="All"
timeout="30"
path="/"
requireSSL="true"
slidingExpiration="true"
cookieless="UseCookies"
domain=""
enableCrossAppRedirects="false">
</forms>
</authentication>
</system.web>
</configuration>
Obviously, you will need a login.aspx page, and when you click the Log in button, you need to authenticate the user like this:
protected void btnLogIn_Click(object sender, EventArgs e) {
string Username = txtUsername.Text;
string Password = txtPassword.Text;
try {
if (ValidateUser(Username, Password)) {
FormsAuthentication.RedirectFromLoginPage(Username, false);
}
else {
lblMessage.Text = "Incorrect Credentials.";
lblMessage.ForeColor = Color.Red;
}
}
catch {
lblMessage.Text = "Login Failed.";
lblMessage.ForeColor = Color.Red;
}
}
The function ValidateUser() can do anything you want for authentication. You can validate the credentials against your database if you like.

If you use FormsAuthentication, you do not need to check Session[Constant.Session.LOGIN_USER_ID] manually. It will redirect to Login page automatically for which you can configure in web.config.
Another thought
It is not directly related to your question. It is just an alternative approach.
Instead of creating multiple session states, you can create custom Context to keep track of the current logged-in user's information
E.g. You can store Company and Fiscal Year properties inside MyUser class.
void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated)
{
MyContext.Current.MyUser =
YOUR_BLL.GetUserByUsername(HttpContext.Current.User.Identity.Name);
}
}
public class MyContext
{
private MyUser _myUser;
public static MyContext Current
{
get
{
if (HttpContext.Current.Items["MyContext"] == null)
{
MyContext context = new MyContext();
HttpContext.Current.Items.Add("MyContext", context);
return context;
}
return (MyContext) HttpContext.Current.Items["MyContext"];
}
}
public MyUser MyUser
{
get { return _myUser; }
set { _myUser = value; }
}
}
}
Addition
C# is strongly type language, so you should not encode the variable name with type of variable. E.g. objLoginBLL and iRetVal. Please read C# Design Guideline or Essential C# 6.0 (Page 7).

Related

session timeout in asp.net mvc when refresh

I am running an ASP.NET 4.0 application and published it using plesk control panel.I have done the following.
1.i set session timeout in web.config file as:
<sessionState timeout="20000"/>
I handled session variable in login controller like this:
Session["userId"] = lUser.userId;
Session["role"] = lUser.userType;
into other controller, the code is like below:
if (Session["role"] == null)
{
return RedirectToAction("Index", "Login");
}
else if (Session["role"].ToString() == "Admin" || Session["role"].ToString() == "Super Admin")
{
return View();
}
this code is ok in my local server but when published into real server using plesk control panel, it also ok for first time. but when i click the same menu second time it redirects to login page.
Try adding:
protected void Session_Start(Object sender, EventArgs e)
{
Session["init"] = 0;
}
to global.asax
Instead of SessionState mode="InProc" use SessionState Mode="StateServer", but you'll need to make sure the server where you're hsoting the application has the StateServer active.
<sessionState mode="StateServer" timeout="20000" cookieless="false" />

Redirecting URL Not Working

I wanted to add authorization before accessing all web pages. So I have used the following configuration in web.config
<authentication mode="Forms">
<forms loginUrl="~/Login/Login.aspx" />
</authentication>
<authorization>
<deny users="?"/>
</authorization>
After this for every page Login.aspx asked, But after logged in successfully Redirection is not working with below code.
//http://localhost:55217/Login/Login.aspx?ReturnUrl=%2fHome%2fdeleteUser.aspx
if (returnMsg == "Success") {
string query0 = Request.QueryString[0];
finalStr = "~" + query0;
Response.Redirect(finalStr, false);
//Session["Login"] = username;
//Response.Redirect("~/Home/Home.aspx");
//Response.Redirect("/Home/HomeTest.aspx");
} else {
StatusLabel.Attributes["style"] = "color:red; font-weight:bold;";
StatusLabel.Text = "Error: Username or Password Wrong";
}
It is staying on the Login page again asking for credentials. But not showing error "Error: Username or Password Wrong"
Any ideas why it is not working?
If you are using Forms authentication you need to create an authentication cookie if authentication is successful. Otherwise the ASP.NET subsystem will not know that the authentication was successful.
See this article:
https://support.microsoft.com/en-us/kb/301240
Here is the relevant text from this article:
4.You can use one of two methods to generate the forms authentication cookie and redirect the user to an appropriate page in the cmdLogin_ServerClick event. Sample code is provided for both scenarios. Use either of them according to your requirement.
•Call the RedirectFromLoginPage method to automatically generate the forms authentication cookie and redirect the user to an appropriate page in the cmdLogin_ServerClick event:
private void cmdLogin_ServerClick(object sender, System.EventArgs e)
{
if (ValidateUser(txtUserName.Value,txtUserPass.Value) )
FormsAuthentication.RedirectFromLoginPage(txtUserName.Value,
chkPersistCookie.Checked);
else
Response.Redirect("logon.aspx", true);
}
•Generate the authentication ticket, encrypt it, create a cookie, add it to the response, and redirect the user. This gives you more control in how you create the cookie. You can also include custom data along with the FormsAuthenticationTicket in this case.
private void cmdLogin_ServerClick(object sender, System.EventArgs e)
{
if (ValidateUser(txtUserName.Value,txtUserPass.Value) )
{
FormsAuthenticationTicket tkt;
string cookiestr;
HttpCookie ck;
tkt = new FormsAuthenticationTicket(1, txtUserName.Value, DateTime.Now,
DateTime.Now.AddMinutes(30), chkPersistCookie.Checked, "your custom data");
cookiestr = FormsAuthentication.Encrypt(tkt);
ck = new HttpCookie(FormsAuthentication.FormsCookieName, cookiestr);
if (chkPersistCookie.Checked)
ck.Expires=tkt.Expiration;
ck.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(ck);
string strRedirect;
strRedirect = Request["ReturnUrl"];
if (strRedirect==null)
strRedirect = "default.aspx";
Response.Redirect(strRedirect, true);
}
else
Response.Redirect("logon.aspx", true);
}

Basic login with one database table

I am currently creating a CMS and have a section where a user can create, edit and delete users. The information is generated from a database, where I have made a table with User_ID, User_Name and User_Password. This means I do not want to use the automatically generated database tables VS gives you for their log ins.
With this I am trying to develop a really basic log in but I am having trouble understanding the process.
This is my web.config for the whole application:
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add name="websiteContent" connectionString="uid=AAA;pwd=AAA;Initial Catalog=AAA;Data Source=.\SQLEXPRESS"/>
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.0"/>
<authentication mode="Forms">
<forms loginUrl="~/tools/default.aspx" timeout="2880"/>
</authentication>
</system.web>
</configuration>
Web.config for login:
<?xml version="1.0"?>
<configuration>
<location path="default.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
This is my log in on the front end:
<asp:Login ID="Login1" runat="server" CssClass="loginSec" TextLayout="TextOnTop"
TitleText="" OnAuthenticate="Login1_Authenticate">
<LabelStyle CssClass="lblLogin" />
<TextBoxStyle CssClass="txtLogin" />
</asp:Login>
Log in from the back end:
protected void Login1_Authenticate(object sender, AuthenticateEventArgs e)
{
string userName = Login1.UserName;
string passWord = Login1.Password;
bool rememberUserName = Login1.RememberMeSet;
using (SqlConnection sqlCon = new SqlConnection(ConfigurationManager.ConnectionStrings["websiteContent"].ConnectionString))
{
sqlCon.Open();
string SQL = "SELECT CMS_Username, CMS_Password FROM CMS_Users WHERE CMS_Username ='" + userName + "' AND CMS_Password ='" + passWord + "'";
using (SqlCommand sqlComm = new SqlCommand(SQL, sqlCon))
{
sqlComm.ExecuteScalar();
if (sqlComm.ExecuteScalar() != null)
{
Response.Redirect("cms.aspx");
}
else
{
Session["UserAuthentication"] = "";
}
}
sqlCon.Close();
}
}
What I have done so far has prevented access to the page cms.aspx, but the log in never redirects to the page.
Any insight would be appreciated!!
I have added the settings of Authenticated as required by the docs
Custom authentication schemes should set the Authenticated property to
true to indicate that a user has been authenticated.
More research has led me to the neccessity to add this line in your code
FormsAuthentication.SetAuthCookie(Login1.UserName, true);
Also I will try to change your code in such a way that ExecuteScalar returns the count of user with that username and password. In this way ExecuteScalar will never return NULL, but a value that could be zero if no user exists or 1 if user exists (I suppose that you don't have two records with the same user and password)
using (SqlConnection sqlCon = new SqlConnection(ConfigurationManager.ConnectionStrings["websiteContent"].ConnectionString))
{
sqlCon.Open();
string SQL = "SELECT COUNT(*) As LoginFound FROM CMS_Users " +
"WHERE CMS_Username =#usr AND CMS_Password = #pwd";
using (SqlCommand sqlComm = new SqlCommand(SQL, sqlCon))
{
sqlComm.Parameters.AddWithValue("#usr", userName);
sqlComm.Parameters.AddWithValue("#pwd", password);
int result = (int)sqlComm.ExecuteScalar();
if (result > 0)
{
// In case of success you need to communicate this
e.Authenticated = Authenticated;
FormsAuthentication.SetAuthCookie(Login1.UserName, true);
Response.Redirect("~/tools/cms.aspx");
}
else
{
Session["UserAuthentication"] = "";
}
}
}
Also, I have removed the string concatenation from your sql command. This is the right way to pass string text to the database. Particularly if the values comes from your user input.
(See Sql Injection )
EDIT
Of course the cmd.aspx page should check if the user has been authenticated because otherwise one could type directly the url of the cms.aspx page bypassing the login control.
So in the Page_Load event of cms.aspx add this code
protected void Page_Load(object sender, EventArgs e)
{
if ( !Request.IsAuthenticated)
{
Response.Redirect("~/tools/default.aspx");
}
}

How to secure a link to be downloaded only by specific users?

I have the following case , and i wanna to ask what's the best solution ?
I have a specific file i wanna specific users(according to some permissions) to download this file .
so i show this file only for the authorized users, but what if someone(not authorized) recognize the file link(knows the link url) and download it !!
How to allow this file to be downloaded only by the authorized users .
Put the file into a directory which is not served by the web server and implement a handler for the "virtual url" which in turn checks for permissions etc. - a possible way would be an ASHX handler (see here for sample code and here for MSDN reference).
My answer would be:
Dont use direct links!
Create a Download.aspx and have the links for downloads post to Download.aspx?params
The params should be encrypted/hashed containing the filepath+name to download and session_id.
On Download.aspx validate that the session_id is valid and active on the browser.
This should allow you to allow downloads to the correct folks only:
If you add to the params also the user_id or the user_type you can deny/permit download on the onLoad of Download.aspx
The following link provides details on Authorization Rules in iis and asp.net, it seems pertinent to your question.
Firstly you want to ensure ASP.NET handles request for your specified file type. You can configure this in IIS (see link below).
Secondly, you will then need to update your web.config to deny anonymous users from reaching your url, providing that you are using rolemanager :
<roleManager defaultProvider="SqlProvider" enabled="true" cacheRolesInCookie="false"
cookieName=".ASPROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false"
cookieSlidingExpiration="true" cookieProtection="All">
<providers>
<add name="SqlProvider" type="System.Web.Security.SqlRoleProvider"
connectionStringName="membership" applicationName="yourApplication"/>
</providers>
</roleManager>
<location path="path/file.extension">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
IIS 6 ignores Web.config authorization settings
The best way would be to add httphandlers and check whether the requested file have special permissions or not, an example for what I said would be:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public class MyHTTPHandler : IHttpHandler, IRequiresSessionState
{
string myFile;
public bool IsReusable {
get { return true; }
}
public void ProcessRequest(System.Web.HttpContext context)
{
myFile = context.Request.Path;
if (myFile.ToLower().Contains("members private files") || myFile.ToLower().Contains("members%20private%20files")) {
if (System.Web.HttpContext.Current.Session["Login"] == null) {
context.Response.Redirect("~/NotAuthorized.aspx");
} else {
if (myFile.ToLower().Contains("privatefiles")) {
StartDownload(context, myFile);
} else {
if (IsMemberAuthoraizedToDownloadFile(context)) {
StartDownload(context, myFile);
} else {
context.Response.Redirect("~/NotAuthorized.aspx");
}
}
}
} else {
StartDownload(context, myFile);
}
}
private void StartDownload(HttpContext context, string downloadFile)
{
context.Response.Buffer = true;
context.Response.Clear();
context.Response.AddHeader("content-disposition", "attachment; filename=" + downloadFile);
context.Response.ContentType = "application/pdf";
context.Response.WriteFile(downloadFile);
}
// just my own function to check if user is valid
private bool IsMemberAuthoraizedToDownloadFile(HttpContext context)
{
GroupMembersControl MyGroupMemberc = new GroupMembersControl();
System.Collections.Generic.List<GroupMembers> MemberGroupsL = MyGroupMemberc.GetMemberGroups(System.Web.HttpContext.Current.Session["Login"]);
MemberGroupControl MyGroupC = new MemberGroupControl();
MemberGroup MyGroup = default(MemberGroup);
foreach (GroupMembers groupmember in MemberGroupsL) {
MyGroup = MyGroupC.GetMemberGroup(groupmember.GroupID);
if (myFile.ToLower().Contains(MyGroup.Name.ToLower)) {
return true;
}
}
return false;
}
}

Membership.DeleteUser(UserName,true) not removing user from role

When I click the "delete" linkbutton, it can delete the all User info from my "UserDetail" table in my "JobPost.mdf", it also delete the corresponding "aspnet_Users" & "aspnet_Membership",but the "UserInRole" still contain that UserName. Even though I specified the Code:Membership.DeleteUser(UserName, true);
I thought true is for bool deleteallrelated data, but it doesn't really delete the userInRole. So next time the user registers with the same name, it automatically get the "admin" role right.
This "deleteUser" page I keep it inside a protected "admin"folder.
How to solve it? Why Membership.DeleteUser(UserName, true) doesn't delete UserInRole?
protected void GridView2_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Delete")
{
string UserName = e.CommandArgument.ToString();
Membership.DeleteUser(UserName, true);
JobPostDataContext db = new JobPostDataContext();
var query = from u in db.UserDetails
where u.UserName == UserName
select u;
foreach (var item in query)
{
db.UserDetails.DeleteOnSubmit(item);
}
db.SubmitChanges();
FormsAuthentication.SignOut();
}
}
My web.config inside the protected Admin folder:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.web>
<authorization>
<allow roles="Administrators" />
<deny users="*" />
</authorization>
</system.web>
</configuration>
The UserInRole table contains two Guid fields. The username is not stored. Whenever a new User is created, they are assigned a new, completely unique UserId.
Why do you think the user-role association is not being deleted? You can test this accurately by performing a query for the SELECT COUNT(*) FROM aspnet_UserInRoles WHERE UserId={DeletedUserId}.
I have included the ASP.NET membership SQL database schema below for your reference.
Also, the Roles API allows you to delete roles manually. So, to delete all the roles for a given user would looke like:
void DeleteUserRoles(string username)
{
foreach (var role in Roles.GetRolesForUser(username))
Roles.RemoveUserFromRole(username, role);
}

Categories

Resources