The method of verifying the registered user's email in the Python flask framework

Source: Internet
Author: User
Tags sqlite browser
This tutorial details how to verify their email address during the user registration process.





On the workflow, a confirmation letter will be sent to the user after registering for a new account. Until the user has completed the "verification" in the message, their account will remain in an "unverified" state. This is the workflow that most Web applications use.



One of the important things about this is what permissions do unauthenticated users have? Or do they have full access to your app, or are they restricted or have no permissions at all? For the app in this tutorial, unauthenticated users who sign in to a page will be alerted that they can enter the app only if they have verified the account.



Before we start, a lot of the features we want to add are flask-users and flask-security extensions--the problem is, why not just use these two extensions? Well, first of all, it's a learning opportunity. At the same time, both extensions have limitations, such as supported databases. What if you want to use RETHINKDB?
Here we go



Flask Basic Registration



We are going to start a flask sample, which includes the basic user registration. Get the code base from this GitHub repository. Once you have created and activated the VIRTUALENV, run the following command to get started 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





With the app running, visit the Http://localhost:5000/register page and register for a new user. Note that the app will automatically log in and guide you to the main page after registration. Take a look and then run the code--especially the user's Blueprint (Blueprint is a concept of flask).



Stop the server when finished.



Update Current App



Model



First, let's add the Confirmed field to the user model in our 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





Notice how this area defaults to "False". Also add a confirmed_on field, which is a DateTime. I want to include this datetime in order to analyze the difference between registered_on and confirmed_on dates with queue analysis.



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



Control commands



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


@manager.command
def 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()





Be sure to import datetime. Now, let's 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 function



Finally, before we register the 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 to the following:


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





Do you understand? Think about why the confirmed default to False.



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






Well, this is my newly registered user, michael@realpython.com is not verified. Let's verify it.



Add Email verification



Generating a validation token



Message validation should include a special URL that allows the user to verify his or her account simply by clicking on it. Ideally, this URL should look like this –http://yourapp.com/confirm/. The key here is its ID. In this ID, use the itsdangerous package to encode the user's message (including the timestamp).



To create a file called project/token.py, 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





So in the Generate_confirmation_token () function, a token is generated by Urlsafetimedserializer with the email address that is obtained when the user registers. The _ Real _email was 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--one hours (3,600 seconds) in effect--as a parameter. As long as the token does not expire, it will return an email.



In your app's configuration (Baseconfig ()), make sure to add Security_password_salt:


Security_password_salt = ' My_precious_two '





Update 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 resolve the email verification:


@user_blueprint.route('/confirm/')
@login_required
def 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 the project/user/views.py. Again, make sure that these imports are updated:



Import datetime
Now we call the Confirm_token () function from the token. If successful, we update the user, change the Email_confirmed property to True, and set the DateTime to the time the validation occurred. Also, if the user has already done the verification process-and has verified-we want to remind the user of this.



Create an email template



Next, add a basic email template:


welcome! Thanks for signing up. Please follow the link to activate the Your account:

{{Confirm_url}}

cheers!




Save this in "Project/templates/user" as a activate.html. This uses a simple variable called Confirm_url, which is created in the Register () view function.





Send mail



Create a basic function to send mail with a little help from the Flask-mail already installed and set in project/__init__.py.



To create a file called email.py:


# 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.



So, we simply have to deal with the recipient list, the subject, the template. We'll handle the settings for the mail configuration at 1.1.



In the project/user/views.py (again!) ) Update 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)





Also add the following import module:



From Project.email import Send_email
We are here to integrate everything together. The basic function of this function is as a controller (direct or indirect):


    • Process Initial Registration,
    • Generates a token and a confirmation URL,
    • Send confirmation email,
    • Quick verification,
    • User log in,
    • Change the user.


Did you notice the _external=true parameter? This adds the full URL that contains the hostname and port (in our case, http://localhost:5000).



Before we test this, we're going to set the email settings.



Mail



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 sign up for a test Gmail account. The environment variables are then temporarily set 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 out.
Start testing Now!



First Test



Open the app and transfer to Http://localhost:5000/register. Then register with the email address you can log in to. Well, you should receive an email that looks like this:






Click on the URL and you will be transferred to Http://localhost:5000/. Ensure that the user is in the database, that the ' Confirmed ' field is true, that there is a datetime and confirmed_on field bound together.



Good!



Handling licenses



If you remember, at the beginning of the tutorial, we decided that "unauthenticated users can log in but they will immediately be transferred to a page-what we call the/unconfirmed path-reminding users that they need to verify their account to use the app."



So, we're going to--


    • Add/unconfirmed Path
    • Add unconfirmed.html Template
    • Update register () view function
    • Creating adorners
    • Update navigation.html templates
    • Add/unconfirmed Path


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


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





You've seen similar code, so let's go ahead.



Add unconfirmed.html Template


welcome!

You don't have a confirmed your account. Please check the your inbox (and your spam folder)-You should has received an e-mail with a confirmation link.

Didn ' t get the email? Resend.

{% Endblock%}





In "Project/templates/user", save this as unconfirmed.html. This should be straightforward. Now, in order to resend the verification email, only a fake URL is added. We're going to fix it next.



Update register () view function



Now simply put in:


Return Redirect (Url_for ("Main.home"))





Become:


Return Redirect (Url_for ("user.unconfirmed"))





Therefore, after sending the verification email, the user will enter the/unconfirmed path.



Creating adorners


# project/decorators.py
 
from functools import wraps
 
from flask import flash, redirect, url_for
from 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 is authenticated. If not verified, the user enters the/unconfirmed path. In the "Project" directory, save this as decorators.py.



Now decorate the profile () View function:


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





Ensure that the adorner is imported:


From project.decorators import check_confirmed





Update navigation.html templates



Finally, update the next section of the navigation.html template--



Put


 
  

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





Become:


 
  

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





It's time to test again!



Second Test



Open the app and register again with the email address you can login to. (You can delete the old user you previously registered from the database, so you can use it again) now, the registration will be transferred to Http://localhost:5000/unconfirmed.



Make sure to test the http://localhost:5000/profile path. This will bring you to http://localhost:5000/unconfirmed.



Verify your email and you'll have access to the full page. Go on, Get it!



Send email again



Finally, to do a re-send link. Add the following view function to project/user/views.py:


@user_blueprint.route('/resend')
@login_required
def 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:


welcome!

You don't have a confirmed your account. Please check the your inbox (and your spam folder)-You should has received an e-mail with a confirmation link.

Didn ' t get the email? Resend.

{% Endblock%}





Third-time Test



You know the drill, this time promise to resend a new confirmation email, test link. There should be no problem.



Finally, if you send yourself several verification letters, what happens? Does every seal work? Test it for a moment. Register a new user and send some new verification letters. Verify the first Test. Is it OK? It should be possible. Is that OK? Do you think that if the new mail, the other email should be invalid?



Do some research on this kind of thing. Test the other web apps you use. How do they deal with this kind of behavior?



Update test Suite



Good. This is for the main function. How do we update the current test suite? Because of it, um, there's a problem.



To 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, just 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 testing, in tests/test_models.py, comment on the test_user_registration () test, because we don't want to send a message for this test really.



Now test it again. It should be all-powerful!



Conclusion



You can also do more:


    • Rich Text vs. plain text email--should all be mailed.
    • Reset Password email--These should be sent when the user forgets the password.
    • User management-should allow users to update email and password, when the email has changed, it should be re-verified again.
    • Testing-more tests need to be written to involve more new features, including Test_user_registration () updated with Mock/patch, to prevent the actual email from being sent.
  • 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.