This video is available to students only

User Registration

`FLASK_APP="yumroad:create_app" flask shell`

Logins

A key component of SaaS applications is user authentication. Authentication is used to show the right content to a user, enforce permissions, or to ensure a valid subscription exists for that user.

Authentication & Cookies

Before we dive into authentication, lets take a brief look at how authentication happens in other websites so we can see what we will need to build into our Flask application.

When you login to a website like Google or Facebook on your browser, here's a simplified overview of what happens

  1. You visit example.com/login which shows you a login form
  2. The form submits your user name and password as part of a POST request to example.com/login
  3. The web server checks if the issued credentials are valid. If so, it finds out which user the credentials belongs to and then when responding to the POST request, it also issues a token to the browser that indicates that the request was authorized for a specific user ID. This "token" is the "cookie" that the browser stores.
  4. In any future requests to the same website, the browser will include the cookie to the web server. When the browser looks at the cookie that the browser included with the request, it can find what user it corresponds to and render the appropriate content.
  5. If there are no cookies or the cookie is invalid and the page requested requires a login, the application might issue a redirect to the login page.

Our approach to logins in Flask will look similar. As we've seen for other parts of our web application, the Flask framework handles the complex parts (issuing and parsing cookies) for us, but leaves what to do with the cookies to us. To handle the rest of these steps, we will use a library called Flask-Login as well as SQLAlchemy for the database side and WTForms for the login forms.

User Model

In order to authenticate users, we need have a record of their information like their email and (hashed) password details in our database.

In yumroad/models.py, create a model called User with a required email field and a password field where we can store a hashed version of their password.

class User(db.Model)
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), nullable=False)
    password = db.Column(db.String(255), nullable=False)

Never store passwords in plain text You should always hash passwords before storing them. Flask-Login makes this easy to do.

We want to store hashed passwords and instead of hashing them every place we might create an User, we can create a single class level method in the User class that is responsible for creating users.

from werkzeug.security import generate_password_hash

class User(db.Model)
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), nullable=False)
    password = db.Column(db.String(255), nullable=False)

    @classmethod
    def create(cls, email, password):
        hashed_password = generate_password_hash(password)
        return User(email=email.lower().strip(), password=hashed_password)

Now within our Flask shell, we can "create" a user. Our database currently does not have a User table, so we will have to reset the database to have SQLAlchemy recreate the database with a table for both Products and Users.

To start our shell, run the following command to start a Flask shell:

$ FLASK_APP="yumroad:create_app" flask shell

>>> from yumroad.extensions import db
>>> from yumroad.models import User
>>> db.drop_all() # To drop all of records and tables
>>> db.create_all() # To create the tables again
>>> test_user = User.create("[email protected]", "test")
>>> db.session.add(test_user)
>>> db.session.commit()
>>> test_user.id
1

Now that we have a user record created, we can build the actual login functionality into our application.

Configuration

Setting up Flask Login

After adding Flask-Login to our requirements.txt file and then installing it with pip install -r requirements', our first task, just like the other Flask extension libraries will be to initialize the library in our extensions.py file and then pass it our app object in the create_app function. We're using the LoginManager from Flask-Login which will let us tell if a user is logged in or not, and let our code login or log out a user.

In yumroad/extensions.py:

from flask_login import LoginManager

login_manager = LoginManager()

In yumroad/__init__.py

from yumroad.extensions import (csrf, db, login_manager)

def create_app(environment_name='dev'):
    ...
    login_manager.init_app(app)
    ...

Sign up

Now that we have our Flask-Login configuration setup, we can move on to creating a form for users to sign up. In forms.py, lets add a form with three fields: a user email, password, and a password confirmation. We want the password field to show up in the HTML with input type of password so that the characters are hidden, so we'll use the PasswordField provided by wtforms.

from wtforms import StringField, PasswordField, validators

class SignupForm(FlaskForm):
    email = StringField('Email')
    password = PasswordField('Password')
    confirm = PasswordField('Confirm Password')

Start a new discussion. All notification go to the author.