`FLASK_APP="yumroad:create_app" flask shell`
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
- You visit example.com/login which shows you a login form
- The form submits your user name and password as part of a POST request to example.com/login
- 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.
- 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.
- 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.
In order to authenticate users, we need have a record of their information like their email and (hashed) password details in our database.
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.
Setting up Flask Login
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.
from flask_login import LoginManager login_manager = LoginManager()
from yumroad.extensions import (csrf, db, login_manager) def create_app(environment_name='dev'): ... login_manager.init_app(app) ...
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
from wtforms import StringField, PasswordField, validators class SignupForm(FlaskForm): email = StringField('Email') password = PasswordField('Password') confirm = PasswordField('Confirm Password')