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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
.. flask-paranoid documentation master file, created by
sphinx-quickstart on Sat Jul 1 17:13:54 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
.. image:: _static/logo.png
:alt: flask-paranoid
:align: center
Flask-Paranoid is a simple extension for the Flask microframework that protects
the application against certain attacks in which the user session cookie is
stolen and then used by the attacker.
How Does It Work?
=================
When a client connects to the application for the first time, a token that
represents certain characteristics of this client is generated and stored. In
succesive requests sent by this client, this token is regenerated and compared
against the stored one. If the tokens are different, it is assumed that the
client is sending requests from a different environment than the one in which
the session was originally created, so in this case the session is destroyed
and the request rejected as a preventive measure.
By default, the token is generated from the IP address and the user agent of
the client. This means that if a user session cookie is stolen and then used
from a different location or from a different browser, the generated token
will change, and that will block this "different" request.
The idea is based on the strong session protection feature of Flask-Login, but
generalized so that it can also be used outside of the Flask-Login context.
The token generation and storage can be customized to fit different types of
applications.
Quick Start
===========
Here is a simple application that uses Flask-Paranoid to protect the user
session::
from flask import Flask
from flask_paranoid import Paranoid
app = Flask(__name__)
app.config['SECRET_KEY'] = 'top-secret!'
paranoid = Paranoid(app)
paranoid.redirect_view = '/'
@app.route('/')
def index():
return render_template('index.html')
In this example, the paranoid token computed on the initial request from a
client will be stored in the user session. If a subsequent request generates
a different token, the session will be cleared and then a redirect to the root
URL will be made, forcing the potential attacker to log in again.
Configuration
=============
The only configuration is what determines the action that is taken when an
invalid session is detected, after the session is cleared. The default is to
return a 401 error back to the client.
To redirect to a given URL, set ``paranoid.redirect_view`` to the desired
location::
paranoid.redirect_view = '/login'
Absolute URLs are also supported::
paranoid.redirect_view = 'https://www.google.com'
To redirect to a registered Flask route, set ``paranoid.redirect_view`` to the
desired endpoint name::
paranoid.redirect_view = 'index'
To redirect to a route inside a blueprint, use the same syntax used by the
``url_for()`` function::
paranoid.redirect_view = 'auth.login'
To have the extension invoke a callback function that generates the response,
use the ``paranoid.on_invalid_session`` decorator. The function should return
a valid Flask response::
@paranoid.on_invalid_session
def invalid_session():
return 'please login', 401
Using with Flask-Login
======================
Since this extension overrides the similar feature in Flask-Login, it is
recommended that when using both extensions in an application, session
protection is disabled in Flask-Login::
login_manager.session_protection = None
Flask-Paranoid detects if Flask-Login is being used, and in that case clears
the "remember me" cookie in addition to the user session when an invalid
session is detected.
Customization
=============
Flask-Paranoid allows applications to customize its inner workings through the
use of subclasses.
Changing the Token Generation Algorithm
---------------------------------------
The default implementation creates a SHA256 hash of the client IP address and
user agent.
To use a different token generation algorithm, override the ``create_token``
method::
class MyParanoid(Paranoid):
def create_token(self):
pass
This method is invoked in the context of a request, so it can access the
``request`` object to obtain information about the client.
Changing Where the Token is Stored
----------------------------------
The default implementation writes the paranoid token to
``session['_paranoid_token']``.
If a different storage mechanism is desired, override the
``write_token_to_session()`` and ``get_token_from_session()`` methods::
class MyParanoid(Paranoid):
def write_token_to_session(self, token):
pass
def get_token_from_session(self):
pass
A tricky use case is when this extension is used with an API project that
does not use the user session and instead provides authentication tokens to
clients. In such a case, the application's token generation function can be
enhanced to include the paranoid token. For example::
def get_token(username, password):
if validate_user(username, password):
return encode_jwt(claims={'username': username,
'paranoid_token': paranoid.create_token()})
And then the token storage methods can be overriden as follows::
class MyParanoid(Paranoid):
def write_token_to_session(self, token):
# nothing to do here, the paranoid token is inserted in the JWT
# by the application
pass
def get_token_from_session(self):
claims = decode_jwt(request.headers['X-API-TOKEN'])
if 'paranoid_token' not in claims:
abort(401)
return claims['paranoid_token']
In this example, the ``encode_jwt`` and ``decode_jwt`` are application
functions that work with JWT tokens. The client authenticates by passing the
JWT token in the ``X-API-TOKEN`` header.
Using a Different Session Cleanup Mechanism
-------------------------------------------
By default, this extension empties the contents of the user session, and if
Flask-Login is used, also deletes its "remember me" cookie.
To change or replace the session cleanup algorithm, override the
``clear_session()`` method::
class MyParanoid(Paranoid):
def clear_session(self, response):
pass
The ``response`` argument passed to this method is the response object that
will be returned to the client after the session has been cleaned up. Any
cookies that need to be deleted or modified must be included in this response
object.
.. toctree::
:maxdepth: 2
:caption: Contents:
|