Verify the registered user's Email method in the Python Flask framework

Source: Internet
Author: User
Tags configuration settings sqlite browser
This article mainly introduces how to verify the Email of registered users in the Python Flask framework, including a very detailed test process, which is highly recommended! For more information about how to verify your email address during user registration, see the next tutorial.

In terms of workflow, a confirmation letter will be sent after the user registers a new account. Until the user completes "Verification" in the email as instructed, their account will remain "Unverified. This is a workflow that most network applications use.

One important thing is that unverified users have any permissions? Or do you have full or restricted permissions for your applications? Do you still have no permissions at all? For applications in this tutorial, unverified users will enter a page after logging on, reminding them that they can only access the application after verifying the account.

Before we begin, I will explain that many of the features we want to add are Flask-user and Flask-security extensions-the question is, why not directly use these two extensions? Well, first of all, this is a learning opportunity. At the same time, both extensions have limitations, such as supported databases. What if you want to use RethinkDB?
Let's get started.

Flask basic registration

We will start with a Flask sample, which includes basic user registration. Obtain the code library from this github repository. Once you create and activate virtualenv, run the following command to start quickly:

$ pip install -r requirements.txt$ export APP_SETTINGS="project.config.DevelopmentConfig"$ python manage.py create_db$ python manage.py db init$ python manage.py db migrate$ python manage.py create_admin$ python manage.py runserver

When the application is running, visit the http: // localhost: 5000/register page to register a new user. Note: after registration, the application will automatically log on to guide you to the home page. Let's take a look at it and run the code-especially the user Blueprint (Blueprint is a concept of flask ).

Stop the server when it is finished.

Update current application

Model

First, add the confirmed field to the User model in project/models. py:

class User(db.Model):  __tablename__ = "users"  id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String, unique=True, nullable=False) password = db.Column(db.String, nullable=False) registered_on = db.Column(db.DateTime, nullable=False) admin = db.Column(db.Boolean, nullable=False, default=False) confirmed = db.Column(db.Boolean, nullable=False, default=False) confirmed_on = db.Column(db.DateTime, nullable=True)  def __init__(self, email, password, confirmed,   paid=False, admin=False, confirmed_on=None): self.email = email self.password = bcrypt.generate_password_hash(password) self.registered_on = datetime.datetime.now() self.admin = admin self.confirmed = confirmed self.confirmed_on = confirmed_on

Note how this area defaults to "False. You also need to add a confirmed_on field, which is a datetime. To analyze the differences between registered_on and confirmed_on dates using queue analysis, I want to include this datetime.

Let's create a database from scratch and migrate it! Therefore, delete the database dev. sqlite and the "Migration" folder first.

Control Command

Next, in manage. py, update the create_admin command to make the new database field take effect:

@manager.commanddef create_admin(): """Creates the admin user.""" db.session.add(User( email="ad@min.com", password="admin", admin=True, confirmed=True, confirmed_on=datetime.datetime.now()) ) db.session.commit()

Make sure to import datetime. Now, run the following command again:

$ python manage.py create_db$ python manage.py db init$ python manage.py db migrate$ python manage.py create_admin

Register () view Functions

Finally, before registering a user again, we need to change the register () view function in project/user/views. py ......

user = User( email=form.email.data, password=form.password.data)

Change it to the following:

user = User( email=form.email.data, password=form.password.data, confirmed=False)

Do you understand? Consider why the default value of confirmed is False.

Well. Run the application again. Transfer to http: // localhost: 5000/register and register a new user. If you open your SQLite database in the SQLite browser, you will see:

So, this new user I registered, the michael@realpython.com is not verified. Let's verify it.

Add email Verification

Generate verification token

Email verification should contain a special URL so that the user can simply click it to verify his/her account. Ideally, this URL should look like this-http://yourapp.com/confirm/ . The key here is its id. Use the itsdangerous package in this id to encode your email (including the timestamp ).

Create a file named project/token. py and add the following code:

# project/token.py from itsdangerous import URLSafeTimedSerializer from project import app def generate_confirmation_token(email): serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT']) def confirm_token(token, expiration=3600): serializer = URLSafeTimedSerializer(app.config['SECRET_KEY']) try: email = serializer.loads(  token,  salt=app.config['SECURITY_PASSWORD_SALT'],  max_age=expiration ) except: return False return email

Therefore, in the generate_confirmation_token () function, use URLSafeTimedSerializer to generate a token from the e-mail address obtained during user registration. The _ real _ email is coded in the token. After confirming the token, In the confirm_token () function, we can use the loads () method, which takes over the token and its expiration time-effective within one hour (3600 seconds)-as a parameter. As long as the token does not expire, it will return an email.

