Use the Flask framework form plug-in Flask-WTF of Python to implement Web login verification, flaskflask-wtf
Form is a basic element that allows users to interact with our web applications. Flask itself does not help us process forms, but the Flask-WTF extension allows us to use popular WTForms packages in our Flask application. This package makes it easier to define the form and process the submission.
Flask-WTF
The first thing we want to do with Flask-WTF (after installing it, GitHub project page: https://github.com/lepture/flask-wtf) is to define a form in the myapp. forms package.
# ourapp/forms.pyfrom flask_wtf import Formfrom wtforms import StringField, PasswordFieldfrom wtforms.validators import DataRequired, Emailclass EmailPasswordForm(Form): email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()])
Before Flask-WTF 0.9, Flask-WTF provides its own encapsulation for WTForms fields and validators. You may see that a lot of code is imported from flask. ext. wtforms instead of TextField and PasswordField from wtforms.
After Flask-WTF 0.9, we should directly import these fields and validators from wtforms.
The defined form is a user login form. We call it EmailPasswordForm (). We can reuse this Form class to do other things, such as a registry ticket. Here we didn't define a long and useless form, but chose a very common form, just to introduce you to using Flask-WTF to define a form. A complex form may be defined in a formal project in the future. If a form contains a field name, we recommend that you use a clear name and keep it unique in a form. I have to say that for a long form, we may want to give a field name that is more in line with the above.
The login form can do something for us. It ensures the security of our applications to prevent CSRF vulnerabilities, verify user input and render appropriate tags that are the fields we define for the form.
CSRF protection and Verification
CSRF indicates cross-site request forgery. CSRF attacks refer to third-party forgery (like a form submission) requests to an application server. A vulnerable server assumes that the data from a form comes from its own website and takes corresponding operations.
For example, an email provider allows you to submit a form to delete your account. The form sends a POST request to the account_delete endpoint on the server and deletes the Logon account when the form is submitted. We can create a form on our website, which sends a POST request to the same account_delete endpoint. Now, if we ask someone to click the submit button of our form (or use JavaScript to do so), the login account provided by the email provider will be deleted. Of course, the email provider does not know that form submission does not happen on their website.
So how can I block POST requests from other websites? WTForms is possible by generating a unique token when rendering each form. The generated token will be passed back to the server. With the data in the POST request, the token must be verified by the server before the form is accepted. The key is that the token is related to a value stored in the user session (cookies) and will expire after a period of time (30 minutes by default ). This method ensures that the person who submits a valid form is the person who loads the page (or at least the person who uses the same computer), and they can only load the page within 30 minutes.
To start using Flask-WTF to protect CSRF, we need to define a view for our login page.
# ourapp/views.pyfrom flask import render_template, redirect, url_forfrom . import appfrom .forms import EmailPasswordForm@app.route('/login', methods=["GET", "POST"])def login(): form = EmailPasswordForm() if form.validate_on_submit(): # Check the password and log the user in # [...] return redirect(url_for('index')) return render_template('login.html', form=form)
If the form has been submitted and verified, we can continue the login logic. If it is not submitted (for example, a GET request), we need to pass the form object to our template so that it can be rendered. The following figure shows the template when CSRF is used for protection.
{# ourapp/templates/login.html #}{% extends "layout.html" %}{% endraw %}
{% Raw %} {form. csrf_token }{% endraw %} renders a hidden field that contains the odd CSRF tokens and will be searched for when WTForms validate the form. We don't have to worry about the logic that contains the processing token. WTForms will take the initiative to help us. Wow!
Custom Verification
Apart from the built-in form validators (such as Required () and Email () provided by WTForms), we can create our own validators. We will compile a Unique () validator to illustrate how to create your own validators. The Unique () validator is used to check the database and ensure that the value provided by the user does not exist in the database. This can be used to ensure that the user name or email address is not used. Without WTForms, we may want to do these things in the view, but now we can do something in the form itself.
Now let's define a simple registration form. In fact, this form is almost the same as the login form. Some custom validators will be added to it later.
# ourapp/forms.pyfrom flask_wtf import Formfrom wtforms import StringField, PasswordFieldfrom wtforms.validators import DataRequired, Emailclass EmailPasswordForm(Form): email = StringField('Email', validators=[DataRequired(), Email()]) password = PasswordField('Password', validators=[DataRequired()])
Now we need to add our validators to ensure that the email addresses they provide do not exist in the database. We put this validator in a new util module, util. validators.
# ourapp/util/validators.pyfrom wtforms.validators import ValidationErrorclass Unique(object): def __init__(self, model, field, message=u'This element already exists.'): self.model = model self.field = field def __call__(self, form, field): check = self.model.query.filter(self.field == field.data).first() if check: raise ValidationError(self.message)
This validator assumes that we use SQLAlchemy to define our model. WTForms expects the validators to return some callable object (for example, a callable class ).
In the \ _ init \ _ of Unique (), we can specify which parameters are passed into the validators. In this example, we need to input the relevant model (for example, in our example, the User model is passed in) and the fields to be checked. When the validators are called, if any instance that defines the model matches the value submitted in the form, it will throw a ValidationError. We can also add a message with common default values, which will be included in ValidationError.
Now we can modify EmailPasswordForm and use our custom Unique validators.
# ourapp/forms.pyfrom flask_wtf import Formfrom wtforms import StringField, PasswordFieldfrom wtforms.validators import DataRequiredfrom .util.validators import Uniquefrom .models import Userclass EmailPasswordForm(Form): email = StringField('Email', validators=[DataRequired(), Email(), Unique( User, User.email, message='There is already an account with that email.')]) password = PasswordField('Password', validators=[DataRequired()])
Rendering form
WTForms can also help us render forms into HTML representation. Fields implemented by WTForms can be rendered as HTML representation of the Field. Therefore, to render them, we only need to call the form fields in our template. This is like rendering the csrf_token field. The following is an example of a logon template in which we use WTForms to render our fields.
{# ourapp/templates/login.html #}{% extends "layout.html" %}
We can customize how to render a field and pass in the attribute of the field as a parameter to the call.
<form action="" method="post"> {{ form.email.label }}: {{ form.email(placeholder='yourname@email.com') }} <br> {% raw %}{{ form.password.label }}: {{ form.password }}{% endraw %} <br> {% raw %}{{ form.csrf_token }}{% endraw %}</form>
Process OpenID Logon
In real life, we find that many people do not know that they have some public accounts. Some big-name websites or service providers provide public account authentication for their members. For example, if you have a google account, you actually have a public account, such as Yahoo, AOL, and Flickr.
To make it easier for our users to use their public accounts, we will add the links to these public accounts to a list so that users do not have to manually enter them.
Let's define some public account service providers that provide users to a list. Put this list in the configuration file (fileconfig. py ):
CSRF_ENABLED = TrueSECRET_KEY = 'you-will-never-guess' OPENID_PROVIDERS = [ { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' }, { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' }, { 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' }, { 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' }, { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]
Next we will use this list in our logon view function:
@app.route('/login', methods = ['GET', 'POST'])def login(): form = LoginForm() if form.validate_on_submit(): flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data)) return redirect('/index') return render_template('login.html', title = 'Sign In', form = form, providers = app.config['OPENID_PROVIDERS'])
We introduced the configuration list of the public account service provider from app. config, and then introduced it as a parameter to the template through the render_template function.
As you can guess, we need to display the links of these service providers in the logon template.
<!-- extend base layout -->{% extends "base.html" %} {% block content %}<script type="text/javascript">function set_openid(openid, pr){ u = openid.search('<username>') if (u != -1) { // openid requires username user = prompt('Enter your ' + pr + ' username:') openid = openid.substr(0, u) + user } form = document.forms['login']; form.elements['openid'].value = openid}</script>
This time the template seems to have added a lot of things. Some public accounts need to provide user names. To solve this problem, we use javascript. When a user clicks the relevant public account link, the public account that requires the user name will prompt the user to enter the user name, and javascript will process the user name as an available public account, then insert it into the text box of the openid field.
The following figure is displayed after you click the google link on the logon page: