bull would record their email address and create a unique id for the purchase, then associate the user with the content they purchased.
It worked fantastically well. Whereas before a potential customer had to not only, enter their full name and address (both of which I had no use for), they also had to create an account on my payment processor’s site. I’m not sure how many sales I lost due to the convoluted checkout process, but I’m sure it was a good deal. With bull, the time between clicking the “Buy Now” button on the book sales page to actually reading the book was about 10 seconds. Customers loved it.
I loved it too, but for a slightly different reason: since
bull was running on my web server, I could get a much richer set of analytics than if I had to send customers to a third-party site for payment. This opened the door to a host of new possibilities: A/B testing, analytics reports, custom sales reports. I was stoked.
I decided that, at a minimum, I wanted
bull to be able to display a “Sales Overview” page that contained basic sales data: transaction information, graphs of sales over time, etc. To do that (in a secure manner), I needed to add authentication and authorization to my little Flask app. Helpfully, though, I only needed to support a single, “admin” user who was authorized to view
Luckily, as is usually the case, a third-party package already existed to handle this. Flask-login is a Flask extension that enables user authentication. All that’s required is a
User model and a few simple functions. Let’s take a look at what was required.
bull was already using Flask-sqlalchemy to create
product models which captured the information about a sale and a product, respectively. Flask-login requires a
User model with the following
- has an
is_authenticated()method that returns
Trueif the user has provided valid credentials
- has an
is_active()method that returns
Trueif the user’s account is active
- has an
is_anonymous()method that returns
Trueif the current user is an anonymous user
- has a
get_id()method which, given a
Userinstance, returns the unique ID for that object
While Flask-login helpfully provides a
UserMixin class that provides default implementations of all of these, I just defined everything required like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
Flask-login also requires you to define a “user_loader” function which, given a user ID, returns the associated user object.
1 2 3 4 5 6 7 8
@login_manager.user_loader piece tells Flask-login how to load users given an id. I put this function in the file with all my routes defined, as that’s where it’s used.
Now, I could create a
/reports endpoint that required authentication. Here’s what the code for that endpoint looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
You’ll notice that most of the code is unrelated to authentication, which is just how it should be. The function assumes, due to the decorator, the user has already been authenticated and is thus authorized to see this data. There’s only one problem: How does a user become “authenticated”?
Login and Logout
/login endpoint, of course! Both
/logout are straightforward and can be pulled almost verbatim from the Flask-login documentation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
You’ll notice we update the
User object in the database once they are authenticated. This is due to the fact that, from one request to the next, a new instance of the
User object is created each time, so we need a place to store the information that the user already authenticated. Ditto for logging out.
Creating an Admin User
Much like Django’s
manage.py, I needed a way to create a single admin user with the proper login credentials. I couldn’t just add a row to the database manually, since the password is stored as a salted hash (rather than plain-text, which would be stupid from a security standpoint). I created the following script,
create_user.py, to do just that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
Finally, I had a way to cordon off a section of the sales site to display sales data for an admin user. In my case, I only needed a single user, but Flask-login obviously supports many users at once.
The Flask Ecosystem
My ability to quickly add this functionality to the site speaks to the rich ecosystem of Flask extensions that exist. Recently, I was looking to create a web application that had, among other things, a forum. Django has all sorts of complex forum applications you can get working with a good deal of effort, but none of them work well with the authentication application I had chosen; the two applications had no reason to be coupled together, but were.
Flask, on the other hand, makes it easy to compose orthogonal applications into a larger, more complex one in much the same way functions are composed in functional languages. Take, for example, flask-forum. It uses the following Flask extensions in creating the forum:
- Flask-Admin for database management
- Flask-Assets for asset management
- Flask-DebugToolbar for debugging and profiling.
- Flask-Markdown for forum posts
- Flask-Script for basic commands
- Flask-Security for authentication
- Flask-SQLAlchemy for database queries
- Flask-WTF for forms
With a list that long, it’s almost surprising that all of the applications are able to work together without creating dependencies on one another (or, rather, it’s amazing if you’re coming from Django), but Flask extensions usually follow the Unix philosophy of “do one thing well”. I’ve yet to encounter a Flask extension that I would consider “bloated”.
While my use case and implementation were rather simple, the great part about Flask is that it makes simple things simple. If something seems like it should be easy to do and not take much time, with Flask, this is usually true. I was able to add an authenticated admin section of my payment processor in under an hour, and there was no magic involved. I knew how everything worked and fit together.
And that’s a powerful concept: no magic. While many web frameworks pride themselves on how little the developer has to do to create an application, they fail to realize that the developer understands only what he writes and uses, and they might be doing developers a disservice by hiding so much. Flask puts it all in the open, and in the process, allows the functional composition of applications that Django set out to create.