Ihr Ingenieurbüro für

Elektronik- und Softwareentwicklung

Flangular - A Flask based Angularjs application with authentication


Veröffentlicht:   |   Weitere Einträge über www flask angularjs

Using Flask as backend for Angularjs applications seems natural. But how does it integrate, especially when it comes to authentication, private areas and so on.

This Proof of Concept started with the following goals:

  • Not serving the Angular code of the private area to the public (not logged in users)
  • Using HTTP Header based authentication for REST requests to prevent from CSRF

The result is called Flangular, a working Flask app made out of the following core components:

The Application Layout

The first objective was to not serve the private application code to the public. So the first problem arises:

How to make a single page application with a login form which reloads the private code components after a successfull login?

Since Angularjs doesn't support lazy loading it seems to be not possible to provide the user with a login prompt and reload the private code components after a successful login on a single page. The Solution is to split the application into two sites:

  • One for logging in and out
  • One for the actual SPA.

The second objective was to prevent from CSRF. Apparently we need two different kinds of authentication:

  • A HTTP header based CSRF preventing one for the REST interface.
  • A cookie based for serving the private code fragments.

This is the directory layout to separate the different areas of access:

flangular
  |--flangular
       |--private
       |--static
       |--templates

We split our static web content into three directories:

static
Public web content. This directory can directly be served by the webserver on deployment. In our case it will contain all the 3rd party libs managed by bower.
private
Private web content only available to authenticated users.
templates
HTML templates. The access can be mangaged individually by the corresponding routes.

The Server Implementation

So how do we implement such an authentication system. There are plenty of examples or Flask extensions out there. But they all seem to target more conventional server based web programming. After spending some time evaluating exsiting solutions I decided to implement it myself.

The Authentication

To let users authenticate with the system we create a User ORM class with that we can verify user credentials. Then we create a route to let users authenticate:

@app.route('/login', methods=('POST',))
def login():
    user = User.login(request.form['email'], request.form['password'])
    if user is not None:
        g.user = user
        return make_response(redirect_back('index'))

    flash('Could not log in.')
    return render_template('login.html')

When a user has successfully authenticated then he is stored on the g object for the rest of the request.

The User class can generate authentication tokens. Theses tokens are encrypted and save to pass as cookie to the browser. But the tokens are only valid for a limited time span. If this time span exceeds the session times out. For an in detail explanation see Miguel's Blog. So we need to refresh the tokens after each request to let the session not expire when the user is active.

To do so we add an application global after_request hook where we refresh the token and append it as a cookie:

@app.after_request
def after_request(resp):
    user = g.get('user', None)
    if user is not None:
        # refresh token
        token = user.generate_auth_token()
        resp.set_cookie('XSRF-TOKEN', token.decode('ascii'));

    return resp

You may have recognized the special cookie name XSRF-TOKEN. We will use Angular's $http service on the client side. This service is actually pretty handy because it looks for a cookie with that name. If it detects such a cookie $http will append a special HTTP header to its XHR requests: X-XSRF-TOKEN which then contains our auth token. Nice!

That's already pretty much everything that's needed. We now only need to verify the token either from the X-XSRF-TOKEN header to be CSRF save or just from the cookie to serve static content:

def user_valid(cookie=False):
    if cookie:
        token = request.cookies.get('XSRF-TOKEN')
    else:
        token = request.headers.get('X-XSRF-TOKEN')

    if token is None:
        return None

    user = User.verify_auth_token(token)
    g.user = user
    return user

This could be turned into a decorator but parameterized decorators are pretty messy so I left it as it is.

As log out mechanism we simply use the login form. If a logged in user requests the login form he will be automatically logged out by deleting the cookie:

@app.route('/login', methods=('GET',))
def login_form():
    resp = make_response(render_template(
            'login.html', next=get_redirect_target()))

    # Reset session information to log user out, since the login page functions
    # also as logout page.
    g.user = None
    resp.set_cookie('XSRF-TOKEN', '', expires=0);
    return resp

Limiting the Access

Now that we have our authentication in place we can start to limit the user's access based on if they are logged in or not. Let's start with our directory layout from above. We need to limit access to our private directory to serve our private application code only to authenticated users. Like already mentioned we have to use the cookie based, not CSRF save method. That's actually not a problem since CSRF attacks do only make sense to trigger some kind of action in the targeted application which is not the case by just downloading static content.

So let's use our user_valid(...) function to protect the private folder:

@app.route('/priv/<path:filename>')
def private_static(filename):
    if user_valid(cookie=True):
        return send_from_directory(PRIVATE_FOLDER, filename)
    else:
        abort(401)

To restrict access to our Flask-Restless API we subclass our base model class and add a before_request hook. This way all published models can simply subclass our model class and override the before_request hook if they need to:

class Model(db.Model):
    # ...
    @classmethod
    def before_request(cls):
        if not core.user_valid(cookie=False):
            abort(401)

That was the server part. Now we can log in and out and have everything restricted like desired.

The Client Implementation

Our Angular application is split up into 3rd party libs in the static directory and our private code in the restricted private folder. For our actual implementation we simply drop our files into the private folder.

As Demo application we create a simple Computer model to perfom basic CRUD on. We will implement a list view with pagination and a detail view to edit and create Computer instances.

The Model is represented as follows:

class Computer(Model):
    processor = db.Column(db.Unicode)
    ram = db.Column(db.Unicode)
    power = db.Column(db.Unicode)

If we are logged in we can query our model with the $http service without caring about authentication which happens transparently in the background:

$http.get('/api/computer').then(function(resp) {
    console.log(resp.data);
});

In fact I decided not to use ngResource because it didn't worked out that well with Flask-Restless.

Thanks to Flask-Restless the pagination can be implemented pretty easily. Models are paginated by default. Let's see the GET response:

GET /computer?page=1
{
  "num_results": 2,
  "objects": [
    {
      "id": 1,
      "power": "100 W",
      "processor": "XEON",
      "ram": "512 MB"
    },
    {
      "id": 2,
      "power": "0.3 W",
      "processor": "ARM Cortex M3",
      "ram": "8 KiB"
    }
  ],
  "page": 1,
  "total_pages": 1
}

The List View

So far so good but let's wrap this into our own service to be a little more convenient. I'd like some kind of collection with methods for pagination attached. An object which can be placed in scope and paginated without further interaction required. For implementation detail see the collection service implemented in private/flangular.js in the GitHub Repository.

function listController($scope, collection) {
    $scope.model = collection('computer');
    $scope.model.load();
}

This model can now be used in the template to render a paginated table. We use UI-Bootstrap for the paginator which can now be easily wired together:

<pagination total-items="model.count"
            ng-change="model.load()"
            ng-model="model.currentPage">
</pagination>

<table>
  <thead>
    <tr>
      <th>#</th>
      <th>Processor</th>
      <th>RAM</th>
      <th>Power</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="row in model.objects" ng-click="edit(row.id)">
      <td>{{ row.id }}</td>
      <td>{{ row.processor }}</td>
      <td>{{ row.ram }}</td>
      <td>{{ row.power }}</td>
    </tr>
  </tbody>
</table>

The Detail View

For manipulating single instances we create another service to provide us with convenient instance methods save and remove: We call it instance.

One could think about adding these methods to the instances already when querying them for the collection but since we are using UI-Router for navigation, instances have to be queried based on the URL parameter anyway.

We just take the instance object from the XHR call and attach the methods:

GET /api/computer/2
{
  "id": 2,
  "power": "0.3 W",
  "processor": "ARM Cortex M3",
  "ram": "8 KiB"
}

How does it look like?

function detailController($scope, $stateParams, instance) {
    $scope.model = instance('computer');
    $scope.model.$select($stateParams.id);
}

The simplified detail form template:

<form novalidate>
    <input type="text" ng-model="model.processor">
    <input type="text" ng-model="model.ram">
    <input type="text" ng-model="model.power">
</form>

TODO

There is error handling missing for the collection and instance services. If the session expires the application silently stops working. There needs to be some kind of notification implemented. Maybe with UI-Bootstrap's $modal.

Remarks

I'm actually an embedded guy and happy if you have improvements especially for the javascript part. Fork it on GitHub.

References

Miguel Grinberg's Blog
Flangular is heavily based on his authentication suggestions.
GitHub Repository
If you like to fork, contribute or just download the code.
Comments powered by Disqus