This is the second post in a two-part series on Flask authentication. You should read the previous article, The Flask Authentication Problem, before continuing.
Since I started working at Stormpath, I’ve been thinking about ways to help Flask developers build simpler, more secure web applications. Partially because it’s now my job, but more importantly because this is something I’ve often needed for my own projects.
In my opinion, Flask developers really need a library that can handle the following, securely:
- User creation, authentication, and storage.
- User states (via sessions).
- Complex user permissions (groups, different tiers of users, etc.).
- Scaling applications from a single user to hundreds of millions without code changes.
- Automatic database maintenance and backups.
- Allow developers to use authentication with very little boilerplate code.
- Employ authentication best practices (HTTPS, HSTS, etc.).
Not only would the above make the lives of all Flask developers significantly easier, but it would help protect user data, while simultaneously making it simpler for developers to build scalable apps from day one, without worrying about all the other problems that go along with handling this stuff on your own.
As I was thinking about the above problems, I came to the conclusion that the best possible way to move forward was to construct a simple framework which could take care of several of these components at once.
Enter Flask-Stormpath
Meet Flask-Stormpath, my new authentication library, which aims to solve these Flask authentication issues.
The idea behind Flask-Stormpath is the following:
- User authentication and security is a difficult problem.
- Building scalable user authentication is out of scope for most open source libraries (to build scalable, redundant systems you need advanced infrastructure).
- When security issues arise in the crypto world, and when best practices change, your frameworks needs to change with them transparently (without affecting your users).
- Your user authentication should be an independent entity, and not tightly coupled to your codebase -- this way you can do all sorts of interesting things: use your user data in more than one application, build service-oriented applications, easily refactor your codebase while not breaking user functionality, etc.
A Quick Note on Stormpath
Stormpath is an API company that allows developers to securely register, manage, authenticate, and authorize users in a scalable, secure, and efficient manner. Stormpath gives you complete data ownership over your user base, and allows you to store custom user data as well -- eliminating the need for you to build your own user storage and data infrastructure.
The way Stormpath works is conceptually simple:
- If you want to securely create a new user account, you POST to Stormpath’s API.
- If you want to assign a user to a group to give them access to resources, you POST to Stormpath’s API to create a group, then you POST to Stormpath’s API to add users from that group.
- If you want to authenticate a user, you POST to Stormpath’s API.
- Want to enforce password constraints, like email verification, you click a box in the Stormpath console, and it will be automatically enforced.
To conceptualize what Stormpath does, you might want to think of it as Stripe for user management.
Back to Flask
Flask-Stormpath provides a simple, elegant interface for Flask developers to securely handle users (and their data) on the backend, through Stormpath.
The benefit to doing this is that adding authentication to a Flask application now only requires two things:
- Sign up for Stormpath.
- Plug Flask-Stormpath into your application.
This means there is no need to secure and manage user databases and servers. No need to roll your own session management, and worry about client-side data security. No need to scale your users or their data. Much less boilerplate to get your application up and running with user accounts.
Building Flask-Stormpath
Building Flask-Stormpath was actually quite easy.
Since the excellent Flask-Login library already handles session management, and provides a good base to work off, I started by writing a proper Flask-Login backend.
Fortunately, I was able to use the new Stormpath Python SDK to build a User abstraction that would make Flask-Login happy. In the end, my User model ended up being quite simple (I’ve removed a bit of the code here for simplicity’s sake):
class User(Account): def get_id(self): return unicode(self.href) def is_active(self): return self.status == 'ENABLED' def is_anonymous(self): return False def is_authenticated(self): return True @classmethod def create(self, email, password, given_name='John', surname='Doe'): _user = current_app.stormpath_manager.application.accounts.create({ 'email': email, 'password': password, 'given_name': given_name, 'surname': surname, }) _user.__class__ = User return _user @classmethod def from_login(self, login, password): _user = current_app.stormpath_manager.application.authenticate_account( login, password, ).account _user.__class__ = User return _user
The code above is nothing more than a wrapper around the Stormpath Account class, which means that (without any additional work) Flask users are able to do powerful things like add data to a user’s account, etc.
For instance, if you have a User object, and want to store a user’s birthday along with the user, doing so is trivial (thanks to Stormpath’s custom data support):
>>> user.custom_data[‘birthday’] = ‘06/28/1988’
>>> user.save() # Sync this newly added user data to Stormpath securely.
NOTE: Stormpath allows you to store up to 10MB of JSON data (nested, or otherwise) per User account, for free. On the backend, this is implemented by offloading data to a highly secured Cassandra cluster.
Next, I read through the Flask extension guidelines and built a very simple StormpathManager abstraction (with a lot of context taken from Flask-Login). This allows Flask developers to hook into Stormpath, and abstracts away all of the Flask-Login details. Again, adding this support was trivial (some pieces removed for clarity):
class StormpathManager(object): def __init__(self, app=None): self.app = app if app is not None: self.init_app(app) def init_app(self, app): app.login_manager = LoginManager(app) app.login_manager.session_protection = 'strong' app.login_manager.user_callback = self.load_user app.stormpath_manager = self app.context_processor(_user_context_processor) self.app = app @property def client(self): ctx = stack.top if ctx is not None: if not hasattr(ctx, 'stormpath_client'): if self.app.config.get('STORMPATH_API_KEYFILE'): ctx.stormpath_client = Client( api_key_file_location = self.app.config.get('STORMPATH_API_KEYFILE'), ) else: ctx.stormpath_client = Client( id = self.app.config.get('STORMPATH_API_KEY_ID'), secret = self.app.config.get('STORMPATH_API_KEY_SECRET'), ) return ctx.stormpath_client @property def application(self): ctx = stack.top if ctx is not None: if not hasattr(ctx, 'stormpath_application'): #ctx.stormpath_application = ctx.stormpath_client.applications.search( ctx.stormpath_application = self.client.applications.search( self.app.config.get('STORMPATH_APPLICATION') )[0] return ctx.stormpath_application @staticmethod def load_user(account_href): user = current_app.stormpath_manager.client.accounts.get(account_href) try: user._ensure_data() user.__class__ = User return user except StormpathError: return None
With this StormpathManager, configuring your Flask application becomes trivial:
from os import environ from flask import Flask app = Flask(__name__) app.config[‘STORMPATH_API_KEY_ID’] = environ.get(‘STORMPATH_API_KEY_ID’) app.config[‘STORMPATH_API_KEY_SECRET’] = environ.get(‘STORMPATH_API_KEY_SECRET’) app.config[‘STORMPATH_APPLICATION’] = environ.get(‘STORMPATH_APP_NAME’) stormpath_manager = StormpathManager(app)
The rest of the library contains some helper functions, context processors, etc. -- but for the most part -- by piggybacking off the excellent Flask-Login library (for session handling), and Stormpath (for user security and storage), I was able to develop a secure, scalable, and future-proof authentication library for Flask.
Flask-Stormpath Issues
Right now, Flask-Stormpath is still at a very early release. Over the coming weeks and months, I’ll be adding a great deal more features to the library, which will make it easier to do things like:
- Create complex user hierarchies.
- Handle API authentication in a simplistic way for developers.
- Support groups, and multi-tenant applications without much thought.
I’m also working on improving the library’s documentation (still very early), to better explain and educate developers about security best practices, and fully cover everything you need to know to launch a secure Flask web application.
If you have any suggestions, requests, or questions, you can check out the official Flask-Stormpath GitHub repository and open an issue.
Sample Application, and Demo
I’d also like to quickly mention that in addition to the Flask-Stormpath library, I’ve also built a very simple sample application which you can clone from GitHub and run locally -- aptly named stormpath-flask-sample (you can find it on GitHub here).
This simple sample application is a basic Flask website with:
- A home page.
- A registration page (it creates new users via email and password).
- A login page (to log existing users back into their account).
- And a dashboard page, which allows you to store custom data on your user account.
If you’re at all interested in seeing how Flask-Stormpath works, I’d highly recommend you check it out here.
And, for the curious, here are a few screenshots of what you can expect it to look like (there’s even a friendly command line bootstrapping script):