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.
Related
I have a WebAPI with ef6 code first setup and working. In app start we have dbmigrator.Update() which applies any pending migrations to database.
After changing the connection string to remove username and password and provide access token instead, dbmigrator.Update() fails with an error:
Login failed for user ''
How to ensure that dbmigrator works with Azure SQL access token instead of username/password in connection string?
Edit 1:
The change done to dbcontext constructor is to change it from
DbContext() : base("nameofConnString"){}
to
DbContext() : base(GetSQLConn(), true)
{
Database.SetInitializer<DbContext>(null);
}
With GetSQLConn(), I am retrieving a connection without uname/pwd and attaching accesstoken to it and returning the connection!
Edit 2:
private static SqlConnection GetSQLConn()
{
var accessToken = TokenFactory.AcquireToken();
var connString = ConfigurationManager.ConnectionStrings["SQLConnectionString"].ConnectionString;
var conn = new SqlConnection(connString)
{
AccessToken = accessToken,
};
return conn;
}
How to ensure that dbmigrator works with Azure SQL access token instead of username/password in connection string?
actually dbcontext works for CRUD operations on all my tables, just this migrator won't work!
According to your comment, it seems that you have no permission to alert table. If you don't grant corresponding permission for the created user. Please have a try to grant permission to the created user. More details about db role please refer to the Database-Level Roles.
EXEC sp_addrolemember N'db_owner', N'your-user-name'
I also following the SO thread you mentioned.If I add the user to do_owner, I test it with Alter Table, it works correctly on my side. The following is my detail steps.
1.Registry an Azure AD Application
2.Provision an Azure Active Directory administrator for your Azure SQL server, more details please refer to the this tutorials
3.Create user for the Azure SQL and grant corresponding permission.
CREATE USER [RegistryAppName] FROM EXTERNAL PROVIDER
EXEC sp_addrolemember N'db_owner', 'RegistryAppName'
4.Change the demo code and run it as expected.
SqlConnectionStringBuilder builder =
new SqlConnectionStringBuilder
{
["Data Source"] = "azureServername.database.windows.net",
["Initial Catalog"] = "databaseName",
["Connect Timeout"] = 30
};
// replace with your server name
// replace with your database name
string accessToken = TokenFactory.GetAccessToken();
if (accessToken == null)
{
Console.WriteLine("Fail to acuire the token to the database.");
}
using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
try
{
connection.AccessToken = accessToken;
connection.Open();
var commentText = "ALTER TABLE AccountRec ADD newColumn varchar(10) ";
SqlCommand sqlCommand = new SqlCommand(commentText, connection);
Console.WriteLine("Executed Result:" + sqlCommand.ExecuteNonQuery());
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Console.WriteLine("Please press any key to stop");
Console.ReadKey();
I can't seem to find any helpful articles of what I'm trying to achieve so I'm assuming I'm approaching it incorrectly. I'm building a web application working with an ibm db2 database. Currently I have the following connection string in my web.config file:
<add name="AS400ConnectionString" connectionString="DATASOURCE=myDataSource;USER ID=userID;PASSWORD=password;DefaultCollection=collection;DataCompression=True;" providerName="IBM.Data.DB2.iSeries"/>
and it works just fine with a statically entered userID and password and will allow me to login. My question is how do I get this to work with a dynamic username and password since this application will have a login page where they will enter their username and password?
Edit:
I can get it to work how I want using this code on my code behind page:
protected void Login1_Authenticate(object sender, System.Web.UI.WebControls.AuthenticateEventArgs e)
{
//connection string for database
string constr = "DATASOURCE=source;USERID=" + Login1.UserName.ToString() + ";PASSWORD=" + Login1.Password.ToString() + ";DefaultCollection=collection;DataCompression=True;";
try
{
//Clears failure text if any
Login1.FailureText = "";
//connects to database
using(iDB2Connection myConnection = new iDB2Connection())
{
Session["username"] = Login1.UserName.ToString();
//Authenticates user and sends to default landing page
FormsAuthentication.RedirectFromLoginPage(Login1.UserName, Login1.RememberMeSet);
}
//Sends user to login landing page
Response.Redirect("~/MemberPages/Warranty.aspx");
}
catch (iDB2Exception er)
{
//Displays login errors
ValidationSummary1.ValidationGroup = Login1.UniqueID;
//Displays login errors from AS400
Login1.FailureText = er.MessageDetails.ToString();
}
But then I guess where I get confused is when I get redirected successfully to my landing page how do I then continue to query the database with that users credentials? Or is that when I should start using the previously listed connection string in my web.config file?
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.
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"])