In the application configuration (BaseConfig (), make sure to add SECURITY_PASSWORD_SALT:

SECURITY_PASSWORD_SALT = 'my_precious_two'

Update the register () view Function

Now update the register () view function from project/user/views. py:

@user_blueprint.route('/register', methods=['GET', 'POST'])def register(): form = RegisterForm(request.form) if form.validate_on_submit(): user = User(  email=form.email.data,  password=form.password.data,  confirmed=False ) db.session.add(user) db.session.commit()  token = generate_confirmation_token(user.email)

Also, make sure that these import modules are updated:

from project.token import generate_confirmation_token, confirm_token

Process Email Verification

Next, add a new view to solve email Verification:

@user_blueprint.route('/confirm/
 
  ')@login_requireddef confirm_email(token): try: email = confirm_token(token) except: flash('The confirmation link is invalid or has expired.', 'danger') user = User.query.filter_by(email=email).first_or_404() if user.confirmed: flash('Account already confirmed. Please login.', 'success') else: user.confirmed = True user.confirmed_on = datetime.datetime.now() db.session.add(user) db.session.commit() flash('You have confirmed your account. Thanks!', 'success') return redirect(url_for('main.home'))
 

Add this to project/user/views. py. Similarly, make sure that these imports are updated:

Import datetime
Now we call the confirm_token () function through the token. If the operation succeeds, update the user, change the email_confirmed attribute to True, and set datetime to the time when the verification occurred. Also, if the user has already completed the verification process and has been verified, we would like to remind the user of this.

Create email template

Next, add a basic email template:

Welcome! Thanks for signing up. Please follow this link to activate your account:

{{ confirm_url }}


Cheers!

Save this as activate.html in "project/templates/usermetadata. This uses a variable called confirm_url, which will be created in the register () view function.

Send email

You can use Flask-Mail installed in project/_ init _. py to create a basic function to send emails.

Create an email. py file:

# project/email.py from flask.ext.mail import Message from project import app, mail def send_email(to, subject, template): msg = Message( subject, recipients=[to], html=template, sender=app.config['MAIL_DEFAULT_SENDER'] ) mail.send(msg)

Save it in the "project" folder.

Therefore, we only need to simply process the recipient list, topic, and template. We will process the email configuration settings.

In project/user/views. py (again !) Update the register () view Function

@user_blueprint.route('/register', methods=['GET', 'POST'])def register(): form = RegisterForm(request.form) if form.validate_on_submit(): user = User(  email=form.email.data,  password=form.password.data,  confirmed=False ) db.session.add(user) db.session.commit()  token = generate_confirmation_token(user.email) confirm_url = url_for('user.confirm_email', token=token, _external=True) html = render_template('user/activate.html', confirm_url=confirm_url) subject = "Please confirm your email" send_email(user.email, subject, html)  login_user(user)  flash('A confirmation email has been sent via email.', 'success') return redirect(url_for("main.home"))  return render_template('user/register.html', form=form)

Add the following import module:

From project. email import send_email
Here we integrate everything together. The basic function of this function is as a controller (either directly or indirectly ):

  • Process initial registration,
  • Generate the token and confirm the URL,
  • Send confirmation email,
  • Quick verification,
  • User Login,
  • Change the user.

Have you noticed the _ external = True parameter? This adds a complete URL containing hostname and port (in our case, http: // localhost: 5000.

Before we test this, we need to set email settings.

Email

Update BaseConfig () in project/config. py ():

class BaseConfig(object): """Base configuration."""  # main config SECRET_KEY = 'my_precious' SECURITY_PASSWORD_SALT = 'my_precious_two' DEBUG = False BCRYPT_LOG_ROUNDS = 13 WTF_CSRF_ENABLED = True DEBUG_TB_ENABLED = False DEBUG_TB_INTERCEPT_REDIRECTS = False  # mail settings MAIL_SERVER = 'smtp.googlemail.com' MAIL_PORT = 465 MAIL_USE_TLS = False MAIL_USE_SSL = True  # gmail authentication MAIL_USERNAME = os.environ['APP_MAIL_USERNAME'] MAIL_PASSWORD = os.environ['APP_MAIL_PASSWORD']  # mail accounts MAIL_DEFAULT_SENDER = 'from@example.com'

View official Flask-Mail documentation for more information
If you already have a GMAIL account, you can use it or register a GMAIL account for testing. Then, temporarily set the environment variable in the current shell:

$ export APP_MAIL_USERNAME="foo"$ export APP_MAIL_PASSWORD="bar"

If your GMAIL account has two-step authorization, Google will block it.
Test now!

First test

Open the application and transfer it to http: // localhost: 5000/register. Then register with the email address you can log on. If it succeeds, you will receive an email, which looks like this:

Click the URL to go to http: // localhost: 5000 /. Ensure that the 'confirmed 'field is True in the database, and a datetime and confirmed_on field are bound together.

Good!

Processing license

If you remember, at the beginning of the tutorial, we decided that "Unverified users can log on, but they will be immediately transferred to a page -- what we call/unconfirmed path -- To remind users that they need to verify the account before using the application ".

So we want --

  • Add/unconfirmed path
  • Unconfirmed.html Template
  • Update the register () view Function
  • Create decorator
  • Update the navigation.html Template
  • Add/unconfirmed path

Add the following path project/user/views. py:

@user_blueprint.route('/unconfirmed')@login_requireddef unconfirmed(): if current_user.confirmed: return redirect('main.home') flash('Please confirm your account!', 'warning') return render_template('user/unconfirmed.html')

You have read similar code, so let's continue.

Unconfirmed.html Template

{% extends "_base.html" %} {% block content %} Welcome!

You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.

Didn't get the email? Resend.

{% endblock %}

In "project/templates/user", save this as unconfirmed.html. This time should be straightforward. Now, only a fake URL is added to resend the verification email. We will solve it later.

Update the register () view Function

Now Simply put:

return redirect(url_for("main.home"))

To:

return redirect(url_for("user.unconfirmed"))

Therefore, after the verification email is sent, the user enters the/unconfirmed path.

Create decorator

# project/decorators.py from functools import wraps from flask import flash, redirect, url_forfrom flask.ext.login import current_user def check_confirmed(func): @wraps(func) def decorated_function(*args, **kwargs): if current_user.confirmed is False:  flash('Please confirm your account!', 'warning')  return redirect(url_for('user.unconfirmed')) return func(*args, **kwargs)  return decorated_function

Here we use a basic function to check whether the user has verified it. If not verified, the user enters the/unconfirmed path. In the "project" directory, save this as decorators. py.

Now describe the profile () view function:

@user_blueprint.route('/profile', methods=['GET', 'POST'])@login_required@check_confirmeddef profile(): ... snip ...

Make sure to import the decorator:

from project.decorators import check_confirmed

Update navigation.html Template

Last, update the subsequent part of the navigation.html template --

Put:

 
 
    {% if current_user.is_authenticated() %}
  • Profile
  • {% endif %}

To:

 
 
    {% if current_user.confirmed and current_user.is_authenticated() %}
  • Profile
  • {% elif current_user.is_authenticated() %}
  • Confirm
  • {% endif %}

This is another test!

Second test

Open the application and register it with the email address that can be logged in again. (You can delete the old user you registered from the database and use it again.) Now, after registration, the user will be transferred to http: // localhost: 5000/unconfirmed.

Make sure that the http: // localhost: 5000/profile path is tested. This will redirect you to http: // localhost: 5000/unconfirmed.

Verify the email and you will have the full page permission. Go and get it!

Resend email

Finally, we will re-Send the link. Add the following view function to project/user/views. py:

@user_blueprint.route('/resend')@login_requireddef resend_confirmation(): token = generate_confirmation_token(current_user.email) confirm_url = url_for('user.confirm_email', token=token, _external=True) html = render_template('user/activate.html', confirm_url=confirm_url) subject = "Please confirm your email" send_email(current_user.email, subject, html) flash('A new confirmation email has been sent.', 'success') return redirect(url_for('user.unconfirmed'))

Now update the unconfirmed.html template:

{% extends "_base.html" %} {% block content %} Welcome!

You have not confirmed your account. Please check your inbox (and your spam folder) - you should have received an email with a confirmation link.

Didn't get the email? Resend.

{% endblock %}

Third test

You know the content of this drill. This time, make sure to re-send a new confirmation email to test the link. It should be okay.

Finally, what happens if you send several verification messages to yourself? Is each letter valid? Test it. Register a new user and send new verification emails. Verify the first one. Is it okay? It should be okay. Is that all right? Do you think other emails should be invalid if new emails are sent?

Investigate such issues. Test other Web applications you are using. How do they deal with such behavior?

Update test suite

Okay. This is the main function. How about updating the current test suite? Because of it, well, there is a problem.

Run the test:

$ python manage.py test

You will see the following error:

TypeError: __init__() takes at least 4 arguments (3 given)

To correct it, you only need to update the setUp () method in project/util. py:

def setUp(self): db.create_all() user = User(email="ad@min.com", password="admin_user", confirmed=False) db.session.add(user) db.session.commit()


Before the test, in tests/test_models.py, comment out the test_user_registration () test, because we do not want to send an email for this test.

Test it now. It should be passed all-around!

Conclusion

You can also do more:

  • Rich Text vs. plain text email-all emails should be sent.
  • Reset Password email-these should be sent when the user forgets the password.
  • User management-the user should be allowed to update the email and password. If the email changes, re-verify it.
  • Test -- more tests need to be written to involve more new functions, including test_user_registration () updated with mock/patch to prevent actual email sending.
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.