7

Confirming User’s Email Address

 3 years ago
source link: https://www.patricksoftwareblog.com/confirming-users-email-address/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction

In this blog post, I’ll show how to confirm the email address of each user when they register. After researching how to manage user’s accounts in a web application, I’m really surprised by websites that don’t follow this process. The idea of sending a password reset email to an unverified email address is a terrible idea that has the chance for compromising your users’ accounts. Also, just think about it in terms of making a mistake when you enter your email address to register… a password reset email would be sent to an incorrect email address.

This blog post will show how to confirm a user’s email address and then in the next blog post, I’ll show how to do a password reset via email.

Here’s the idea on how to confirm a user’s email address when they register with your application: send an email address to the specified email address with a unique link for the user to click on; Once they click on the link, you can process the unique link that was clicked on to confirm the user’s email address.

Adding this functionality will require updating the User model to add additional fields associated with email confirmation (this will also involve working with time stamps), adding a new function to send email confirmations, and adding a new route to handle the unique links that will be clicked. To make all this happen, we’ll be using the itsdangerous module, which provides the ability to generate unique tokens for creating the links in the emails asking users to confirm their email address. This is a really powerful module and I think the introduction from the itsdangerous documentation provides a great description of the module:

Sometimes you just want to send some data to untrusted environments. But how to do this safely? The trick involves signing. Given a key only you know, you can cryptographically sign your data and hand it over to someone else. When you get the data back you can easily ensure that nobody tampered with it.

Configuration

Before we start making any updates, let’s create a new feature branch:

(ffr_env) $ git checkout -b add_confirm_email
(ffr_env) $ git branch

If you’ve been following along with the tutorial, you should have the itsdangerous module installed already. Just to check:

(ffr_env) $ pip install itsdangerous
(ffr_env) $ pip freeze > requirements.txt  (only necessary if you need to install itsdangerous)

Updating the User Model

In order to keep track of whether or not a user’s email address has been confirmed, we need to add the following fields to our User model:

  • Has the email address been confirmed?
  • When was the email with the unique confirmation link sent?
  • When was the email address confirmed?

The last two items will require the use of the Datetime module that is built in to the python standard library to create time stamps for when these events occur.

Here is the updated User model as defined in …/project/models.py:

class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String, unique=True, nullable=False)
_password = db.Column(db.String, nullable=False)
authenticated = db.Column(db.Boolean, default=False)
email_confirmation_sent_on = db.Column(db.DateTime, nullable=True)
email_confirmed = db.Column(db.Boolean, nullable=True, default=False)
email_confirmed_on = db.Column(db.DateTime, nullable=True)
def __init__(self, email, plaintext_password, email_confirmation_sent_on=None):
self.email = email
self.password = plaintext_password
self.authenticated = False
self.email_confirmation_sent_on = email_confirmation_sent_on
self.email_confirmed = False
self.email_confirmed_on = None

The ‘email_confirmed’ field is initialized to False, as the user’s email address needs to be confirmed before setting this value to True. The time stamp associated with when the emil was confirmed is initialized to None, as this field has not been defined yet. The time stamp that the email with the confirmation link was sent is provided as a parameter, which has a default value of None if this parameter is not specified.

Sending Email to Confirm User’s Email Address

The first thing we need to do is create a new helper function for sending the email with a unique token to confirm a user’s email address. Here’s the helper function in …/projects/users/views.py:

def send_confirmation_email(user_email):
confirm_serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
confirm_url = url_for(
'users.confirm_email',
token=confirm_serializer.dumps(user_email, salt='email-confirmation-salt'),
_external=True)
html = render_template(
'email_confirmation.html',
confirm_url=confirm_url)
send_email('Confirm Your Email Address', [user_email], html)

This function utilizes the ‘URLSafeTimedSerializer’ class from the itsdangerous module to generate a unique token based on the email address provided. Plus, there is a time stamp embedded in the token to allow us to set a time limit on the validity of the token (handled when we process the link clicked by the user – see next section).

This function requires that we create a new template for generating the email address. Typically, we’ve been using templates to generate HTML pages for our site, but we can follow a similar process for generating the HTML to use in an email. Here’s the new template defined in …/projects/templates/email_confirmation.html:

