Motivation

Angular is a powerful frontend framework for building sophisticated Single-Page Applications (SPA). Flask can be used to serve SPAs very easily as explained here. However, incoorporating common security measures that are used in SPAs needs extra effort and therefore, additional extensions are required. A very common extension is flask-security which comes with a lot of  functionality for handling authentication, authorization, CSRF protection and much more.

However, leveraging flask-security to handle SPAs is a bit tricky, since flask-security is more focused on forms and contains a rich variety of adjustments that have to get right during initialization. Therefore I want to show which adjustments must be made in order to create a secure and modern SPA using Flask and Angular.

Example application

We have implemented a simple demo application that allows bloggers to write and view news articles. User can register, log in to their account and log out. Similar to GitHub, we want to allow logging in either by username or password.
The frontend and backend logic are separated properly. The frontend code consists of HTML and JavaScript code that could be served independently from the backend code (e.g. by Nginx). The backend is a Flask REST API that only serves as data interface to the database.
 
The Angular login form look like this:
And the main form with all news articles that the author has written so far:

Flask implementation

I want to highlight the important pieces of the Flask backend that are needed for the Angular integration.

First, let’s start with the architecture of the database models. The database schema consists of models for saving users and roles:

from flask_security import UserMixin, RoleMixin, SQLAlchemyUserDatastore

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True, nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean)
    confirmed_at = db.Column(db.DateTime)
    roles = db.relationship(
        "Role", secondary=roles_users, backref=db.backref("users", lazy="dynamic")
    )


class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(40))
    description = db.Column(db.String(255))


user_datastore = SQLAlchemyUserDatastore(db, User, Role)

The UserMixin and RoleMixin provide additional functionality  for assigning roles and permissions to users. The class SQLAlchemyUserDatastore acts as a proxy for the SQLAlchemy database.

Next, a Security object from flask-security has to be created:  

    flask_wtf.CSRFProtect(app)
    Security(
        app,
        user_datastore,
        confirm_register_form=ExtendedRegisterForm,
        login_form=ExtendedLoginForm,
    )

First, CSRF protection is activated by calling flask_wtf.CSRFProtect(app). This returns a CSRF-Cookie whenever a user logs in. Angular automatically appends this cookie to every subsequent POST request. Security takes the Flask app object, the created datastore and custom forms for registration and login. We need to create custom forms because out of the box, flask-security only supports registration and login by email. Since we want to additionally support logging in by username we have to overwrite the default forms.

Special care has to be taken regarding invalid requests. flask-security returns Flask WTForms by default if the Content-Type header is text/html. This is easily observable via Postman:

Since we implemented our own forms in Angular, we don’t need these forms. So we have to make sure that requests have the correct Content-Header.

Further we want to make sure that the request method type is POST. If, for example, we use GET as request method type for the login, Flask will return a CSRF cookie although the login hasn’t succeeded. Again this can be shown in Postman:

This CSRF token is assigned to an anonymous user that has no permissions and cannot authorize to any endpoint. So in practice, this will not pose a security vulnerability but still, it is good practice to return an error message rather than an unusable CSRF token.

The views are implemented using flask-restful. For example, the endpoint used for returning one specific article:

class ArticleGetView(Resource):
    @auth_required()
    def get(self, id):
        article = db.session.query(Article).filter_by(id=id).first()
        if not article:
            return "Not found.", 404
        return {"id": article.id, "content": article.content}, 200

We showed already in other blog articles how to use this extension so there’s nothing new here. The only important piece is the @auth_required() decorator provided by flask-security. This decorator ensures that only logged in users have access to their articles.

Summary

In this blog article, we showed how flask-security can be used to serve SPAs via Angular. One important part that we left out are the configuration parameters that have to be set so that Flask and Angular can play nicely together. However, explaining them here is not necessary since the usage of these parameters is described well enough in the __init_.py file in the repo.

The source code for this project can be found here.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments