In my asp.net mvc application, when a user registers, I send a email to their account with a link for validation before they can use the app. See code snippet below.
var emailActionLink = Url.Action("ValidateAccount", "Register",
new { Token = registeredUserViewModel.Id, Username = registeredUserViewModel.Username },
Request.Url.Scheme);
The snippet above is what they will click on which will then call an action with the route values,
Validate Account Action
public ActionResult ValidateAccount(string token, string username)
{
try
{
if (!string.IsNullOrEmpty(token) && !string.IsNullOrEmpty(username))
{
var user = _userServiceClient.IsUserNameAvailable(username);
if (!user.HasValue) throw new NullReferenceException("This account does not exist");
var userContract = user.Value;
userContract.EmailVerified = true;
if (_userServiceClient.UpdateUser(userContract) == null) throw new Exception("Something has gone wrong");
return View("ValidationCompleted");
}
else
{
ViewBag.RegisteredUser = null;
}
}
catch (Exception exception)
{
throw;
}
return View();
}
The problem is, this method is not verifiying the token, what will happen if someone changes the value of token
in the uri, this will still pass and very the account. What will be the right approach in improving this.
In this case, token is the user's Id which is a Guid, but it is encoded and there is no way of comparing the user's Id in my database with this encoded token. I think this is encoded in the action link.
Rather than using your Id, you would probably be better off having a Token field in the table (nullable, so that it can be cleared out after validation). Generate a URL-safe token (I use hex strings, which don't use any special characters), and then look for that token in the database in the validation action.
Here's an example of a token generator:
public class TokenGenerator
{
public static string GenerateToken(int size = 32)
{
var crypto = new RNGCryptoServiceProvider();
byte[] rbytes = new byte[size / 2];
crypto.GetNonZeroBytes(rbytes);
return ToHexString(rbytes, true);
}
private static string ToHexString(byte[] bytes, bool useLowerCase = false)
{
var hex = string.Concat(bytes.Select(b => b.ToString(useLowerCase ? "x2" : "X2")));
return hex;
}
}
Then, add the appropriate method to your service class:
public YourUserType GetUserForToken(string token, string userName)
{
return YourDbContext.Users
.SingleOrDfault(user => user.Token.Equals(token, StringComparison.OrdinalIgnoreCase)
&& user.UserName.Equals(userName, StringComparison.OrdinalIgnoreCase));
}
Obviously, this makes some assumptions on your table structure and data access code.