I will see if I can explain this clearly enough. I have 2 web forms. One is a basic Forms Authentication login page and the other form displays tasks from multiple servers. I am creating a cookie that stores the UserID. Here is the code for my cookie:
FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(1, txtUser.Text, DateTime.Now, DateTime.Now.AddMinutes(120), true, rdr.GetInt32(0).ToString(), FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(tkt);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
On my other form, I have a drop down box that displays all servers by Server IP from the Servers table.
public void Populate()
{
SqlConnection myConnection1 = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString);
myConnection1.Open();
SqlCommand cmd1 = new SqlCommand("SELECT ServerIP FROM Servers", myConnection1);
SqlDataReader dropReader;
dropReader = cmd1.ExecuteReader();
drpChoose.DataSource = dropReader;
drpChoose.DataTextField = "ServerIP";
drpChoose.DataValueField = "ServerIP";
drpChoose.DataBind();
}
I am calling Populate in Page Load. I have another table that stores permissions. It has UserID, ServerID, and Permission (read or execute). Let's say that UserID 1 is associated with only ServerID 1 which has an IP of 192.168.0.10. How can I get this one Server IP to display in the drop down? I am pretty sure if I pass the cookie into the second form that I can take the UserID from that but I do not know where to begin.
I apologize if I have not given enough information. I will provide more if need be.
Looks like you'll need to do a join to your permissions table something like
SELECT ServerIP from Servers s, Permissions p where p.serverid = s.serverid and p.userid = :userIdFromCookie
Then you'll need to pass in the user id from your cookie into the Populate method and use a DbParameter to pass the value into your Sql command.
something like (this is pseudocode by the way as I'm not at my dev machine)
cmd.AddInParameter(":userIdFromCookie",dbType.AnsiString, Request.Cookies["mycookie"]["userid"])
Related
I currently have a system where if a user has forgotten their password, they can reset it by clicking on a forgot password link. They will be taken to a page where they enter in their username/email and then an email will be sent to the user, I wanted to know how can I implement a password reset link in the email so once the user clicks on the link he/she is taken to a page which will allow them to reset their password.
This is the code in my controller
public ActionResult ForgotPassword()
{
//verify user id
string UserId = Request.Params ["txtUserName"];
string msg = "";
if (UserId == null)
{
msg = "You Have Entered An Invalid UserId - Try Again";
ViewData["ForgotPassword"] = msg;
return View("ForgotPassword");
}
SqlConnection lsql = null;
lsql = DBFactory.GetInstance().getMyConnection();
String sqlstring = "SELECT * from dbo.[USERS] where USERID = '" + UserId.ToString() + "'";
SqlCommand myCommand = new SqlCommand(sqlstring, lsql);
lsql.Open();
Boolean validUser;
using (SqlDataReader myReader = myCommand.ExecuteReader())
{
validUser = false;
while (myReader.Read())
{
validUser = true;
}
myReader.Close();
}
myCommand.Dispose();
if (!validUser)
{
msg = "You Have Entered An Invalid UserId - Try Again";
ViewData["ForgotPassword"] = msg;
lsql.Close();
return View("ForgotPassword");
}
//run store procedure
using (lsql)
{
SqlCommand cmd = new SqlCommand("Stock_Check_Test.dbo.RESET_PASSWORD", lsql);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter paramUsername = new SqlParameter("#var1", UserId);
cmd.Parameters.Add(paramUsername);
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
if (Convert.ToInt32(rdr["RC"]) == 99)
{
msg = "Unable to update password at this time";
ViewData["ForgotPassword"] = msg;
lsql.Close();
return View("ForgotPassword");
}
}
}
msg = "new password sent";
ViewData["ForgotPassword"] = msg;
lsql.Close();
return View("ForgotPassword");
}
This is my current stored procedure which sends the user an email
ALTER PROCEDURE [dbo].[A_SEND_MAIL]
#var1 varchar (200), -- userid
#var2 varchar (200) -- email address
AS
BEGIN
declare #bodytext varchar(200);
set #bodytext = 'Password Reset for user: ' +#var1 + ' #' + cast (getDate() as varchar) + ' ' ;
EXEC msdb.dbo.sp_send_dbmail
#profile_name='Test',
#recipients=#var2,
#subject='Password Reset',
#body=#bodytext
END
GO
Create a table that has a structure like
create table ResetTickets(
username varchar(200),
tokenHash varbinary(16),
expirationDate datetime,
tokenUsed bit)
Then in your code when the user clicks the reset password button you will generate a random token then put a entry in that table with the hashed value of that token and a expiration date of something like DATEADD(day, 1, GETDATE()) and appends that token value on the url you email to the user for the password reset page.
www.example.com/passwordReset?username=Karan&token=ZB71yObR
On the password reset page you take the username and token passed in, hash the token again then compare that with the ResetTickets table, and if the expiration date has not passed yet and the token has not been used yet then take the user to a page that lets them enter a new password.
Things to be careful about:
Make sure to expire the token, don't let a email from two years ago reset the password.
Make sure to mark the token as used, don't let other users of the computer use the browser's history to reset other users passwords.
Make sure you generate the random token safely. Don't use Rand and use it to generate the token, two users who reset at the same time would get the same token (I could reset my password and your password at the same time then use my token to reset your account). Instead make a static RNGCryptoServiceProvider and use the GetBytes method from that, the class is thread safe so you don't need to worry about two threads using the same instance.
Be sure to parameterize your queries. In your current code if I typed in the userid '; delete dbo.[USERS] -- it would delete all the users in your database. See the linked SO post for more info on how to fix it.
Be sure you hash the token, your passwordReset page only accepts the unhashed version, and you never store the unhashed version anywhere (including email logs of outgoing messages to users). This prevents an attacker who has read access to the database from making a token for some other user, reading the value that was sent in the email, then sending the same value himself (and perhaps getting access to an administrator user who can do more stuff than just read values).
here are 2 alternatives using HMAC or JWT (which i think provide better, more secure, email URLS)
https://neosmart.net/blog/2015/using-hmac-signatures-to-avoid-database-writes/
https://www.smashingmagazine.com/2017/11/safe-password-resets-with-json-web-tokens/
I am really struggling with creating my own user access roles and utilising the IIdentity and IPrincipal classes.
EDIT
I have been looking at this but I can't get it to work.
I have created a simple EndUser table with email, password and roles columns. However at the moment, I don't think the roles isn't being parsed through to the IIdentity or IPrincipal and I am unsure of how to do it.
I have looked at several guides and most either rely on VS and the .NET Framework building the basis for you with many clunky looking tables, or they are MVC, which isn't helpful at all.
In my table I have three defined roles which are Client, Sales and Admin, I want certain pages inaccessible to certain users. These are then reflected in my webconfig as Denying access globally but allowing access to certain roles and only if authenticated.
At present the login functionality is working, and users are denied unless logged in but the specific roles are not being taken into account. I have an object stored in session of all of the user data across the site, but "roles" is completely ignored in the web config.
Ultimately I need to do something with uRole, but I am not sure what or how.
Could someone point me in the right direction please?
Many thanks
Login C#:
public partial class Login : Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void logindd_Click(object sender, EventArgs e)
{
EndUser newEndUser = new EndUser();
string user = ((TextBox)loginForm1.FindControl("UserName")).Text;
string conRef = ((TextBox)loginForm1.FindControl("Password")).Text;
using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalConnection"].ConnectionString))
{
try
{
con.Open();
string checkIdent = #"SELECT Count(*) FROM EndUser WHERE EndUser.Email=#email AND EndUser.Password=#pass";
using (SqlCommand cmd = new SqlCommand(checkIdent, con))
{
cmd.Parameters.AddWithValue("#email", user);
cmd.Parameters.AddWithValue("#pass", pass);
int chk = (int)cmd.ExecuteScalar();
if (chk > 0)
{
//grabbing user role
SqlCommand cmd2 = new SqlCommand("SELECT EndUser.Role FROM EndUser WHERE EndUser.Password=#pass;",con);
cmd2.Parameters.AddWithValue("#pass", pass);
SqlDataReader sdr = null;
sdr = cmd2.ExecuteReader();
sdr.Read();
string uRole = sdr["Role"].ToString();
UserIdentity userr = new UserIdentity(user, true, uRole);
userr.Roles.Add(uRole);
FormsAuthentication.Initialize();
FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, user, DateTime.Now, DateTime.Now.AddMinutes(30), false, userr.Roles.ToString());
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(fat)));
newEndUser.email = user;
newEndUser.pass = pass;
Session["EndUserObj"] = newEndUser;
FormsAuthentication.RedirectFromLoginPage(user, false);
}
else
{
ErrorLbl.Visible = true;
ErrorLbl.Text = "Details are incorrect";
}
}
}
catch (Exception er)
{
ErrorLbl.Visible = true;
ErrorLbl.Text = er.ToString();
}
}
}
}
I had this issue when I was trying to sort this out a few months back. You're currently logging in at the moment, from the code that you've shown I would have to assume that this is a web forms project with pre-built Web Form kit. Therefore you're still using OWIN to login.
Not a code-based walk-through but this is what you need to do, I'm happy to assist further if you get stuck:
Change the global.asax so that it authenticates the user on Application_AthenticateRequest and remove anything related to OWIN in it's methods.
You need a class to derive from IIdentity and accept params of username, authtype and isAuthenticated and a class to derive from IPrincipal which will contain the derived IIdentity, and the role. I went for public getter and setters and private variables of things like name, is auth etc.
A class to get the identity of the user, this where you'll get against the password and username and create an instance of the derived IIdentity to set, also check against the information held within the database.
The global method Application_AthenticateRequest will need to verify the user on each page load, so you need to have a method that creates an instance of the derived IPrincipal to check whether the user is authenticated by grabbing the derived IIdentity and checking what role the user is in.
Modify the webconfig so that it uses FormsAuthentication, doesn't use OWIN, so within AppSettings add <add key="owin:AutomaticAppStartup" value="false"/> as I assume you have a pre-built site, don't try and remove OWIN, just disable it.
You should then be able to login and the custom roles you declare will then be used by the derived IIdentity which in turn will be acknowledged by the webconfig location based security.
Hope this helps you and others.
Hi I am running two forms of Authentication. The first authentication I run is a an AD Authentication which works fine. The Second is to confirm that the username is in a SQL Database. I am using this code to verify the username exists in SQL.
I get the error
Operator '==' cannot be applied to operands of type 'string' and 'System.Data.SqlClient.SqlDataReader'
with this code....
using (SqlConnection con = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString))
{
SqlCommand cmd = new SqlCommand("select Username from dbo.users", con);
con.Open();
SqlDataReader sqlresult = cmd.ExecuteReader();
if (txtLoginID.Text == sqlresult)
{
Response.Redirect("account/orders.aspx");// Authenticated user redirects to default.aspx
}
else
{
Response.Redirect("default.aspx");// Authenticated user redirects to default.aspx
}
con.Close();
}
}
You have several problems here. First off your query:
select Username from dbo.users
Is pulling every record in the table. This is not the correct way to check if the user name exists. You need something like this:
select Username from dbo.users WHERE UserName = #UserName
Where #UserName is a parameter that contains the value from txtLoginID.Text. Once you have done this you can use:
if(sqlresult.HasRows)
{
Response.Redirect("account/orders.aspx");// Authenticated user redirects to default.aspx
}
else
{
Response.Redirect("default.aspx");// Authenticated user redirects to default.aspx
}
You should also look at using statements.
While it isn't doesn't really matter if you use the above solution, the reason you are getting the error is pretty self explanatory. You are attempting to compare a string value to a SqlDataReader object. This is not possible.
I'm connecting to OpenLDAP with C#, and when I pass in my username and password, I have to pass them into my LdapConnection object as cn=Username, Password. If I just pass in username and password my call to Bind fails. Why do I have to do that? Is something misconfigured on my OpenLDAP server?
It's just a byproduct of the implementation. Novell's eDirectory solution takes a very similar approach, and I use the same Novell.Directory.Ldap code to handle bind requests to both eDirectory and OpenLDAP. Now obviously, the users themselves shouldn't have to enter their entire CN when authorizing - we can just issue a search for them, based of thier UID :
//Setup the initial bind for the admin user
var lc = new LdapConnection();
lc.SecureSocketLayer = SSL;
lc.UserDefinedServerCertValidationDelegate += delegate { return true; };
lc.Connect(ServerName, Port);
lc.Constraints.TimeLimit = Timeout;
lc.Bind(AdminUsername, AdminPassword);
Now I just filter for the user, and bind using their distinguished name, or full container name (CN) :
//Ex. (uid=jsmith)
string filter = config.LdapAuth.LdapFilter.Replace("{{uid}}", username);
//Find the user we're trying to authorize
var lsc = lc.Search(config.LdapAuth.LdapDomain, LdapConnection.SCOPE_SUB, filter, null, false);
if (lsc.hasMore())
{
LdapEntry nextEntry = lsc.next();
//Check the Entries DN so we can properly bind
lc.Bind(nextEntry.DN, Password);
}
This was the most widely used approach I could find, and it's worked quite well so far.
Im using a base controller & OnActionExecuting to get 'common' site data that I want to show & keep accross most page's (name, ID etc... not more than 4/5 fields)
The Method in OnActionExecuting reads the database & saves to ViewBag which Ipick up in my Views but I cant help thinking this is a waste as it needs a DB call for every OnActionExecuting.
How can consolodate these DB calls and slim down the DB access?
What I have done on a recent project is during Login I get the 'common' data which in this case was UserID, FirstName and an ImageName, I saved it in the Auth Ticket like this :
UserData = pModel.PartyId.ToString() + "|" + pModel.BusinessName + "|" + pModel.FirstName + "|" + pModel.LastName + "|" + pModel.ImageUrl + "|" + UsersRole + "|" + IsAct;//PID, BusName, FirstName, LastName, imgUrl, Role, IsAct
// Create the cookie that contains the forms authentication ticket
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(UN, true);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, UserData);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
I then retreive this Cookie when the data is needed and get the data out of it like this :
var cookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
dynamic UN = FormsAuthentication.Decrypt(cookie.Value);
string UserData = UN.UserData;//PID, BusName, FirstName, LastName, imgUrl, Role, IsAct
string[] pFields = UserData.Split('|');
string[] MyRoles = { pFields[5] };
Please Note : This is only good for static data that you know wont change during the login session & be careful what you post in this Cookie.
Don't bloat the cookie either keep the fields to a minimum, max is 4K but I aim for 500-1000 bytes.
Cache the common data values in your repository layer.
You can use System.Runtime.Caching.MemoryCache for this purpose.
Some examples:
http://stevescodingblog.co.uk/net4-caching-with-mvc/
http://msprogrammer.serviciipeweb.ro/tag/memorycache/
The cache will persist across requests until the expiration for each cache item is reached.
Inheriting from a base controller does not mean that you have shared state across requests. Each request will result in a new controller instance.
You need to store this data in some state (cache, session, application) if you don't want to retrieve it every time. Then you can make whatever mechanism you store it in accessible from your base controller.
Then there's the disclaimer...are these trips to the database expensive? Are you going to trade minimal latency problems for memory management issues?
UPDATE:
If it is just 4-5 fields (which appear to be user specific, and like they are not going to change during a user's session), then I'd just store them in session state.
Personal preference here, but I like to have strongly typed accessors in base controllers/pages for things like this:
protected string Name
{
get
{
if (Session["Name"] == null)
{
var results = GoLoadFields();
return Session["Name"].ToString();
}
return Session["Name"].ToString();
}
set
{
Session["Name"] = value;
}
}
Then in all of your controllers that inherit from your base controller can just reference these properties:
myAwesomeViewModel.Name = this.Name;
The memory management disclaimer is meant to have you avoid querying the database, getting the same large result set, and shoving it in the session for each user. Before you keep anything around in memory, just make sure you're only keeping what you need and for how long you need it.