How to generate a ciphered html URL in C#? - c#

Creating an intranet application, in which URL will be sent to the user via Email(outlook). This URL should be generated using cipher(need a randon URL based on his Email/Emp ID) and through this URL user should be able to login into account. The link should also have time validity. Example: link expires after 7 days.
What I have?
User's Email and Employee ID.
Any pointers, how to achieve the above task in C#? or Any other idea, because I just need to User to access application for certain time using Email only.

It seems that you have gotten it a bit mixed up. The URL cannot be encrypted with the ID or emai. There is little point in using as a key the Employee Id or the email since they are public information.
What you could do is create a random string for the URL and just use the two pieces of information to generate the string. However, once they visit the URL you will need to ask them for a secret password in order to log in. Just by visiting the URL and the inputting their email you achieve nothing in terms of security. It is trivial to break such a system by bruteforce if you have access to the emails/employee ids (which you must assume all have).
For the URL based on the email you can xor the two strings and send the output. Then xor the output with the email and get the url.
public static string xorIt(string email, string url)
{
StringBuilder sb = new StringBuilder();
for(int i=0; i < input.Length; i++)
sb.Append((char)(url[i] ^ email[(i % key.Length)]));
String result = sb.ToString ();
return result;
}
a way to check for validity is to store the url in a database along with a timestamp and check at the time of login if it still valid.
There are some caveats with the above approach (e.g. xor always produces the same output for the same two strings but this can be solved if you add a random string and store that string in the table above).

You can't lock this down to their e-mail only, because they could just copy & paste the URL into a browser.
Essentially to get the time-sensitive behaviour, you need to have the following:
A database table (or some form of storage) to indicate that this user has been issued a token, and when that token expires
A means to generate the cipher-url from the user's e-mail address.
Generating the URL part is easy, in pseudocode you could envision it being as such:
var user = GetUser("John.Doe#gmail.com");
var token = EncryptAndHashEmailAddress(user);
var entry = new TokenTableEntry(token:token, expires:DateTime.Now.AddDays(7), User: user.Id);
var url = #"https://www.mywebsite.com/login.aspx?token=" + token;
Then when the user clicks the link and hits whatever handles the login request:
var token = GetQueryParameter(0); // Get first query parameter
var entry = TokenTable.Element(e => e.Token == token);
TokenTable.Remove(entry);
if (DateTime.Now > entry.Expires)
{
// Their token has expired
GoTo("TokenExpired.aspx");
return;
}
LoginController.UserLoggedIn(entry.Id);
This is very rough psuedocode, but it should put you on the right tracks.

Related

how to add additional query after link path

I am sending link to email address for password reset functionality and after sometime i want this link to expire. for that i have created a token(which is encrytped using a key) and expire-date and i want to put these as query in my email link but i don't know to do it.
this is how i use token class in forgotPassword Post method.
var tokenModel = new LinkExpire();
tokenModel.ExpiresOn = DateTime.Now.AddSeconds(10);
tokenModel.CreateToken = TokenHelperMethods.GetToken(tokenModel);
this is my link code.
string resetCode = Guid.NewGuid().ToString();
var varifyUrl = "/E_HealthCare_Web/Account/ResetPassword/" + resetCode;
var link = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, varifyUrl);
and in my email body i am sending link like this
"<br/> <br/> <a href = '" + link +"&expire="+tokenModel.ExpiresOn+"&token="+tokenModel.CreateToken+"'>Reset link</a> <br/><br/>" +
which does not to work as expected. anyone can help me achieve this, also i am not using core, only mvc5.
Edit this is my controller where i am recieving link values
public ActionResult ResetPassword(string id, DateTime expire, string token)
while clicking on link gives A potentially dangerous Request.Path value was detected from the client (&) error.
Here is my suggestion, instead of adding expiration token query parameters with URL manage this at your method action level i.e.
You already have the information that which login is going to this URL. All you have to do is that before sending this URL via email, make a separate temp table that will have user ID, reset password URL path, created date/time column (this column will mange the data/time when you send the URL to the user for password reset) and active/Iactive status column.
Now at code level when this particular URL is hit by user, first get the active row only entry against this URL & user ID and get the created date/time column value.
Check the difference between the active created date/time column and current date/time.
if difference between two dates is more than 24hr send expiration response otherwise change the password.
Mark that entry as inactive.
Know that against each user the active entry in this new table exist only when user request's password reset, otherwise all existing entries are marked as inactive.
You can delete instead of active/inactive as well. this is temp table.

Logging in to website with C# with MD5 password encryption

