Registering a New User
Before using the Anonymous Agile Estimator, a new user will have to register their email address and create a password. This post adds the code required.
First a note on password strength. I had considered enforcing some of the common restrictions on password choices that have become vogue, such as forcing special characters, upper and lower-case letters etc., however, there is mounting evidence that this way of choosing a password can be compromised by the number of hacks around it.
For example, in order to make the passwords easier to remember, it is common for users to substitute symbols for letters in easy to remember substitutions: ‘$’ for ‘s’ or ‘@’ for ‘a’ and so on. This allows users to choose passwords that although they conform to the requirements of the password selection regular expressions, they can still succumb to dictionary attacks.
It would be better not to continue the myth that “s3cr3t” or “p@$$w0rd” are better passwords than “secret” or “password”. Simply increasing the length of the minimum password from eight letters to sixteen can have a dramatic effect on the difficulty of cracking it with dictionary attacks.
There may be a complaint that passwords of that length would simply be too difficult to remember, however, there are multiple free and reliable password managers available and most operating systems and browsers can store these longer passwords without any noticeable impact for users.
I have come to use long strings of contiguous, randomly selected words, separated by spaces, as a reasonable form of secure password generation. Just take three or four unrelated words with at least two syllables and separate with spaces.
You can use a random word generator if you like, such as https://randomwordgenerator.com, or come up with the words yourself. I got the password “persist microphone beneficiary neutral” by asking for four words of at least two syllables and clicking a few times.
Testing this password on https://howsecureismypassword.net shows that it has got a decent level of security.
Although there are some problems with this simple form of password such as repetition of letters, I am happy with the length of time it would take to brute force check the password. Another site for password checking, https://password.kaspersky.com, gives it a similar appraisal.
Note that some would still argue that this form of password is weak since it does not contain numerals or non-letter characters, but I’d argue the length is sufficiently long to make it safe from dictionary attacks. The plus side is that it can be easier to remember such a password. Adding simple letter substitutions would not increase its security all that much.
So with that in mind, I have enforced a minimum password length of sixteen characters and used no maximum length. To help users, I added a few tips on how to come up with a secure password.
Code Required
The first thing to add is the option to register if not already a user. There are two places this should be added. The first is in the navigation bar for a user that is not logged in. The code becomes:
<ul class="nav navbar-nav">
{% if current_user.is_authenticated %}
<li><a href="/">My Groups</a></li>
<li><a href="/confirm-logout">Sign Out {{ current_user }}</a></li>
{% else %}
<li><a href="/login">Login</a></li>
<li><a href="/register">Register</a></li>
{% endif %}
</ul>
The other place is on the login form itself. This will allow users that have been redirected to this page to choose to register instead of logging in. Although they may lose the redirect to the original page they were going to.
<div class="container">
<h3>If you have not signed up...</h3>
Click <a href="/register">here to register</a>.
</div>
We are going to use some new validators from WTForms. We already discussed the Email
validator in the last post which ensures that the value in the email field is in the correct format. This will be reused in this form.
Another validator is required to ensure that the password length is at least sixteen characters long. To do this we can set the min
argument of the Length
validator.
We must ensure that the password field matches the repeat password field to protect against user entry error. This can be done EqualTo
validator, specifying the other field to which it must be compared. The new form looks like this:
class RegisterForm(FlaskForm):
email = StringField('Enter your email address', validators=[DataRequired(), Email()])
nickname = StringField('Enter a nickname', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(), EqualTo('repeat_password', message='The passwords do not match.'), Length(min=16, message='Please use at least 16 characters.')])
repeat_password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Register')
The template for entering the password will be stored in register.html
and is simply going to use the wtf.quick_form
to generate the code. I’ve decided to add some tips on password creation to remind prospective users not to make some obvious mistakes with their password choice.
{% extends "base-template.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block content %}
<div class="container">
<h1>Register:</h1>
{{ wtf.quick_form(form) }}
</div>
<div class="container">
<h3>Password advice:</h3>
<ul>
<li>Do not share passwords between different sites or accounts</li>
<li>Choose a long, unique password, of at least 16 characters</li>
<li>Let your browser suggest and store a secure password (most modern browsers offer this functionality)</li>
<li>Do not make a password from guessable data such as your pet, a date of birth, or spouse’s name</li>
<li>Download a password manager to help securely store each unique password and lock it with a strong password</li>
</ul>
</div>
{% endblock %}
The controller is next. The main thing to note is that before creating a new user in the database, we have to first ensure that the email address has not already been used. The code will explicitly search for the email address and redirect the user to the generic error page if it finds a match.
All the other checks have been taken care of by the validators so there is little else to add here, besides the code to create and return the form for the GET request.
@web.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if request.method == 'GET':
return render_template('register.html', form=form)
if form.validate_on_submit():
# check user not already registered
user = User.query.filter_by(email=form.email.data)
if user.count() > 0:
error_message = 'This email address has already been registered.'
back_url = url_for('web.register')
return render_template('generic-error.html', error_message=error_message, back_url=back_url), 400
# add the new user
user = User(form.nickname.data, form.email.data, form.password.data)
db.session.add(user)
db.session.commit()
# log them in
login_user(user)
# redirect to home
return redirect(url_for('web.index'))
# return just the basic view
return render_template('register.html', form=form)
Here is a screen shot of what we end up with showing a couple of the validation errors.
Note that I had forgotten to add the email address to the constructor of the User
object so that will be resolved with this commit and it necessitated some changes to the conftest.py
.
There were some tests to add, such as making sure that a new user is registered. I added some tests to ensure that the passwords matched, although these were probably overkill since the functionality is provided by WTForms. I will leave them in to ensure that my use of the validators is done correctly and not broken by future updates.
You can see all the changes from this post on GitHub with link. Let me know if you have any feedback by emailing [email protected].