I created single page app that requires username
and password
for users authentication. Each account in the system is created by administrators. There is no option for user to click on the link and create account. After researching and looking for the best solution first thing that I changed was the way how user can recover their password. Here is the short schema on how that logic works. User first has to click on the link Forgot Password
enter their email and submit the form. They will see the message:
An email has been sent to example@gmail.com with further instructions.
The function that will execute this process looks like this:
cfstoredproc( procedure="CheckEmail", datasource=dsn ) {
cfprocparam( maxlength=80, null=!len(trim(arguments.email)), cfsqltype="cf_sql_varchar", dbvarname="@Email", value=trim(arguments.email) );
cfprocresult( name="EmailResult" );
}
if( EmailResult.recordCount EQ 1) {
// Generate new token
local.newToken = createUUID();
// Build URL with token parameter and add hashed value
local.theUrl = "https://example.com/Reset.cfm?token=" & local.newToken;
// Set expiration time (30 minutes)
local.Expires = DateAdd("n", 30, now());
cfstoredproc( procedure="SaveToken", datasource=dsn ) {
cfprocparam( maxlength=80, null=!len(trim(arguments.email)), cfsqltype="cf_sql_varchar", dbvarname="@Email", value=trim(arguments.email) );
cfprocparam( maxlength=35, null=!len(trim(local.newToken)), cfsqltype="cf_sql_varchar", dbvarname="@Token", value=trim(local.newToken) );
cfprocparam( null=!len(trim(local.Expires)), cfsqltype="cf_sql_timestamp", dbvarname="@Expires", value=trim(local.Expires) );
cfprocresult( name="TokenResult" );
}
if ( len(TokenResult.RecID) ) {
savecontent variable="mailBody"{
writeOutput('<br>Here is your password reset link: <a href="' & theUrl & '">Click here</a> as soon as possible and change your password.<br>' );
}
local.mail = new mail();
// Set it's properties
local.mail.setSubject("Example Application");
local.mail.setTo(arguments.email);
local.mail.setFrom("noreply@example.com");
local.mail.setType("html");
// Send the email
local.mail.send(body = mailBody);
local.fnResults = {status : "200", message : "An email has been sent to <b>" & arguments.email & "</b> with further instructions."};
} else {
local.fnResults = {status : "400", message : "Error! Something went wrong."};
}
}else{
savecontent variable="mailBody"{
writeOutput('<br>We recieved a password reset request. The email you have provided does not exist in our system.<br>');
}
local.mail = new mail();
// Set it's properties
local.mail.setSubject("Example Application");
local.mail.setTo(arguments.email);
local.mail.setFrom("noreply@example.com");
local.mail.setType("html");
// Send the email
local.mail.send(body = mailBody);
local.fnResults = {status : "200", message : "An email has been sent to <b>" & arguments.email & "</b> with further instructions."};
}
Then next step is if email exists and user clicks on the link I will either show the form where they can enter new password or they will see message This link has expired or does not exist anymore.
. Here is example of Reset.cfm
page:
if (structKeyExists(url,"token") && isValid("uuid", url.token) && len(trim(url.token)) == 35){
cfstoredproc( procedure="CheckToken", datasource=dsn ) {
cfprocparam( maxlength=35, null=!len(trim(url.token)), cfsqltype="cf_sql_varchar", dbvarname="@Token", value=trim(url.token) );
cfprocresult( name="TokenResult" );
}
if( TokenResult.recordCount == 1 ){ //If token is valid (not expired) show the form.
<form name="frmRecovery" id="frmRecovery" autocomplete="off">
<input type="hidden" name="token" value="<cfoutput>#url.token#</cfoutput>">
<div class="form-group">
<div class="alert alert-info"><strong>Info!</strong> After saving your changes, you will be taken back to the login screen. Log into the system with the account credentials you have just saved.</div>
</div>
<div class="form-group">
<label class="control-label" for="password"><span class="label label-primary">Password</span></label>
<input type="password" class="form-control" name="frmRecovery_password" id="frmRecovery_password" placeholder="Enter Password" maxlength="64" required>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
<div class="form-group">
<div class="alert message-submit"></div>
</div>
</form>
}else{
<div class="alert alert-warning">
<strong>Warrning!</strong> This link has expired or does not exist anymore! </div>
}
}
If user gets directed to the form to enter the password, I will save new password and delete the token. The next step is directing them to login page and they can enter credentials and login. So my question is can I use similar approach when administrator creates new account? the admin needs to enter first
, last name
, username
, etc. Then click the button Send Email
that will forward username
and temporary link where new user can enter they password and login in the application. The logic that is used before is generating temporary password, user logs in and then have to reset the password. I'm wondering if solution that I proposed wold have any security risks or is equally good as one with temp password?
You can use the existing process, given you also have a proc for adding the user information which isn't here. Additionally, you didn't specify the admin would provide an email which would obviously be important to sending the token (unless username IS the email).
To improve security, depending on how hardened / secure you want, some suggestions :
GUID/UUID's are intended to be unique, but certainly not intended to be non-guessable. Their goal is to avoid collision, not prediction. If you output a set of UUID's generated by coldfusion, you could guess with some degree some possible combinations in order to feign a reset process until you found one - given you had a valid UUID to begin with, within a few million or so (very little).
This is how this attack would work - you would send two simultaneous reset requests for two users. One that is known (lets say an employee who has the email but with low permissions) and one that is not known (lets say a manager who you do NOT have access to their email).
You would have the "seed" or known UUID created for the employee (attacker) at roughly the same time as the reset key for the manager (victim). Then you could create reset attempts for that block of UUID's based on the known UUID. It wouldn't take much processing power or time to do so.
If you instead create a composite string of a UUID combined with a randomly generated / stored salt (this would be an additional field you would want to store for the user record.) - this would be the strongest measure as it would be unique and can't be guessed with a known key.
Another is to throttle / limit requests to your reset process (i.e. a 500ms delay etc, the user doesn't see any noticeable difference but severely limit the usefulness of an automated attack).
Additionally you can store attempts by email or user and lock the user out of attempting too many reset requests and require human intervention / an alternate more scrutinizing path for unlocking.
Your passwords should be hashed with a random salt as well, of course.
Goes without saying that requiring strong passwords is more secure.