I've searched this site and found how to programmatically log in to a website in C#. However, this specific site encrypts the password somehow before sending the form.
I snooped on several POSTs using Live HTTP Headers for Firefox and found:
Account=[REDACTED]&pw=a0c41f57ef14e43642269bb1e452ae40
Account=[REDACTED]&pw=17fd04959cdf6f44b799221fb9a2e0e3
The password that is sent changes every time.
As I press login, I can see the password change in the form.
I looked at the page source and it might use "MD5 encryption", however I don't know how I would call the function that encrypts it, and I don't know why it changes every time after researching MD5.
Here's the function:
function doLogin(form)
{
var originalpw = form.pw.value;
var b64pw = b64_md5(originalpw);
var hmac_md5pw = hex_hmac_md5(pskey, b64pw)
form.pw.value = hmac_md5pw;
form.dbpw.value = hex_hmac_md5(pskey, originalpw.toLowerCase())
if (form.ldappassword!=null) {
// LDAP is enabled, so send the clear-text password
// Customers should have SSL enabled if they are using LDAP
form.ldappassword.value = originalpw; // Send the unmangled password
}
return true;
}
EDIT:
Alright, now I'm running the javascript using Noesis.Javascript. It works perfectly, but one last thing is the "pskey" variable. It changes every time you load the login page, and I found where it is so I can Regex it from the page source.
HOWEVER:
How will the page know that the webclient that downloaded the html code for the pskey is the same one that sends the POST to login and also GETs the logged in page?
I have these requests:
GET login page for pskey
POST login
GET logged in page
How can I use the same WebClient for each?
Javascript can be run in C# by using Javascript.Net.
using Noesis.Javascript;
//...
string runJavascript(string str){
using (JavascriptContext context = new JavascriptContext())
{
//Set external variables:
context.SetParameter("var", str);
context.Run("function jsFunc(s) { //encode in here } str2 = jsFunc(var)");
Console.WriteLine(context.GetParameter("str2"));
return context.GetParameter("str2").ToString();
}
}

C# ASP.NET Identity