{# project/templates/email_confirmation.html #}
Your account on Kennedy Family Recipes was successfully created.
Please click the link below to confirm your email address and
activate your account:
<a href="{{ confirm_url }}">{{ confirm_url }}</a>
--
Questions? Comments? Email [email protected].

This template defines a basic message to instruct the new user to click on the link to confirm their email address with our site.

We now need to update the ‘/register’ route to use this new helper function:

@users_blueprint.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm(request.form)
if request.method == 'POST':
if form.validate_on_submit():
try:
new_user = User(form.email.data, form.password.data)
new_user.authenticated = True
db.session.add(new_user)
db.session.commit()
send_confirmation_email(new_user.email)
flash('Thanks for registering!  Please check your email to confirm your email address.', 'success')
return redirect(url_for('recipes.index'))
except IntegrityError:
db.session.rollback()
flash('ERROR! Email ({}) already exists.'.format(form.email.data), 'error')
return render_template('register.html', form=form)

This is a simple change, thanks to the use of helper functions to handle sending emails. We’re simply changing the basic email that we defined in the previous blog post (Sending Email) to the more complex email with a unique token for confirming a user’s email address.

Processing Unique Token

We haven’t discussed this yet, but this is a really powerful feature of the Flask framework… we can define our routes with variables. Let’s take a look at the code for processing the unique link send to a user when they register (defined in …/projects/users/views.py):

@users_blueprint.route('/confirm/<token>')
def confirm_email(token):
try:
confirm_serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
email = confirm_serializer.loads(token, salt='email-confirmation-salt', max_age=3600)
except:
flash('The confirmation link is invalid or has expired.', 'error')
return redirect(url_for('users.login'))
user = User.query.filter_by(email=email).first()
if user.email_confirmed:
flash('Account already confirmed. Please login.', 'info')
else:
user.email_confirmed = True
user.email_confirmed_on = datetime.now()
db.session.add(user)
db.session.commit()
flash('Thank you for confirming your email address!')
return redirect(url_for('recipes.index'))

This route will be called whenever a user clicks on the link in the email that is sent to them when they register. We couldn’t possibly handle routes for every option of tokens that are specified in the links to the users, so luckily we can use a variable () to define the route and then pass this variable into our function to process the token.

This function first attempts to extract the email address from the token, including a check that the link is less than 3600 seconds (60 seconds per minute * 60 minutes = 1 hour). If the email is successfully extracted, then the user associated with the email address is retrieved from the PostgreSQL database. The ’email_confirmed’ field is updated to true and the current time is saved as a time stamp for when the user confirmed their email address. The datetime module is used for this time stamp, which is a module built-in to the python standard library. This is a great module and calling the now() function provides a quick way to get a date/time stamp. We also need to make sure to commit our changes to the database before proceeding, otherwise the changes to the user account will not be saved.

Putting It All Together

Let’s test this functionality… but first we need to re-initialize our database since we made updates to the User model:

(ffr_env) $ python db_create.py

Now we can fire up the development server:

(ffr_env) $ python run.py

Start by registering for a new account and you should see a new flash message:

Check your email and you should see an email like this with a unique link:

After you click on the link in your email, you should be directed to the main page with a flash message indicating that your email address was confirmed:

Finally, check that clicking on the link a second time will generate an error message since your email address has already been confirmed:

Unit Testing

We’ve made some minor updates to the project, so our unit tests should still pass:

$ nose2

Committing Our Changes

Now that we’ve made changes to our project, we need to stage, commit, merge, and push the changes into our version control system:

$ git add .
$ git status
$ git commit -m “Added confirming email address”
$ git checkout master
$ git merge add_confirm_email
$ git push -u origin master

Conclusion

This blog post showed you how to use the itsdangerous module to confirm a user’s email address when they register. This is a really important feature of your application, as you should not send emails with sensitive information (such as password resets) to an unconfirmed email address.

In the next blog post, we’ll add the ability to reset a user’s password via email, but this will of course be contingent on their email address having been confirmed!

The files associated with this blog post can be found using the ‘v1.4’ tag in GitLab.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK