Flask-RESTPlus-CSRF is a fork of Flask-RESTPlus that adds support for CSRF tokens.
Because Flask-RESTPlus-CSRF is just a small set of changes on top of
the original Flask-RESTPlus, and is intended to be a drop-in
replacement, we have left the original package's name unchanged in the
Python namespace. That is, this CSRF-enabled version is also
installed as Flask-RESTPlus, and therefore cannot coexist with the
original Flask-RESTPlus within the same project.
In this document, we will usually refer to this overall package as "Flask-RESTPlus-CSRF" (except when we're talking about the original package). However, the actual installation commands will still use just "flask-restplus".
Flask-RESTPlus is an extension for Flask that adds support for quickly building REST APIs. Flask-RESTPlus encourages best practices with minimal setup. If you are familiar with Flask, Flask-RESTPlus should be easy to pick up. It provides a coherent collection of decorators and tools to describe your API and expose its documentation properly using Swagger.
Browsers "do" things by making web requests. Users declare their intentions quite broadly: "fetch me the New York Times homepage" or "do an image search for 'cute cats'". The browser then issues dozens of web requests to fetch text, formatting, and instructions. Those requests happen without user interaction or specific authorization.
A users's browser authenticates to a website, say FederalBank.com. That browser now has permission to act with the user's permission. From FederalBank.com's perspective, anything the browser does is done by the user, including the automated requests needed to fulfill broadly-expressed user needs.
This is all well and good. It's how the web is meant to work, and it mostly works pretty well. Users, though, visit many different sites, and in the absence of security measures (like CORS and CSRF), any of them might ask your browser to issue a request to any site. The site might treat those requests as being intentionally issued by a user, even though that user did not authorize them or even know about them.
The security goal of this project is to prevent such unauthorized requests from one site to another. We do not want to allow forged requests that borrow a browser's authentication cookies for unauthorized purposes.
There are two main approaches to addressing this problem. The first is CORS, which is beyond the scope of this exercise. The second is via issuance of CSRF tokens to visitors.
The workflow for CSRF tokens is that, upon login, a site generates a random token. On the server, it remembers this token along with the user's session information. It inserts this token into the javascript of the webpage it serves to its user. It does not set the CSRF token in cookies. In this manner, the user has a copy of the CSRF token in the site's webpage, but that token is not available from anyplace else. The server can treat requests submitted without the token as unauthenticated.
For third parties that might want to issue requests while disguised as this user, they would first have to obtain the token. Even if CORS policy allowed it, they could not simply use another site to cause the browser to make the request. The other site wouldn't have the needed token.
This brings us to the CSRF workflow:
-
On login, choose a CSRF token. Store it with the session data. It is good for a set amount of time or until logout. On every request, generate a new token with a similar duration of validity.
-
Insert the token into the javascript of pages generated by the site. This makes the token available to scripts on the page.
-
Design the webpage's scripts to include the token in authenticated requests.
-
On the server side, reject API requests that require authentication but do not provide one of the valid tokens.
-
On logout, invalidate all the tokens, typically by destroying the server's copy of them.
Flask-RESTPlus-CSRF requires Python 3.6.3. Yes, that's a pretty specific version of python. We're open to other versions, but that's the one we need right now, so we're focused on it.
You can install Flask-RESTPlus-CSRF with pipenv:
$ pipenv install -e git+https://github.com/OpenTechStrategies/flask-restplus-csrf@master#egg=flask-restplus-csrf
With Flask-RESTPlus-CSRF, you only import the api instance to route and document your endpoints.
from flask import Flask
from flask_restplus import Api, Resource, fields
app = Flask(__name__)
api = Api(app, version='1.0', title='TodoMVC API',
description='A simple TodoMVC API',
)
ns = api.namespace('todos', description='TODO operations')
todo = api.model('Todo', {
'id': fields.Integer(readOnly=True, description='The task unique identifier'),
'task': fields.String(required=True, description='The task details')
})
class TodoDAO(object):
def __init__(self):
self.counter = 0
self.todos = []
def get(self, id):
for todo in self.todos:
if todo['id'] == id:
return todo
api.abort(404, "Todo {} doesn't exist".format(id))
def create(self, data):
todo = data
todo['id'] = self.counter = self.counter + 1
self.todos.append(todo)
return todo
def update(self, id, data):
todo = self.get(id)
todo.update(data)
return todo
def delete(self, id):
todo = self.get(id)
self.todos.remove(todo)
DAO = TodoDAO()
DAO.create({'task': 'Build an API'})
DAO.create({'task': '?????'})
DAO.create({'task': 'profit!'})
@ns.route('/')
class TodoList(Resource):
'''Shows a list of all todos, and lets you POST to add new tasks'''
@ns.doc('list_todos')
@ns.marshal_list_with(todo)
def get(self):
'''List all tasks'''
return DAO.todos
@ns.doc('create_todo')
@ns.expect(todo)
@ns.marshal_with(todo, code=201)
def post(self):
'''Create a new task'''
return DAO.create(api.payload), 201
@ns.route('/<int:id>')
@ns.response(404, 'Todo not found')
@ns.param('id', 'The task identifier')
class Todo(Resource):
'''Show a single todo item and lets you delete them'''
@ns.doc('get_todo')
@ns.marshal_with(todo)
def get(self, id):
'''Fetch a given resource'''
return DAO.get(id)
@ns.doc('delete_todo')
@ns.response(204, 'Todo deleted')
def delete(self, id):
'''Delete a task given its identifier'''
DAO.delete(id)
return '', 204
@ns.expect(todo)
@ns.marshal_with(todo)
def put(self, id):
'''Update a task given its identifier'''
return DAO.update(id, api.payload)
if __name__ == '__main__':
app.run(debug=True)
See https://github.com/OpenTechStrategies/flask-restplus-csrf-demo/ for an example usage.
When doing development, you can link your webserver to this cloned version:
$ pipenv install -e $PATH/TO/THIS/LIBRARY
Documentation for the original Flask-RESTPlus is hosted on Read the Docs.