I'm working on an Asp.Net 4.0 web application which is using the Membership and Roles features of Asp.Net.
In the application, there will be three roles which have access to the login page and one role which doesn't.
The way it is supposed to work is as follows.
Back end user (in one of the three privileged roles) creates a user. In the code, this is done programmatically rather than using the Register User page. When the user is created, they are added to the unprivileged role, which, for the sake of clarity is called the Visitor role.
Back end user then creates a link for the new user which is in the form of https://www.example.com?link=cc82ae24-df47-4dbf-9440-1a6923945cf2
When the link is visited, the application will locate the user associated with the query string, get the username and password (which are suitably hashed and salted) and logs the user in.
This is where I'm coming undone. So far, the user creation is working just fine, the database query based on the value of the query string is doing its job and returning the relevant bits of information, it all seems to be flowing pretty well, except for the actual logging in. There doesn't seem to be a clear method of logging a user in programmatically, without going through the rigmarole of getting the user to log themselves in, which I have been explicitly told to avoid.
Here is my code, the "pers" object is a class which is populated from the database when they land on the default page of the link:
protected void LogUserIn(string p)
{
SqlConnection conn = UtilityMethods.GetConnection();
Guid par = Guid.Parse(p);
BioProspect pers= new BioProspect(conn);
pers.FillDetailsFromDb(par);
testlable.Text = pers.ToString();
Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
try
{
if (Membership.ValidateUser(pers.Email, pers.Pword))
{
FormsAuthentication.SetAuthCookie(pers.Email, true);
Response.Redirect(Request.Url.Scheme + "://" + Request.Url.Host + "/About.aspx");
testlable.Text = "Logged in!";
}
else
{
throw new Exception("Something went wrong");
}
}
catch (Exception ex)
{
StringBuilder sb = new StringBuilder();
foreach (DictionaryEntry d in ex.Data)
{
sb.Append(d.Key.ToString());
sb.Append(": ");
sb.Append(d.Value.ToString());
sb.Append("\r\n");
}
AMessage(sb.ToString());
}
}
Now, I've checked the values of the username and password against the database table itself and it is all correct. I understand the password stored in the aspnet tables has been salted and hashed, so I'm checking the values in a temporary table I created to hold the clear text. It's right. In addition, in the code above, the FormsAuthentication.SetAuthCookie method is asking for a username and password - in this instance, the username is the email address. This information is also correct when inspected during debugging.
I should point out, the links we are sending are pretty much one time links. Every time a change is made relevant to that particular user, the value of the link parameter will change and the old link will be come completely useless. The page they will be redirected to will hold documents which are directly relevant to that particular user and to no others.
However, we still need the benefits of the Asp.Net Membership, Profiles and Roles framework, since the "Visitor" may well have several links sent to them, with different documents and versions of documents being added and changed over time.
Can anyone think of a better way? I have so far looked at most of the relevant entries here and here but they all seem somewhat incomplete.
EDIT
I seem to have at least partially resolved this, using information gleaned from the accepted answer here
Essentially, the problem was my web.config membership section needed to be told which hashing algorithm to use. At the moment, I have no idea what the default one is, if there is one, but adding
<membership hashAlgorithmType="SHA1">
to the web.config has at least allowed me to log in users created after the above line was added. The next step is for me to understand why I can't get the other users logged in.
I am however still getting a ThreadAbortException as suggested by Joe, which I am now busily working to resolve.