Computer scienceBackendFlaskUser-specified interactions

Sign up

7 minutes read

Introduction

Have you ever found yourself lost in the labyrinth of long and complex sign-up forms, asking more questions than a quiz show host, only to abandon it midway through? Or perhaps you've noticed how linking social accounts like Google or Apple makes the sign-up process a breeze?

Sign up pages are vital to any website or application as they convert visitors into users. While the ease of creating an account is essential, ensuring it's secure and reliable is just as important. That's the focus of this topic - exploring how to create such sign-up pages using Flask.

Overview

Sign-up forms, also known as registration forms, are web pages that allow visitors to create an account on an application by providing their personal details such as name, email address, and password.

sign-up page

The process of creating a sign-up form in Flask can be divided into four main categories. By concentrating on these categories, we can effectively implement forms ranging from the simplest to the most complex in Flask.

sign-up page parts

Now, let's start by breaking down each of these steps using an example to make things clearer.

The Conversation Starter - Forms

Installing the necessary packages: pip install Flask-WTF, pip install email_validator

Forms are the primary interaction point between the user and a web application. This is where users input their data that the application then processes. Not only crucial for gathering user information, they also validate it before storing it in a database.

We will use the Flask-WTF extension for handling forms in our Flask application. Flask-WTF offers a convenient way to create and validate forms. This ensures the collected data is relevant and secure, enhancing the user experience and the efficiency of data processing in the application.

Let's see how we can do this:

First, let's create a form for user registration. This form will include fields for the user's name, email, and password. We’ll also add a confirmation field for the password to verify the user has entered the correct password.

from flask import Flask
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo

app = Flask(__name__)
app.config["SECRET_KEY"] = "super-secret-key"


class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

Here, validators are used to examine the data users input. Flask-WTF provides several built-in validators, such as DataRequired which checks if a field is submitted empty, and Email which validates the email address after installing the package email_validator. Additionally, EqualTo checks if two fields are the same, useful for confirming passwords as shown above. When the validate_on_submit method is invoked on the form, it runs all these validators and if all data is valid, it returns True.

The DataRequired validator ensures that form fields are not left blank, and even checks for "falsey" values such as None, False, 0, [], etc. If any of these values are found, it will raise an error. However, if you wish to allow these values as valid inputs in any of your forms, the InputRequired validator should be used instead.

You may notice we set a secret key for our app at the beginning. This is necessary to prevent cross-site request forgery (CSRF) attacks aided by the Flask_WTF package.

Data Safe house - Database

Installing necessary packages: pip install Flask-SQLAlchemy

After collecting user data via the form, we need somewhere to store it long-term, so we don't have to ask for the details each time the user visits our site. This is the role of the database, our primary safe house for data - storing it securely and offering easy retrieval when needed.

Here, we’ll use Flask-SQLAlchemy to interact with our database:

#...
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash

#...

db = SQLAlchemy(app)
bcrypt = Bcrypt(app)

#...

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    hashed_password = db.Column(db.String(60), nullable=False)

    def set_password(self, passwd):
        self.hashed_password = generate_password_hash(passwd)
    def check_password(self,passwd):
        return check_password_hash(self.hashed_password, passwd)

You might be wondering about the werkzeug.security in the code above. It's included to ensure user data security. If our database is compromised, we don't want attackers to gain access to user passwords, which often are reused across multiple services. To prevent this, we use Werkzeug, which comes automatically installed with Flask, to hash the password before saving it in the database.

The method set_password() employs Werkzeug's generate_password_hash() function to create a salted hash value from the plaintext password. This hash value is then stored in the database rather than the plaintext password. When a user logs in, their input password is hashed using the same algorithm and compared to the stored hash value using the check_password_hash() function. If the two match, it returns True.

