FlaskSimpleAuth Recipes
Here are a few task-oriented recipes with FlaskSimpleAuth. Feel free to submit your questions!
Installation
How-to install FlaskSimpleAuth?
FlaskSimpleAuth is a Python module available from PyPI.
Install it, for instance with pip
, with the required dependencies:
pip install FlaskSimpleAuth[password,jwt,cors,redis]
Authentication
For details, see the relevant authentication section in the documentation.
How-to configure basic authentication?
HTTP Basic authentication is a login and password authentication encoded in
base64 into an Authorization
HTTP header.
set
FSA_AUTH
tobasic
or to containbasic
, or have a route with aauth="basic"
parameter.register a
get_user_pass
hook.simple authentication routes are triggered with
authorize="AUTH"
How-to configure parameter authentication?
This is login and password authentication passed as HTTP or JSON parameters.
set
FSA_AUTH
toparam
or to containparam
, or have a route with aauth="param"
parameter.the name of the expected two parameters are set with
FSA_PARAM_USER
andFSA_PARAM_PASS
.register a
get_user_pass
hook.simple authentication routes are triggered with
authorize="AUTH"
How-to configure token authentication?
It is enabled by default if FSA_MODE
is a scalar.
It can be enabled explicitely with FSA_MODE
as a list which
contains token
.
If you do not really need JWT compatibility, keep the
default fsa
token type (FSA_TOKEN_TYPE
) which is human readable, unlike JWT.
How to disable token authentication?
set
FSA_AUTH
to the list of authentication schemes, which must not containtoken
.set
FSA_TOKEN_TYPE
toNone
.
How-to get the current user login as a string?
There are several equivalent options:
use a special
CurrentUser
parameter type on a route to retrieve the user name.call
app.current_user()
on an authenticated route.call
app.get_user()
on any route, an authentification will be attempted.
How-to get the current user as an object?
You must build the object yourself, based on the string user name.
with a function of your making:
def get_user_object(): return UserObject(app.current_user())
for convenience, this function can be registered as a special parameter associated to the type:
app.special_parameter(UserObject, lambda _: get_user_object()) @app.route("/...", authorize="AUTH") def route_dotdotdot(user: UserObject) ...
How-to store login and passwords?
Passwords must be stored as cryptographic salted hash, so as to deter hackers from recovering passwords too easily of the user database is leaked.
FlaskSimpleAuth provides state-of-the-art settings by default using bcrypt
with 4 rounds (about 2⁴ hash calls) and ident 2y
. Keep that unless you really
have a strong opinion against it.
The number of rounds is kept as low as allowed by the library because
cryptographic password functions are very expensive, eg with 12 rounds,
which is passlib
default, results in several 100 ms computation time,
which for a server is astronomically high.
Method app.hash_password(…)
applies hashes the passwords according to the
current configuration. The get_user_pass
hook will work as expected if it
returns such a value stored from a previous call.
How-to implement my own authentication scheme?
You need to create a callback to handle your scheme:
create a function which returns the login based on the app and request:
def xyz_authentication(app, req): # investigate the request and return the login or None for 401 # possibly raise an ErrorResponse with fsa.err(…) return ...
register this callback as an authentication scheme:
app.authentication("xyz", xyz_authentication)
use this new authentication method in
FSA_AUTH
or maybe on some route with anauth="xyz"
parameter.
How-to use LDAP/AD authentication?
The AD password checking model is pretty strange, as it requires to send the clear password to the authentication server to check whether it is accepted. To do that at the library level:
create a new password checking function:
def check_login_password_with_AD_server(login: str, password: str) -> bool|None: import ldap # connect to server... send login/pass... look for result... return ...
on
True
: the password is acceptedon
False
: it is noton
None
: 401 (no such user)if unhappy: raise an
ErrorResponse
exception withfsa.err(...)
register this hook
app.password_check(check_login_password_with_AD_server)
you do not need to have a
get_user_pass
hook if this is the sole password scheme used by your application.
Alternatively, this could be implemented at the application level with one route which checks the credentials and provides a token which will be used afterwards:
create the token route:
# this route is open in the sense that it takes charge of checking credentials @app.post("/token-ad", authorize="OPEN", auth="none") def get_token_ad(username: str, password: str): if not check_login_password_with_AD_server(username, password): fsa.err("invalid AD credentials", 401) return {"token": app.create_token(username)}, 201
use
FSA_AUTH_DEFAULT="token"
so that all other routes require the token.
How-to use multi-factor authentication (MFA)?
The idea is to rely on an intermediate token with a temporary realm to validate that an authenfication method has succeeded, and that another must still be checked.
create a route with the first auth method, eg a login/password basic authentication.
@app.get("/login1", authorize="AUTH", auth="basic") def get_login1(user: fsa.CurrentUser): # trigger sending an email or SMS for a code send_temporary_code_to_user(user) # 10 minutes token provided with this basic authentication return app.create_token(user, realm="login1", delay=10.0), 200
create a route protected by the previous token, and check the email or SMS code provided by the user at this stage.
@app.get("/login2", authorize="AUTH", auth="token", realm="login1") def get_login2(user: fsa.CurrentUser, code: str): if not check_code_validity(user, code): return "invalid validation code", 401 # else return the final token return app.create_token(user), 200
only allow token authentication on other routes, eg with
FSA_AUTH_DEFAULT = "token"
.
See MFA demo.
How to ensure that no route is without authentication?
With belt and suspenders:
do not use
authorize="OPEN"
on any route.use an explicit
FSA_AUTH
list setting withoutnone
.use
FSA_AUTH_DEFAULT="token"
.
How-to … authentication?
Parameters
How-to use pydantic or dataclasses in requests and responses?
Pydantic and dataclass classes are well integrated both for input parameters and route outputs:
request parameters work out of the box with through JSON:
import pydantic # this also works with pydantic and standard dataclasses class User(pydantic.BaseModel): login: str firstname: str lastname: str @app.post("/users", authorize="ADMIN") def post_users(user: User): # user.login, user.firtname, user.lastname… return "", 201
responses must be processed through FlaskSimpleAuth’s
jsonify
:@app.get("/users/<uid>", authorize="ADMIN") def get_users_uid(uid: int): user: User = whatever(uid) return fsa.jsonify(user), 200
See types demo.
How-to use generic types in requests and responses?
Just use them! They are converted from/to JSON, but for list[*]
with HTTP
parameters are expected to be repeated parameters.
@app.get("/generic", authorize="OPEN")
def get_generic(data: dict[str, list[int]]):
# data["foo"][0]
return {k: len(v) for k, v in data.items()}
The generic type support is not perfect, consider using data classes instead. Simple standard types are expected, they cannot be mixed with data classes in general, although some instances may work.
How-to … parameters?
Miscellaneous
How-to allow non-TLS connections?
The default configuration emphasizes security, so non TLS connections are
rejected, unless running on localhost for tests.
To allow non-TLS connections, set FSA_SECURE = False
and feel deeply ashamed
to have done that.
How-to get an anwer?
Submit your question here!
How-to contribute?
Implement a feature, improve the code, fix a bug… and submit a pull request.