I have a scenario here whereby when a user wants to reset a password, the system will have to send a temporary random generated password to the user by email. I tried storing the temporary password into a new column in the database but I am not really sure about whether this approach works well. Some people recommend using token such as below:
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
However, I am really new to ASP.NET and I am not familiar with token. How do I compare the temporary generated token with the token in the database?
Another method that I found to implement this is to have a Membership.GeneratePassword function that generates a random string of characters:
model.temppwd = Membership.GeneratePassword(10, 1);
Can anybody provide me an ideal way to implement this functionality with some example? Thank you!
In our project we used
Guid.NewGuid();
and sent the email containing the link to the recover password action (MVC) as a query string: https://yoursite.com/account/reset/?code=your_guid_code
Example:
ResetPassword resetPassword = new resetPassword();
resetPassword.Code = Guid.NewGuid();
string strLink = string.Format("{0}", actionUrl + "?code="+ resetPassword.Code);`
And now you can use the strLink to send with your e-mail. You'll need to store the Guid in a database table alongside with the userId, so that you can implement the resetting procedure. When the user clicks the link from your email he'll get in a form / view that asks for a new password. Also you'll want to add an extra column to that table in order to provide an expiration limit for that code. If the user clicks the link in the e-mail and the code expired you'll have to inform the user and send another e-mail with another code.

Trying to encrypt a random key for password reset functionality

I am trying to encrypt a random token to be stored in the user table when a user uses the forgot password field by entering their username. It will also send out an e-mail with a url to the change user password page. This url will have a query string param called 'key'.
i.e. www.mysite.com/Changepassword?key=xfsdfsdffsdfiughjksdf.
Once the user clicks the link and they are on this page. I have a function that will get a user by ResetToken. If it finds a user then proceed.
I need advice on a few things:
What kind of random token/encryption technique should I use to
ensure that no one can go to the Changepassword page and guess a key
and be able to change a users password.
How will I handle making this key url friendly?
If you want to be super-secure, using the cryptographically secure random number generator would work. And converting to hex is the easiest way to ensure it's URL-safe:
byte[] bytes = new byte[8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(bytes);
}
string key = string.Join("", bytes.Select(b => b.ToString("X2")));
EDIT Keep in mind that e-mail is potentially insecure, so it's possible (however unlikely) that your message can be intercepted by a malicious third-party before reaching the recipient.

How to Implement Password Resets?

I'm working on an application in ASP.NET, and was wondering specifically how I could implement a Password Reset function if I wanted to roll my own.
Specifically, I have the following questions:
What is a good way of generating a Unique ID that is hard to crack?
Should there be a timer attached to it? If so, how long should it be?
Should I record the IP address? Does it even matter?
What information should I ask for under the "Password Reset" screen ? Just Email address? Or maybe email address plus some piece of information that they 'know'? (Favorite team, puppy's name, etc)
Are there any other considerations I need to be aware of?
NB: Other questions have glossed over technical implementation entirely. Indeed the accepted answer glosses over the gory details. I hope that this question and subsequent answers will go into the gory details, and I hope by phrasing this question much more narrowly that the answers are less 'fluff' and more 'gore'.
Edit: Answers that also go into how such a table would be modeled and handled in SQL Server or any ASP.NET MVC links to an answer would be appreciated.
EDIT 2012/05/22: As a follow-up to this popular answer, I no longer use GUIDs myself in this procedure. Like the other popular answer, I now use my own hashing algorithm to generate the key to send in the URL. This has the advantage of being shorter as well. Look into System.Security.Cryptography to generate them, which I usually use a SALT as well.
First, do not immediately reset the user's password.
First, do not immediately reset the user's password when they request it. This is a security breach as someone could guess email addresses (i.e. your email address at the company) and reset passwords at whim. Best practices these days usually include a "confirmation" link sent to the user's email address, confirming they want to reset it. This link is where you want to send the unique key link. I send mine with a link like: example.com/User/PasswordReset/xjdk2ms92
Yes, set a timeout on the link and store the key and timeout on your backend (and salt if you are using one). Timeouts of 3 days is the norm, and make sure to notify the user of 3 days at the web level when they request to reset.
Use a unique hash key
My previous answer said to use a GUID. I'm now editing this to advise everyone to use a randomly generated hash, e.g. using the RNGCryptoServiceProvider. And, make sure to eliminate any "real words" from the hash. I recall a special 6am phone call of where a woman received a certain "c" word in her "suppose to be random" hashed key that a developer did. Doh!
Entire procedure
User clicks "reset" password.
User is asked for an email.
User enters email and clicks send. Do not confirm or deny the email as this is bad practice as well. Simply say, "We have sent a password reset request if the email is verified." or something cryptic alike.
You create a hash from the RNGCryptoServiceProvider, store it as a separate entity in an ut_UserPasswordRequests table and link back to the user. So this so you can track old requests and inform the user that older links has expired.
Send the link to the email.
User gets the link, like http://example.com/User/PasswordReset/xjdk2ms92, and clicks it.
If the link is verified, you ask for a new password. Simple, and the user gets to set their own password. Or, set your own cryptic password here and inform them of their new password here (and email it to them).
Lots of good answers here, I wont bother repeating it all...
Except for one issue, which is repeated by almost every answer here, even though its wrong:
Guids are (realistically) unique and statistically impossible to guess.
This is not true, GUIDs are very weak identifiers, and should NOT be used to allow access to a user's account.
If you examine the structure, you get a total of 128 bits at most... which is not considered a lot nowadays.
Out of which the first half is typical invariant (for the generating system), and half of whats left is time-dependant (or something else similar).
All in all, its a very weak and easily bruteforced mechanism.
So don't use that!
Instead, simply use a cryptographically strong random number generator (System.Security.Cryptography.RNGCryptoServiceProvider), and get at least 256 bits of raw entropy.
All the rest, as the numerous other answers provided.
First, we need to know what you already know about the user. Obviously, you have a username and an old password. What else do you know? Do you have an email address? Do you have data regarding the user's favorite flower?
Assuming you have a username, password and working email address, you need to add two fields to your user table (assuming it is a database table): a date called new_passwd_expire and a string new_passwd_id.
Assuming you have the user's email address, when someone requests a password reset, you update the user table as follows:
new_passwd_expire = now() + some number of days
new_passwd_id = some random string of characters (see below)
Next, you send an email to the user at that address:
Dear so-and-so
Someone has requested a new password for user account <username> at <your website name>. If you did request this password reset, follow this link:
http://example.com/yourscript.lang?update=<new\_password\_id>
If that link does not work you can go to http://example.com/yourscript.lang and enter the following into the form: <new_password_id>
If you did not request a password reset, you may ignore this email.
Thanks, yada yada
Now, coding yourscript.lang: This script needs a form. If the var update passed on the URL, the form just asks for the user's username and email address. If update is not passed, it asks for username, email address, and the id code sent in the email. You also ask for a new password (twice of course).
To verify the user's new password, you verify the username, email address, and the id code all match, that the request has not expired, and that the two new passwords match. If successful, you change the user's password to the new password and clear the password reset fields from the user table. Also be sure to log the user out/clear any login related cookies and redirect the user to the login page.
Essentially, the new_passwd_id field is a password that only works on the password reset page.
One potential improvement: you could remove <username> from the email. "Someone has request a password reset for an account at this email address...." Thus making the username something only the user knows if the email is intercepted. I didn't start off that way because if someone is attacking the account, they already know the username. This added obscurity stops man-in-the-middle attacks of opportunity in case someone malicious happens to intercept the email.
As for your questions:
generating the random string: It doesn't need to be extremely random. Any GUID generator or even md5(concat(salt, current_timestamp())) is sufficient, where salt is something on the user record like timestamp account was created. It has to be something the user can't see.
timer: Yes, you need this just to keep your database sane. No more than a week is really necessary but at least 2 days since you never know how long an email delay might last.
IP Address: Since the email could be delayed by days, IP address is only useful for logging, not for validation. If you want to log it, do so, otherwise you don't need it.
Reset Screen: See above.
A GUID sent to the email address of record is likely enough for most run-of-the-mill applications - with timeout even better.
After all, if the users emailbox has been compromised(i.e. a hacker has the logon/password for the email address), there is not much you can do about that.
You could send an email to user with a link. This link would contain some hard to guess string (like GUID). On server side you would also store the same string as you sent to user. Now when user presses on link you can find in your db entry with a same secret string and reset its password.
1) For generating the unique id you could use Secure Hash Algorithm.
2) timer attached? Did you mean an Expiry for the reset pwd link?
Yes you can have an Expiry set
3) You can ask for some more information other than the emailId to validate..
Like date of birth or some security questions
4) You could also generate random characters and ask to enter that also along with the
request.. to make sure the password request is not automated by some spyware or things like that..
I think Microsoft guide for ASP.NET Identity is a good start.
https://learn.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity
Code that I use for ASP.NET Identity:
Web.Config:
<add key="AllowedHosts" value="example.com,2.example" />
AccountController.cs:
[Route("RequestResetPasswordToken/{email}/")]
[HttpGet]
[AllowAnonymous]
public async Task<IHttpActionResult> GetResetPasswordToken([FromUri]string email)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
Logger.Warn("Password reset token requested for non existing email");
// Don't reveal that the user does not exist
return NoContent();
}
//Prevent Host Header Attack -> Password Reset Poisoning.
//If the IIS has a binding to accept connections on 80/443 the host parameter can be changed.
//See https://security.stackexchange.com/a/170759/67046
if (!ConfigurationManager.AppSettings["AllowedHosts"].Split(',').Contains(Request.RequestUri.Host)) {
Logger.Warn($"Non allowed host detected for password reset {Request.RequestUri.Scheme}://{Request.Headers.Host}");
return BadRequest();
}
Logger.Info("Creating password reset token for user id {0}", user.Id);
var host = $"{Request.RequestUri.Scheme}://{Request.Headers.Host}";
var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = $"{host}/resetPassword/{HttpContext.Current.Server.UrlEncode(user.Email)}/{HttpContext.Current.Server.UrlEncode(token)}";
var subject = "Client - Password reset.";
var body = "<html><body>" +
"<h2>Password reset</h2>" +
$"<p>Hi {user.FullName}, please click this link to reset your password </p>" +
"</body></html>";
var message = new IdentityMessage
{
Body = body,
Destination = user.Email,
Subject = subject
};
await UserManager.EmailService.SendAsync(message);
return NoContent();
}
[HttpPost]
[Route("ResetPassword/")]
[AllowAnonymous]
public async Task<IHttpActionResult> ResetPasswordAsync(ResetPasswordRequestModel model)
{
if (!ModelState.IsValid)
return NoContent();
var user = await UserManager.FindByEmailAsync(model.Email);
if (user == null)
{
Logger.Warn("Reset password request for non existing email");
return NoContent();
}
if (!await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", model.Token, UserManager, user))
{
Logger.Warn("Reset password requested with wrong token");
return NoContent();
}
var result = await UserManager.ResetPasswordAsync(user.Id, model.Token, model.NewPassword);
if (result.Succeeded)
{
Logger.Info("Creating password reset token for user id {0}", user.Id);
const string subject = "Client - Password reset success.";
var body = "<html><body>" +
"<h1>Your password for Client was reset</h1>" +
$"<p>Hi {user.FullName}!</p>" +
"<p>Your password for Client was reset. Please inform us if you did not request this change.</p>" +
"</body></html>";
var message = new IdentityMessage
{
Body = body,
Destination = user.Email,
Subject = subject
};
await UserManager.EmailService.SendAsync(message);
}
return NoContent();
}
public class ResetPasswordRequestModel
{
[Required]
[Display(Name = "Token")]
public string Token { get; set; }
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 10)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}

Categories

Resources