These, generate_password_hash() and check_password_hash(), are two important methods in our User model. As their names suggest, generate_password_hash() creates a password hash from a given string (the user's password in this case), and check_password_hash() checks if a hash was created from a specific string.

Now we can put measures in place to prevent the same email address or similar usernames being chosen when creating accounts, ensuring each user has a unique identity within our platform, maintaining the integrity and security of their information.

We achieve this using custom validators. In Flask-WTF, custom validator methods within our form class follow this syntax:

def validate_field_name(self,field_name):
    #check if field_name.data meets a certain condition
    #if not:
    raise ValidationError("this message will be shown when the user input fails to meet this validator")

We replace field_name with the name of the field to build the validator for.

In our case, for example, we simply query the user name or email from our database, and check if such user exists:

#...
from wtforms import ValidationError
#...

class RegistrationForm(FlaskForm):
    #...
    def validate_username(self, username):
        user = User.query.filter_by(username = username.data).first()
        if user is not None:
            raise ValidationError('Username already registered.')

    def validate_email(self, email):
        user =  User.query.filter_by(email = email.data).first()
        if user is not None:
            raise ValidationError('email already registered.')

Application Conductor - View Functions

View functions tie everything together, ensuring all other aspects fit together perfectly like a well-crafted puzzle. We'll build our view function in several steps, starting from a basic one, with more features added at each step.

  • Passing Forms to Templates

Templates will be covered in the next step. But for now, a template can be seen as a "sign_up.html" file located in our templates directory.

The first task our view function needs to perform is to pass the form to the template to render it correctly.

from flask import render_template

#...

@app.route("/", methods=['GET','POST'])
def sign_up():
    form = RegistrationForm()
    return render_template("sign_up.html", form=form)
  • Validating User Input

The next step is validating the user input. This vital task ensures only correct and relevant data is stored in the database, preventing potential security breaches or inconsistencies.

We accomplish this by using the built-in validate_on_submit method provided by Flask-WTF. When invoked, this method checks if the incoming request is a POST request and whether the form data passes the validation tests specified in the form definition. If successful, the method returns True;

#...
@app.route("/register", methods = ["GET","POST"])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        return redirect(url_for("login"))
    else:
        return render_template("sign_up.html", form = form)

Here, we're redirecting users who have registered to the login route. While beyond the scope of our current discussion, it's assumed it has already been constructed for this illustration.

  • Storing the data

Once we validate the user's input, we can store it permanently.

@app.route("/register", methods = ["GET","POST"])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username = form.username.data , email = form.email.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        return redirect(url_for("login"))
    else:
        return render_template("sign_up.html", form = form)

Notice that we're not directly storing the password into the User model. Instead, we're utilizing the function we created in step 2.

UI Architect - Templates

This is how we present all the work done above to the user. In our case, we're going to create a simple HTML file:

<!DOCTYPE html>
<html>
    <head>
    <title>Sign Up</title>
    </head>
    <body>
    <form action = "" method="post">
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}
            {{ form.username }}
            {% for error in form.username.errors %}
            {{ error }}
            {% endfor %}
        </p>
        <p>
            {{ form.password.label }}
            {{ form.password }}
            {% for error in form.password.errors %}
            {{ error }}
            {% endfor %}
        </p>
        <p>
            {{ form.confirm_password.label }}
            {{ form.confirm_password }}
            {% for error in form.confirm_password.errors %}
            {{ error }}
            {% endfor %}
        </p>
        <p>
            {{ form.email.label }}
            {{ form.email }}
            {% for error in form.email.errors %}
            {{ error }}
            {% endfor %}
        </p>        
        <p>
            {{ form.submit() }}
        </p>
    </form>
    </body>
</html>

The consistent naming convention of form.name.label-form.name-form.name.error helps to organize the forms and their corresponding labels and errors in a visually appealing manner.

Other Considerations

While we've covered the fundamentals of incorporating sign-up pages into Flask applications, there are additional functionalities provided by Flask extensions which facilitate their implementation. Some examples include:

Conclusion

Creating a sign-up page is critical for many websites as it allows users to join your platform or access specific features. A well-designed sign-up page should be simple, intuitive, and straightforward to navigate, and provide clear instructions and feedback to help users complete their registration process.

In this topic, we have explored building a basic example of this, emphasizing the importance of protecting user data and validating all user input to guarantee a smooth and safe user experience.

How did you like the theory?
Report a typo