In my ASP.NET C# website I am attempting to use Sessions to allow users to log in and navigate throughout the secure pages while the session is valid. If for whatever reason they timeout or sign out, they are to be redirected to the landing page. Currently the site only allows one user to be logged in at a time. It seems apparent that the session information is being stored incorrectly, but I don't understand where or why this occurs. If you access the page using another browser, you can see the code pulling information out of the session (like the username) that it should not know.
I want to allow multiple valid users to be logged in simultaneously and have no adverse affect on each other while doing so. If you need further information than the code samples I post below, please ask.
My Login page:
//Login.ascx.cs
...
private void Login_Click(object sender, EventArgs e)
{
if (sender == null || e == null)
{
throw new ArgumentNullException("Null Exception: Login_Click");
}
User user = new User();
user.Login(_username.Text, _password.Text);
if (user.IsValid() && user.GetIsUser() != false)
{
user.Save();
Session["Username"] = _username.Text;
Response.Redirect("Secure/Default.aspx");
}
else
{
DisplayErrors(user._validationErrors);
}
_errors.Text = errorMessage;
}
The welcome page (first secure page a user sees)
private void Page_Load(object sender, System.EventArgs e)
{
Business.User user = new Business.User();
_labelUsername.Text = MySession.Current.Username;
_labelCompanyId.Text = MySession.Current.CompanyId;
redirectLogin = "../Default.aspx";
//redirect if any conditions fail for user validation.
if (sessionKey != MySession.GetSessionId() || (string)Session["Username"] != _labelUsername.Text)
{
Response.Redirect(redirectLogin);
}
else
{
Debug.WriteLine("Welcome SUCCESS: " + _labelUsername.Text);
Debug.WriteLine("Welcome " + sessionKey);
}
}
And finally the User page that includes SQL Query
public static string dataUsername;
public static string dataCompanyId;
private const string _getUserByUsernameQuery =
#"SELECT [User].[username], [Company].[name]
FROM [User] WITH (NOLOCK) INNER JOIN [Company] WITH (NOLOCK)
ON [User].[companyId] = [Company].[id]
WHERE [User].[username] = #username AND [User].[password] = #password";
private string _username;
private string _companyid;
public User(){}
public void Login (string username, string password)
{
using (SqlConnection connection = new SqlConnection(SQLConfiguration.ConnectionString))
{
SqlCommand command = new SqlCommand(_getUserByUsernameQuery, connection);
command.Parameters.AddWithValue("#username", username);
command.Parameters.AddWithValue("#password", password);
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
if (reader.Read())
{
Username = Convert.ToString(reader["username"]);
CompanyId = Convert.ToString(reader["name"]);
dataUsername = Username;
dataCompanyId = CompanyId;
}
}
}
}
#region Properties
public string Username
{
get{ return _username; }
set{ _username = value;}
}
public string CompanyId
{
get{ return _companyid;}
set{ _companyid = value;}
}
#endregion
EDIT: In response to some of the questions:
//in the first accessed page for secure users, before 'Page_load'
public static string sessionKey
{
get
{
return MySession.GetSessionId();
}
}
...
//in my 'MySession' class
public static string GetSessionId()
{
return System.Web.HttpContext.Current.Session.SessionID;
}
See Static Classes and Static Class Members (C# Programming Guide)
A static constructor is only called one time, and a static class remains in memory for the lifetime of the application domain in which your program resides.
The code-
public static string GetSessionId()
{
return System.Web.HttpContext.Current.Session.SessionID;
}
Will return the same SessionID for the lifetime of the application.
Like other posters I would strongly recommend you use the ASP.NET built in membership providers rather than try and invent your own, this will be more secure, more widely understood and thus supported, easier to maintain and more extendable.
Just use ASP.NET Forms Authentication. I bet all anonymous users are sharing the same session object. You can hook it into your own authentication scheme if something already exists. (passwords in a database or file, for instance)
Related
I have created a class UserInfo and I have set values on user login response, now I don't know how to access those values in another part of my app (another form).
Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MYCUSTOMAPP
{
class UserInfo
{
public string userId;
public string userType;
public string userName;
public string userUserName;
public string userPhone;
public string userIdCard;
}
}
Login (set values to class stings)
//....
SqlDataReader result = cmd.ExecuteReader();
if (result.HasRows)
{
while (result.Read())
{
UserInfo loggedUser = new UserInfo();
loggedUser.userId = result.GetValue(result.GetOrdinal("Id")).ToString();
loggedUser.userName = result.GetValue(result.GetOrdinal("Name")).ToString();
loggedUser.userType = result.GetValue(result.GetOrdinal("UserType")).ToString();
loggedUser.userUserName = result.GetValue(result.GetOrdinal("UserName")).ToString();
loggedUser.userPhone = result.GetValue(result.GetOrdinal("Phone")).ToString();
loggedUser.userIdCard = result.GetValue(result.GetOrdinal("IdCard")).ToString();
}
}
// the rest...
Question
Now let say I am in MainWindow form, how can I get value of userId for instance?
Update
Logic
User login
Store user data in class (or anything else that you might suggest)
Get those user data globally in my app so I don't need to call database each time I need user info (it will be presented already)
Update 2
My login function
using (SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["MYCUSTOMAPPDatabaseString"].ConnectionString))
{
if (cn.State == ConnectionState.Closed)
cn.Open();
using (DataTable dt = new DataTable())
{
using (SqlCommand cmd = new SqlCommand("dbo.LoginUser", cn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#UserName", usernameBox.Text);
cmd.Parameters.AddWithValue("#Password", passwordBox.Text);
SqlDataReader result = cmd.ExecuteReader();
if (result.HasRows)
{
while (result.Read())
{
// Name of logged user from database (this is database response)
Console.WriteLine(result.GetValue(result.GetOrdinal("Name")));
// Add logged user info to `UserInfo` class
UserInfo loggedUser = new UserInfo();
loggedUser.userId = result.GetValue(result.GetOrdinal("Id")).ToString();
loggedUser.userName = result.GetValue(result.GetOrdinal("Name")).ToString();
loggedUser.userType = result.GetValue(result.GetOrdinal("UserType")).ToString();
loggedUser.userUserName = result.GetValue(result.GetOrdinal("UserName")).ToString();
loggedUser.userPhone = result.GetValue(result.GetOrdinal("Phone")).ToString();
loggedUser.userIdCard = result.GetValue(result.GetOrdinal("IdCard")).ToString();
MainWindow mainWindow = new MainWindow();
this.Hide();
mainWindow.ShowDialog();
Show();
}
}
else
{
cn.Close();
MessageBox.Show("Your credentials are not match!", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
I think you can pass it as the parameters when you call another form's constructor, if you directly call that form. Like in this case : Communicate between two windows forms in C#
However, if you need to load it later on, you can either store it as user settings: https://www.youtube.com/watch?v=P432z8q9iVE. Or even more structural approach is store the data in database like MySQL, MSSQL, or another database that you can put when user click the call to action button.
Solved
I have changed my class to the following
class UserInfo
{
private string userId1;
private static string userType1;
private static string userName1;
private static string userUserName1;
private static string userPhone1;
private static string userIdCard1;
public string GetuserId()
{
return userId1;
}
public void SetuserId(string value)
{
userId1 = value;
}
public string GetuserType()
{
return userType1;
}
public void SetuserType(string value)
{
userType1 = value;
}
public string GetuserName()
{
return userName1;
}
public void SetuserName(string value)
{
userName1 = value;
}
public string GetuserUserName()
{
return userUserName1;
}
public void SetuserUserName(string value)
{
userUserName1 = value;
}
public string GetuserPhone()
{
return userPhone1;
}
public void SetuserPhone(string value)
{
userPhone1 = value;
}
public string GetuserIdCard()
{
return userIdCard1;
}
public void SetuserIdCard(string value)
{
userIdCard1 = value;
}
}
And changed my code in login response to:
while (result.Read())
{
UserInfo loggedUser = new UserInfo();
loggedUser.SetuserId(result.GetValue(result.GetOrdinal("Id")).ToString());
loggedUser.SetuserName(result.GetValue(result.GetOrdinal("Name")).ToString());
loggedUser.SetuserType(result.GetValue(result.GetOrdinal("UserType")).ToString());
loggedUser.SetuserUserName(result.GetValue(result.GetOrdinal("UserName")).ToString());
loggedUser.SetuserPhone(result.GetValue(result.GetOrdinal("Phone")).ToString());
loggedUser.SetuserIdCard(result.GetValue(result.GetOrdinal("IdCard")).ToString());
}
Now I can get my values in any form like:
UserInfo loggedUser = new UserInfo();
loggedUser.GetuserName(); // will return logged user name
How can i sign-out and redirect to the login page from a public static class?
I have tried the following but it does not stop page execution..
public static DatabaseNameEntities CreateEntitiesForSpecificDatabaseName(bool contextOwnsConnection = true)
{
string database_name = "";
try
{
database_name = System.Web.HttpContext.Current.Application["DB_NAME"].ToString();
}
catch (NullReferenceException)
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
//Initialize the SqlConnectionStringBuilder
//Initialize the EntityConnectionStringBuilder
//Create entity connection
EntityConnection connection = new EntityConnection(entityBuilder.ConnectionString);
return new DatabaseNameEntities(connection);
}
I have tried the following but it does not stop page execution..
That's because it's simply not the MVC way. It's also breaks the Single Responsibility Principle, that is, why would a method named CreateEntitiesForSpecificDatabaseName() know anything about MVC or logging out a user. The code you posted generally breaks this principle multiple times (application state, signing out a user).
Additionally, catching an exception you can prevent is also poor practice (or as the Lead Developer for the C# Compiler team called it, Boneheaded Exceptions).
Consider the following code.
public static ControllerBaseExtensions
{
private const string DBNAME = "DB_NAME";
public static bool TryGetDatabaseName(this ControllerBase instance,
out string DbName)
{
DbName = null;
var app = GetApp(instance);
var result = app.Any(k => k == DBNAME);
if (result)
{
DbName = instance.Application[DBNAME] as string;
result = DbName != null;
}
return result;
}
public static void SetDatabaseName(this ControllerBase instance,
string DbName)
{
var app = GetApp(instance);
app[DBNAME] = DbName;
}
private static HttpApplication GetApp(ControllerBase instance)
{
return instance.ControllerContext.HttpContext.Application;
}
}
public ActionResult MyMethod()
{
string DbName;
if (!this.TryGetDatabaseName(out DbName))
{
FormsAuthentication.SignOut();
// http://stackoverflow.com/questions/30509980
RedirectToAction("Login", "Account");
}
CreateEntitiesForSpecificDatabaseName(Dbname);
}
public static DatabaseNameEntities CreateEntitiesForSpecificDatabaseName(
string dbName,
bool contextOwnsConnection = true)
{
//Initialize the SqlConnectionStringBuilder
//Initialize the EntityConnectionStringBuilder
//Create entity connection
EntityConnection connection = new
EntityConnection(entityBuilder.ConnectionString);
return new DatabaseNameEntities(connection);
}
Does simply ie, no try or catch work as expected
public static DatabaseNameEntities CreateEntitiesForSpecificDatabaseName(bool contextOwnsConnection = true)
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
having difficulty with a bit of code I have. I think variables are stuck inside the method I'm invoking and when I go to run that method again variables are no longer there.
Below is an example of my main:
public partial class Login : Form
{
public Login()
{
InitializeComponent();
}
sqlconnect sql = new sqlconnect();
public string pass;
public string user;
public void button1_Click(object sender, EventArgs e)
{
//Username and password textboxes send to public strings
user = textBox1.Text;
pass = textBox2.Text;
try
{
//try connecting to SQL database using SQL class
sql.GetSqlConnection();
sql.myConnection.Open();
this.Hide();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Here is the SQL connection class:
namespace NHS_Workshop_System
{
public class sqlconnect
{
public SqlConnection myConnection { get; set; }
Settings1 set = new Settings1();
Login var = new Login();
public SqlConnection GetSqlConnection()
{
if (myConnection == null)
myConnection = new SqlConnection("user id="+(var.user)+"; password="+(var.pass)+";server="+(set.SelectServer)+";Trusted_Connection="+(set.SelectContype)+";database="+(set.SelectDataBase)+"; connection timeout="+(set.Condrop)+"");
return myConnection;
}
}
}
So when I try to login it may work first time but if another form tries to run that method the username and password are missing. I'm not sure how to make the variables global so that every time I run the method it calls those details without fail. So the second time it uses sqlconnection on another form the ex message states that username and password are non existent. Is this a scoping issue if so can someone shed some light on how i might go about this maybe a new method of gaining access to an SQL server? Thank you tearing out my hair
Below is the answer for your question the way you have it now. But I strongly recommend you to think about what you are doing since your class logics doesn't seem to be very good. I see that your trying to implement Singleton which is good, but you can still make more instances of sqlconnect if you would like.
In your Login class, pass this instance of Login so the sqlconnector knows the user and pass that is declared here.
public partial class Login : Form
{
// Old style - sqlconnect sql = new sqlconnect();
sqlconnect sql;
public string pass; // These arent declared and wont be of use for your sql connect class
public string user;
public Login( String pass, String user )
{
this.pass = pass;
this.user = user;
// Now that we have a login with declared variables, pass it to the sqlconnect object
this.sql = new sqlconnect(this);
InitializeComponent();
}
Now in your sqlconnect class, before your Login variable was just empty. Now you have received an instance of it in your constructor that has it's variables set,
public class sqlconnect
{
public SqlConnection myConnection { get; set; }
Settings1 set = new Settings1();
// Here you maked a new one, use the existing instead from which this class is called.
// changed variable named from var to login..
Login login;
// *NEW* Constructor where you pass your login object
public sqlconnect(Login login)
{
this.login = login;
}
public SqlConnection GetSqlConnection()
{
if (myConnection == null)
myConnection = new SqlConnection("user id="+(login.user)+"; password="+(login.pass)+";server="+(set.SelectServer)+";Trusted_Connection="+(set.SelectContype)+";database="+(set.SelectDataBase)+"; connection timeout="+(set.Condrop)+"");
return myConnection;
}
}
In the login page I am determining if the user is admin or not by the following code:
if (r.IsUserInRole(txtUserUsername.Text, "User") == true)
{
connection = ConfigurationManager.ConnectionStrings["GameHutDBEntities1"].ToString();
switch (new BusinessLayer.Users().ValidateLogin(txtUserUsername.Text, txtUserPassword.Text, connection))
{
case Helpers.LoginStatus.LoginSuccessful:
{
Response.Write("<Script> alert('Welcome to GameHut Admin Panel!')</Script>");
FormsAuthentication.RedirectFromLoginPage(txtUserUsername.Text, chkRemember.Checked);
break;
}
case Helpers.LoginStatus.Blocked:
{
Response.Write("<Script> alert('Account Blocked')</Script>");
break;
}
case Helpers.LoginStatus.Invalid:
{
Response.Write("<Script> alert('Invalid username or password')</Script>");
break;
}
}
}
In the connection class I am passing the connection string as follows:
public class ConnectionClass
{
public GameHutDBEntities Entities { get; set; }
public System.Data.IDbTransaction Transaction { get; set; }
public ConnectionClass()
{
this.Entities = new GameHutDBEntities();
}
public ConnectionClass(GameHutDBEntities _Entities)
{
this.Entities = _Entities;
}
public ConnectionClass(GameHutDBEntities _Entities, string conn)
{
this.Entities = _Entities;
this.Entities.Connection.ConnectionString = conn;
}
}
After the admin is logged and is redirect into another page the connection string is lost and the default connection string is set? Can someone tell me how to keep the connection string as long as the user logs out.
That's because you instantiate the class in the web form code behind and the instantiated object is lost when you switch to another page because of ASP.NET statelessness.
If you need to reuse the connection string save it in a session variable:
Session["conn"] = "connection string";
you will be able to reuse it wherever you want.
There are several other options like for example Cache, or a static property, or even read it from Configuration every time you want to use it. It really depends on the context of usage.
EDIT:
To read the connection string from configuration you can use the ConfigurationManager class from System.Configuration:
string conn = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
Keep the connection string name inside data-access layer and read it from Web.config every time you need (or cache it locally).
I have this databases: table<User>(UserID,Name,Surname,Username,Password,Email), table<Role>(RoleID,RoleName,Description), and table<UsersInRole>(UserID,RoleID). I create a login authentication with username and password to access to the application (with Linq ToSql to store data), and it is right.
Now I wish to create a role for each user, but I don't know how work out it; I saw some features about it but it refers to web.app.
This is the code of the procedure that applies to login:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
public bool ValidateApplicationUser(string userName, string password)
{
{
var AuthContext = new DataClasses1DataContext();
var query = from c in AuthContext.Users
where (c.Username == userName.ToLower() && c.Password == password.ToLower())
select c;
if(query.Count() != 0 )
{
return true;
}
return false;
}
}
private void mahhh(object sender, RoutedEventArgs e)
{
bool authenticated = true;
{
if (usernameTextBox.Text !="" && passwordTextBox.Text != "")
{
authenticated = ValidateApplicationUser(usernameTextBox.Text , passwordTextBox.Text);
}
}
if (!authenticated)
{
MessageBox.Show("Invalid login. Try again.");
}
else
{
MessageBox.Show("Congradulations! You're a valid user!");
Window3 c = new Window3();
c.ShowDialog();
this.Close();
}
}
}
I don't know how to implement a method to assign a role to the user.
Do you have any idea or suggest to make it right?
First, try not to store passwords in the database; it is better to store a hash. I'm not quite sure what you mean "assign a role to the user" - are you having difficulty getting the role from the db? Or are you unsure what to do with it afterwards? If the latter, the "principal" is the way to go; at the simplest level:
string username = ...
string[] roles = ...
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity(username), roles);
Now you can use role-based security, either declarative or imperative.
Declarative:
[PrincipalPermission(SecurityAction.Demand, Role="ADMIN")]
public void Foo()
{ // validated automatically by the .NET runtime ;-p
}
Imperative:
static bool IsInRole(string role)
{
IPrincipal principal = Thread.CurrentPrincipal;
return principal != null && principal.IsInRole(role);
}
...
bool isAdmin = IsInRole("ADMIN");