FlaskSimpleAuth Documentation
This modules helps handling authentication, authorizations, parameters and
provide other utils for Flask, controled
from Flask configuration and the extended route
decorator.
Contents
-
Schemes (basic, param, token…)
Parameters http, json and file parameters are managed with type hints.
Utils such as
Reference
wrapper, Error Handling and Miscellaneous Directives.See Also Flask-Security, Flask-RESTful, Flask-AppBuilder, Flask-Login and others.
Motivation
FlaskSimpleAuth is designed to help an application REST API back-end development. The features provided in this module point at fixing what I deem Flask lack of helpfulness and wrong style, such as:
not providing a simple and effective declarative security layer, which must address both authentication and authorization possibly in connection to application data, thus requires some kind of integration;
not providing a clean way to put authentication in the configuration only, where it belongs, and taking into account password management best practices.
not providing any real help with handling parameters, which is demonstrated by the fact that the user must find them in different dictionaries depending on where they come from, and having to access them through ugly global proxies such as
request
.
The resulting module allows clean application development which only has to focus on handling routes depending on cleanly typed parameters, with declarative security on each one, reducing both line counts and code complexity.
An emphasis is also put on performance by providing caching where it matters,
so this is not only about style:
Many hooks are provided to be able to take full control of various
features, with reasonable defaults which make this less a necessity.
Many key features rely on proven third-party packages such as passlib
,
PyJWT
or flask-cors
.
This module does not care much about web and application oriented features: it is a Flask framework extension which aims at better handling HTTP-related features. It could be used as a cleaner layer for other Flask application-oriented extensions such as Flask-Security.
Install
Use pip install FlaskSimpleAuth
to install the module, or whatever
other installation method you prefer.
Depending on options, the following modules should be installed:
ProxyPatternPool for sharing.
cachetools and CacheToolsUtils for caching. Possibly pymemcache and redis for external back-ends.
bcrypt for password hashing (default algorithm), argon2-cffi for password hashing, passlib for other password management, PyJWT for JSON Web Token (JWT), cryptography for pubkey-signed JWT.
Flask HTTPAuth for
http-*
authentication options.Flask CORS for CORS handling.
These modules are installed with the corresponding options: password
, jwt
,
memcached
, redis
, httpauth
, cors
: pip install FlaskSimpleAuth[jwt]
.
Sharing and cache-related modules are currently always installed, even if they are unused or may be desactivated.
Initialization
The module is simply initialize by calling its Flask
constructor and providing
a configuration through FSA_*
directives (from a separate file or directly
in the constructor).
import FlaskSimpleAuth as fsa
app = fsa.Flask("acme", FSA_MODE="debug")
app.config.from_envvar("ACME_CONFIG")
Once initialized, app
behaves as a standard Flask object with many additions.
The main change is the route
decorator, an extended version of Flask’s own
with an authorize
parameter and transparent management of request parameters.
Per-method shortcut decorators post
, get
, put
, patch
and delete
which support the same extensions.
The security first principle means that if the parameter is missing the route
is closed with a 403.
@app.get("/store", authorize="OPEN")
def get_store(filter: str = None):
# return store contents, possibly filtered
...
@app.post("/store", authorize="contributor")
def post_store(data: str):
# append new data to store, return id
...
@app.get("/store/<id>", authorize="OPEN")
def get_store_id(id: int):
# return data corresponding to id
...
Inside a request handling function, additional methods on app
give access to
authentication-dependent data, for instance:
get_user
extracts the authenticated user or raise an exception, andcurrent_user
gets the authenticated user if any, orNone
. It can also be requested as a parameter with theCurrentUser
type.user_scope
checks if the current token-authenticated user has some authorizations.hash_password
andcheck_password
hash or check a password.create_token
computes a new authentication token for the current user.check_user_password
recheck a password for a user.
Various decorators/functions allow to register hooks, such as:
user_in_group
,group_check
,get_user_pass
andobject_perms
functions/decorators to register authentication and authorization helper functions:a function to retrieve the password hash from the user name.
a function which tells whether a user belong to some group.
a function which tells whether a user is in a provided group or role.
functions which define object ownership and access permissions.
password_quality
a function/decorator to register a function to check for password quality.password_check
a function/decorator to register a new password checker, so as to handle recovery codes, for instance.cast
a function/decorator to register new str-to-some-type casts for function parameters.
# return password hash if any (see with FSA_GET_USER_PASS)
# None means that the user does not exists
@app.get_user_pass
def get_user_pass(user: str) -> str|None:
return ...
# return whether user belong to some group (see with FSA_GROUP_CHECK)
@app.group_check("admin")
def user_is_admin(user: str) -> bool:
return ...
# or alternative catch-all dynamic approach
# return whether user is in group (see with FSA_USER_IN_GROUP)
@app.user_in_group
def user_in_group(user: str, group: str) -> bool:
return ...
# return whether user can access the `foo` object for an operation
# None will generates a 404
@app.object_perms("foo")
def allow_foo_access(user: str, fooid: int, mode: str) -> bool|None:
return ...
These hooks allow taking over control of most internal processes, if needed.
Authentication
The main authentication configuration directive is FSA_AUTH
which governs the
authentication methods used by the get_user
function, as described in the
following sections. Defaut is httpd
.
If a non-token scheme is provided, authentication will be token
followed by the provided scheme, i.e. token
is tried first anyway if
enabled.
To take full control of authentication schemes, provide an ordered list. Note that it does not always make much sense to mix some schemes, e.g. basic and digest password storage assumptions are distinct and should not be merged. Also, only one HTTPAuth-based scheme can be active at a time.
Authentication is always performed on demand, either to check for a route
authorization declared with authorize
or when calling get_user
.
The authentication scheme attempted on a route can be altered with the auth
(or authn
) parameter added to the route
decorator.
This may be used to restrict or change the authentication scheme on a route.
Some combinations may or may not work depending on module internals, so this is
only for special cases.
A legitimate use for a REST API is to have FSA_AUTH
defined to token and
have only one password-authenticated route to obtain the token used by all other
routes.
Authentication Schemes
Authentication schemes are enabled with mandatory directive FSA_AUTH
.
The available schemes are:
none
No authentication. This is necessary for opening routes (
authorize="OPEN"
).httpd
Inherit web server supplied authentication through
request.remote_user
.There are plenty authentication schemes available in a web server such as Apache or Nginx, including LDAP or other databases, all of which probably more efficiently implemented than python code, so it should be the preferred option. However, it could require significant configuration effort compared to the application-side approach.
basic
HTTP Basic password authentication, which rely on the
Authorization
HTTP header in the request.FSA_REALM
provides the authentication realm.
See also Password Management below for how the password is retrieved and checked.
http-basic
Same as previous based on flask-HTTPAuth.
FSA_REALM
provides the authentication realm.FSA_HTTP_AUTH_OPTS
allow to pass additional options to the HTTPAuth authentication class.
param
HTTP or JSON parameter for password authentication. User name and password are passed as request parameters.
FSA_PARAM_USER
is the parameter used for the user name. Default isUSER
.FSA_PARAM_PASS
is the parameter used for the password. Default isPASS
.
See also Password Management below for the password is retrieved and checked.
password
Shortcut to try
basic
thenparam
authentication.http-digest
ordigest
HTTP Digest authentication based on flask-HTTPAuth.
Note that the implementation relies on sessions, which may require the
SECRET_KEY
option to be set to something. The documentation states that server-side sessions are needed because otherwise the nonce and opaque parameters could be reused, which may be a security issue under some conditions. I’m unsure about that, but I agree that client-side cookie sessions are strange things best avoided if possible.FSA_REALM
provides the authentication realm.FSA_HTTP_AUTH_OPTS
allow to pass additional options to the HTTPAuth authentication class, such asuse_ha1_pw
, as a dictionary.
See also Password Management below for how the password is retrieved and checked. Note that password management is different for digest authentication because the simple hash of the password or the unhashed password itself is needed for the verification.
token
Only rely on signed tokens for authentication. A token certifies that a user is authenticated in a realm up to some time limit. The token is authenticated by a signature which is usually the hash of the payload (realm, user and limit) and a secret hold by the server.
There are two token types chosen with the
FSA_TOKEN_TYPE
configuration directive:fsa
is a simple compact readable custom format, andjwt
RFC 7519 standard based on PyJWT implementation.The
fsa
token syntax is:<realm>:<user>:<limit>:<signature>
, for instance:comics:calvin:20380119031407:4ee89cd4cc7afe0a86b26bdce6d11126
. The time limit is a simple UTC timestamp YYYYMMDDHHmmSS that can be checked easily by the application client. Compared tojwt
tokens, they are short and easy to interpret and compare manually, no decoding is involved. If an issuer is set (seeFSA_TOKEN_ISSUER
below), the name is appended to the realm after a/
.The following configuration directives are available:
FSA_TOKEN_TYPE
type of token, either fsa, jwt orNone
to disable. Default is fsa.FSA_TOKEN_CARRIER
how to transport the token: bearer (Authorization
HTTP header), param, cookie or header. Default is bearer.FSA_TOKEN_NAME
name of parameter or cookie holding the token, or bearer scheme, or header name. Default isAUTH
for param carrier,auth
for cookie carrier,Bearer
for HTTP Authorization header (bearer carrier),Auth
for header carrier.FSA_REALM
realm of authentication for token, basic or digest. Default is the simplified lower case application name. For jwt, this is translated as the audience. The application realm may be overriden on a route for creating MFA stages.FSA_TOKEN_ISSUER
the issuer of the token. Default is None.FSA_TOKEN_SECRET
secret string used for validating tokens. Default is a system-generated random string containing 256 bits. This default will only work with itself, as it is not shared across server instances or processes.FSA_TOKEN_SIGN
secret string used for signing tokens, if different from previous secret. This is only relevant for public-key jwt schemes (R…
,E…
,P…
). Default is to use the previous secret.FSA_TOKEN_DELAY
number of minutes of token validity. Default is 60.0 minutes.FSA_TOKEN_GRACE
number of minutes of grace time for token validity. Default is 0.0 minutes.FSA_TOKEN_ALGO
algorithm used to sign the token. Default isblake2s
forfsa
andHS256
for jwt.FSA_TOKEN_LENGTH
number of hash bytes kept for token signature. Default is 16 forfsa
. The directive is ignored forjwt
.FSA_TOKEN_RENEWAL
for cookie tokens, the fraction of delay under which the cookie/token is renewed automatically. Default is 0.0, meaning no renewal.
Function
create_token(user)
creates a valid token for the user depending on the current scheme and detailed configuration. Ifuser
is not given, the current user is taken. Other parameters can be overriden, such asrealm
,delay
,issuer
,secret
… to allow full control if necessary, eg for MFA.Token authentication is always attempted unless the secret is empty. Setting
FSA_AUTH
totoken
results in only token authentication to be used.Token authentication is usually much faster than password verification because password checks are designed to be slow so as to hinder password cracking, whereas token authentication relies on simple hashing for its security. Another benefit of token is that it avoids sending passwords over and over. The rational option is to use a password scheme to retrieve a token and then to use it till it expires. This can be enforced by setting
FSA_AUTH
totoken
and to only addauth="basic"
on the login route used to retrieve a token.Token expiration can be understood as a kind of automatic logout, which suggests to choose the delay with some care depending on the use case.
Internally token checks are cached so that even with slow JWT public-key schemes the performance impact should be low.
Note: it is possible to switch from an expiration model (the token tells when it expires) to a validity model (the token tells when it was generated) by setting
FSA_TOKEN_DELAY
to zero andFSA_TOKEN_GRACE
to the amount of time a token should be considered valid.oauth
Synonymous to
token
, but to be used on a route so as to trigger JWT scope authorizations on that route.See also OAuth Authorizations below for how to use JWT token scopes.
http-token
Token scheme based on flask-HTTPAuth. Carrier is bearer or header.
FSA_HTTP_AUTH_OPTS
passes additional options to the HTTPAuth authentication class, such asheader
, as a dictionary.
fake
Trust a parameter for authentication claims. Only for local tests, obviously. This is enforced.
FSA_FAKE_LOGIN
is the parameter holding the user name. Default isLOGIN
.
Other authentication schemes can be added by registering a new hook early in the application initialization. This hook:
is passed the application and request
returns the authenticated login as a str, or None
may raise an
ErrorResponse
if really unhappy
@app.authentication("code")
def code_authentication(app: Flask, req: Request) -> str|None:
...
@app.get("/code-authentication", authorize="AUTH", auth="code")
def get_code_authentication(user: CurrentUser):
return f"Hello code-authenticated {user}!", 200
The FSA_AUTHENTICATION
configuration directive is a dictionary which can be
used for the same purpose.
By default, authentication is performed when required on a route by trying
all enabled schemes in turn.
This can be changed by setting FSA_AUTH_DEFAULT
to a subset of FSA_AUTH
,
in which case other schemes will only be tried if set explicitely on a route
with auth=…
.
Password Management
Password authentication is performed for the following authentication schemes:
param
, basic
, http-basic
, http-digest
, digest
, password
.
The provided password management comprises handling password verification
in the application, relying on standard password hashing schemes and
a user-provided function to retrieve the password hash (get_user_pass
),
and/or delegating the whole verification process to a user-provided function
(password_check
).
For checking passwords internally, the password (salted hash) must be retrieved
through get_user_pass(user)
.
This function must be provided when the module is initialized.
Because this function is cached by default, the cache expiration must be reached
so that changes take effect, or the cache must be cleared manually, which may
impair application performance.
The following configuration directives are available to configure passlib
password checks:
FSA_PASSWORD_SCHEME
password provider and scheme to use for passwords. There are two providers:fsa
(internal) andpasslib
.Default is
bcrypt
. Set to None to disable internal password checking.The internal
fsa
provider supportsbcrypt
andscrypt
(with a dependency to their eponymous package),argon2
(withargon2-cffi
),plaintext
(do not use) anda85
andb64
obfuscated plaintext (do not use either).Further documentations:
bcrypt for
bcrypt
options.argon2 for
argon2
options.scrypt for
scrypt
options, to whichsaltlength
is added by the interface.passlib for available options, including the bad plaintext.
Note: a list of schemes can also be provided, in which case it is passed as-is to passlib.
FSA_PASSWORD_OPTS
relevant options (forpasslib.CryptContext
). Default is ident 2y with 4 rounds.
Beware that modern password checking is often pretty expensive in order to thwart password cracking if the hashed passwords are leaked, so that you do not want to have to use that on every request in real life (eg hundreds milliseconds for passlib bcrypt 12 rounds). The above defaults result in manageable password checks of a few milliseconds. Please consider using tokens to reduce the authentication load on each request.
For digest
authentication, the password must be either in plaintext or a
simple MD5 hash (RFC 2617).
The authentication setup must be consistent (set use_ha1_pw
as True for the
later).
As retrieving the stored information is enough to steal the password (plaintext)
or at least impersonate a user (hash), consider avoiding digest
altogether.
HTTP Digest Authentication only makes sense for unencrypted connexions, which
is bad practice anyway. It is just provided here for completeness.
Function hash_password(pass)
computes the password salted digest compatible
with the current configuration, and may be used by the application for setting
or resetting passwords.
This function checks the password quality by relying on:
FSA_PASSWORD_LENGTH
minimal password length, 0 to disable.FSA_PASSWORD_RE
list of regular expressions that a password must match.FSA_PASSWORD_QUALITY
hook function which returns whether the password is acceptable, possibly raising an exception to complain if not. This hook can also be filled with thepassword_quality
method/decorator. It allows to plug a password strength estimator such as zxcvbn.
This application-managed standard password checking can be overridden by providing an alternate password checking function with a directive:
FSA_PASSWORD_CHECK
hook function which returns whether user and password provided is acceptable. This allows to plug a LDAP server or a temporary password recovery scheme or other one-time or limited-time passwords sent by SMS or mail, for instance. This hook can also be filled with thepassword_check
method/decorator. This alternate check is used if the primary check failed or is disactivated.
An opened route for user registration with mandatory parameters could look like that:
@app.post("/register", authorize="OPEN")
def post_register(user: str, password: str):
if user_already_exists(user):
return f"cannot create {user}", 409
add_new_user_with_hashed_pass(user, app.hash_password(password))
return "", 201
Because password checks are usually expensive, it is advisable to switch
to token
authentication. A token can be created on a path authenticated
by a password method:
# token creation route for all registered users
@app.get("/login", authorize="AUTH")
def get_login():
return jsonify(app.create_token()), 200
The client application will return the token as a parameter or in headers for authenticating later requests, till it expires.
Multi-factor authentication (MFA) is supported by generating intermediate tokens on distinct realms at different stages, as discussed in recipes and illustrated in the demo.
Note that route-dependent realms do not work with http-*
authentications
because the realm is frozen by the external implementation in this case.
Also, The same shared secret is used to validate all realms.
Parameters
Request parameters (HTTP or JSON) are translated automatically to named function parameters by relying on function type annotations. Parameters are considered mandatory unless a default value is provided.
@app.get("/something/<id>", authorize=...)
def get_something_id(id: int, when: date, what: str = "nothing"):
# `id` is an integer path-parameter
# `when` is a mandatory date HTTP or JSON parameter
# `what` is an optional string HTTP or JSON parameter
...
Request parameter string values are actually converted to the target type,
and generate a 400 if the configuration fails.
For int
, base syntax is accepted for HTTP/JSON parameters, i.e. 0x11
,
0o21
, 0b10001
and 17
all mean decimal 17.
For bool
, False is an empty string, 0
, False
or F
, otherwise
the value is True.
Type path
is a special str
type which allows to trigger accepting
any path on a route.
Type JsonData
is a special type to convert, if necessary, a string value
to JSON, expecting a list or a dictionary.
If one parameter is a dict of keyword arguments, all remaining request parameters are added to it, as shown below:
@app.put("/awesome", authorize="AUTH")
def put_awesome(**kwargs):
...
Pydantic-generated classes and dataclasses work out of the box both with HTTP and JSON parameters.
class Search(pydantic.BaseModel):
words: list[str]
limit: int
# or
@dataclass
class Search:
words: list[str]
limit: int
@app.get("/search", authorize="OPEN")
def get_search(q: Search):
...
Generic types can be used, although with restrictions: only combinations of
list
and str
-key dict
of standard Python types are supported and checked.
Using custom classes may or may not work, consider using data classes instead.
@app.get("/question", authorize="OPEN")
def get_question(q: list[str]):
...
Note that list[*]
when using HTTP parameters are assumed to be repeated
parameters, not one parameter with a list value.
Custom classes can be used as parameter types, provided that the constructor accepts a string (for HTTP parameters) or whatever value provided (for JSON) to build the expected type.
class EmailAddr:
def __init__(self, addr: str):
self._addr = addr
@app.get("/mail/<addr>", authorize="AUTH")
def get_mail_addr(addr: EmailAddr):
...
Defining new types can be used to factor out some parameter checks, for instance requiring a positive integer:
class nat(int): # this demonstrate Python simplicity
def __new__(cls, val):
if val < 0:
raise ValueError(f"nat value must be positive: {val}")
return super().__new__(cls, val)
@app.get("/pos", authorize="OPEN")
def get_pos(i: nat, j: nat):
# i and j are positive integers
...
If the constructor does not match, a custom function can be provided with the
cast
function/decorator and will be called automatically to convert
parameters:
class House:
...
@app.cast(House)
def strToHouse(s: str) -> House:
return ...
# or: app.cast(House, strToHouse)
@app.get("/house/<h>", authorize="OPEN")
def get_house_h(h: House)
...
The FSA_CAST
directive can also be defined as a dictionary mapping
types to their conversion functions:
FSA_CAST = { House: strToHouse, ... }
As a special case, the Request
, Session
, Globals
, Environ
,
CurrentApp
, CurrentUser
, Cookie
, Header
and
FileStorage
types, when used for parameters,
result in the request
, session
, g
flask special objects, environ
WSGI
parameter, the current authenticated user, the current application, the cookie value,
the header value or a special file parameter (for upload) to be passed as this
parameter to the function, allowing to keep a functional programming style by
hidding away these special proxies.
More special parameters can be added with the special_parameter
app
function/decorator, by providing a type and a function which is given the
parameter name (usually useless, but not always) and returns the expected value.
For instance, the Request
and FileStorage
definitions roughly correspond to:
app.special_parameter(Request, lambda _: request)
app.special_parameter(FileStorage, lambda p: request.files[p])
An example use-case is to make user-related data easily available through such a special type:
class User:
def __init__(self, login: str):
self.firstname, self.lastname = db.get_user_data(login=login)
app.special_parameter(User, lambda _: User(app.current_user()))
@app.get("/hello", authorize="AUTH")
def get_hello(user: User):
return f"Hello {user.firstname} {user.lastname}!", 200
The FSA_SPECIAL_PARAMETER
directive can also be defined as a dictionary
mapping types to their parameter value function.
Python parameter names can be prepended with a _
, which is ignored when
translating HTTP parameters. This allows to use python keywords as parameter
names, such as pass
or def
.
@app.put("/user/<pass>", authorize="AUTH")
def put_user_pass(_pass: str, _def: str, _import: str):
...
Finally, configuration directive FSA_REJECT_UNEXPECTED_PARAM
tells whether to
reject requests with unexpected parameters.
Default is True.
Utils
Utilities include the Reference
generic object wrapper class,
error handling utilities and
miscellaneous configuration directives which cover security,
caching and CORS.
Reference
Object Wrapper
This class provides a proxy object based on the Proxy
class
from ProxyPatternPool.
This class implements a generic share-able global variable which can be used by modules (eg app, blueprints…) with its initialization differed.
Under the hood, most methods calls are forwarded to a possibly sub-thread-local object stored inside the wrapper, so that the Reference object mostly behaves like the wrapped object itself.
See the module for a detailed documentation.
Error Handling
Raising the ErrorResponse
exception with a message, status, and possibly
headers and content type from any user-defined function generates a Response
of this status with the text message as contents.
The following directives provide convenient configuration about error handling:
FSA_ERROR_RESPONSE
sets the handler for generating actual responses on errors. Text values plain or json generate simpletext/plain
orapplication/json
responses. Using json:error generates a JSON dictionary with key error holding the error message. The response generation can be fully overriden by providing a callable which expects the error message, status code, headers and content type as parameters. This handler can be restricted to apply only to FSA-generated errors, seeFSA_HANDLE_ALL_ERRORS
below. Default is plain.FSA_HANDLE_ALL_ERRORS
whether to handle all 4xx and 5xx errors, i.e. take full responsability for generating error responses using FSA internal error handler (seeFSA_ERROR_RESPONSE
above). Default is True. When set to False, some errors may generate their own response in any format based on Flask default error response generator.FSA_KEEP_USER_ERRORS
whether to refrain from handling user errors and let them pass to the outer WSGI infrastructure instead. User errors are intercepted anyway, traced and raised again. They may occur from any user-provided functions such as various hooks and route functions. Default is FalseFSA_SERVER_ERROR
controls the status code returned on the module internal errors, to help distinguish these from other internal errors which may occur. Default is 500.FSA_NOT_FOUND_ERROR
controls the status code returned when a permission checks returns None. Default is 404.
A possible pattern is to put checks, say about parameters, in a function
which raises the ErrorResponse
exception to control client-facing results,
and to call this function for checking these parameters:
def check_foo_param(foo):
if db_has_no_foo(foo):
raise ErrorResponse(f"no such foo: {foo}", 404)
if this_foo_is_confidential(foo, app.current_user()):
raise ErrorResponse(f"no way foo: {foo}", 403)
@app.get("/foo/<fid>", authorize="AUTH")
def get_foo_fid(fid: int):
check_foo_param(fid)
...
Such checks can also be performed in an object constructor, with the inconvenience that the objects become specific to the HTTP context.
Miscellaneous Configuration Directives
Some directives govern various details for this extension internal working.
FSA_MODE
set module mode, expecting prod, dev, debug to debug4. This changes the module verbosity. Under dev or debug,FSA-*
headers is added to show informations about the request, the authentication and the elapsed time from the application code perspective. Default is prod.FSA_LOGGING_LEVEL
adjust module internal logging level. Default is None.FSA_SECURE
only allows secured requests on non-local connections. Default is True.FSA_LOCAL
sets the internal object isolation level. It must be consistent with the module WSGI usage. Possible values are process, thread (several threads can be used by the WSGI server), werkzeug (may work with sub-thread level request handling, eg greenlets), gevent and eventlet. Default is thread.FSA_ADD_HEADERS
allows to add headers to the generated response, as a dictionary. Keys are header names and values are either strings, which are used as is, or functions which are called with the response as a parameter to generate a value. None returned values are silently ignored. The correspondingadd_headers
method allows to add headers as keyword arguments. Default is empty.FSA_DEFAULT_CONTENT_TYPE
allows to replace the defaulttext/html
header added for string or byte responses. Default is None, meaning no replacement.FSA_JSON_STREAMING
allows to forcejsonify
to generate a string instead of a string generator when serializing a generator. Default True is to stream the JSON output, which may interact badly with the database transactions in some cases depending on the driver and WSGI server.FSA_BEFORE_REQUEST
andFSA_AFTER_REQUEST
allow to add a list of before and after request hooks from the configuration instead of the actual application code. As a slight deviation from Flask before request hook, before request functions are passed the current request as an argument. They are executed first (just after some internal initializations and before application-provided before request hooks) and last, respectively. Defaults are empty.FSA_BEFORE_EXEC
allows to add a list of functions called after all preprocessing and just before the actual execution of the route function. The hooks are passed the request, the login and the authentication scheme. It may return a response to shortcut the route function. Such functions can also be register with thebefore_exec
function/decorator. Defaults are empty.
Some control is available about internal caching features used for user authentication (user password access and token validations) and authorization (group and per-object permissions):
FSA_CACHE
controls the type of cache to use, set to None to disallow caches. Values for standardcachetools
cache classes arettl
,lru
,tlru
,lfu
,mru
,fifo
,rr
plusdict
. MemCached is supported by setting it tomemcached
, and Redis withredis
. Default isttl
. The directive can also be set to aMutableMapping
instance to take direct control over the cache.FSA_CACHE_OPTS
sets internal cache options with a dictionary. This must contain the expected connection parameters forpymemcache.Client
and forredis.Redis
redis, for instance. Forredis
andttl
, an expiration ttl of 10 minutes is used and can be overwritten by providing thettl
parameter.FSA_CACHE_SIZE
controls size of internalcachetools
caches. Default is 262144, which should use a few MiB. None means unbounded, more or less.FSA_CACHE_PREFIX
use this application-level prefix, useful for shared distributed caches. A good candidate could beapp.name + "."
. Default is None, meaning no prefix.Method
clear_caches
allows to clear internal process caches. This is a mostly a bad idea, you should wait for thettl
.Methods
password_uncache
,token_uncache
,user_token_uncache
,group_uncache
,object_perms_uncache
andauth_uncache
allow to remove a particular entry from the shared cache, without waiting for its eventual expiration.Note that uncaching features are on a best-effort basis, and may not work as expected especially with multi-process or multi-level cache settings. You should really wait for the
ttl
…
Web-application oriented features:
FSA_401_REDIRECT
url to redirect to on 401. Default is None. This can be used for a web application login page.FSA_URL_NAME
name of parameter for the target URL after a successful login. Default isURL
if redirect is activated, else None. Currently, the login page should use this parameter to redirect to when ok.FSA_CORS
andFSA_CORS_OPTS
control CORS (Cross Origin Resource Sharing) settings.CORS is a security feature implemented by web browsers to prevent JavaScript injection. It checks whether a server accepts requests from a given origin (i.e. from JavaScript code provided by some domain).
CORS request handling is enabled by setting
FSA_CORS
to True which allows requests from any origin. Default is False. Additional options are controled withFSA_CORS_OPTS
. The implementation is simply delegated to theflask_cors
Flask extension which must be available if the feature is enabled.
See Also
The following links point to alternative to FlaskSimpleAuth, which may be a better match depending on the use case.
Flask-Security
Flask-Security is a feature-full web-oriented authentication and authorization framework based on an ORM. By contrast, Flask Simple Auth:
does NOT assume any ORM or impose a data model, you only have to provide callback functions to access the needed data (password, groups, object permissions…).
does NOT do any web-related tasks (forms, views, templates, blueprint, translation…), it just helps providing declarative security layer (role or object permissions) to an HTTP API, well integrated into Flask by extending the existing
route
decorator.does provide a nice integrated parameter management to Flask, including conversions and type checks, detecting missing parameters…
does care about performance by providing an automatic and relevant caching mechanism to expensive authentication and authorization checks, including relying on external stores such as redis.
provides simple hooks to extend features, such as adding a password strength checker or a password alternate verifier.
is much smaller (about 1/10th, ignoring dependencies), so probably it does less things!
Flask-RESTful
Flask-RESTful is a Flask extension designed to ease developping a REST API by associating classes to routes, with class methods to handle each HTTP method. By contrast, Flask Simple Auth:
does NOT propose/impose a method/class for each route.
does provide a simpler parameter management scheme.
integrates cleanly authentification and authorizations, including handling 404 transparently. Our implementation of the doc example is shorter (32 vs 40 cloc), elegant and featureful.
Flask-AppBuilder
Flask-AppBuilder is yet another Flask web-application framework on top of Flask and SQLAlchemy. By contrast, Flask Simple Auth:
does NOT impose an ORM or database model.
keeps close to Flask look and feel by simply extending the
route
decorator, instead of adding a handful of function-specific ones, which is error-prone as some may be forgotten.has a simpler and direct yet more powerful parameter management framework based on type declarations instead of additional decorators and specially formatted comments.
offers an integrated authorization scheme linked to application objects.
Flask-Login
Flask-Login is yet another web-oriented Flask helper to manage logins and logouts using Flask and an underlying session management. It does not help much with actual authentication though, and does nothing about authorizations. By contrast, Flask Simple Auth:
does NOT impose a user model.
does NOT require enabling session management.
does NOT require any additional decorator to protect routes.
does actually provide a consistent authentication, authorization and parameter management framework.
Others
FlaskSimpleAuth is a Flask extension, however:
you do not have to use Flask for your back-end! Other (less popular) HTTP frameworks in the Python ecosystem include: CherryPy, Django, Falcon, FastAPI, Pylon, Pyramid…
you do not have to use Python for your back-end! Other languages with more-or-less convenient HTTP frameworks include: C Ulfius, C++ Oat++, C# ASP.NET, Go Gin, Java Spring, JavaScript Node.js, Perl Dancer2, PHP Lumen, Ruby Rails, Rust Rocket, Scala Play, SQL PostgREST…
License
This software is public domain.
All software has bug, this is software, hence… Beware that you may lose your hairs or your friends because of it. If you like it, feel free to send a postcard to the author.
Versions
Sources, documentation and issues are hosted on GitHub. Install package from PyPI.
See all versions.
TODO
Todo or not todo…
Authn/Authz
demo LDAP auth? One class plus a new
check_password
?add
any
token scheme?use
authlib
?test
FSA_HTTP_AUTH_OPTS
?password re could use a dict for providing an explanation?
how to have several issuers and their signatures schemes?
add
issuer
route parameter? seerealm
.declare scopes per domain?
Params
FSA_PARAM_STYLE
any/http/json to restrict/force parameters? being lazy is not too bad?check for bad char in parameter names
allow handling files in kwargs
add a filter on returned value?
make_response
?
Features
how to add a timeout? or manage an outside one?
logging
default behavior is a pain and the maintainer is self satisfied. how to ensure that logging is initialized?the doc and implementation should clarify exception handling, and possible overrides.
add ability to catch and process any user error. what about Flask?
json mode: generate json in more cases? automatically?
declare some exceptions to be turned into 400 instead of 500? currently this can be done below, eg anodb, maybe this is enough?
Software Engineering
reduce sloc?
check for more directive types (dynamically)?
add app.log?
take advantage of
TypedDict
?
Documentation
more recipes?
include demo? point to demo?
comparisons with other frameworks
use
FlaskTester
in tutorial?add
POST /token
to tutorial?