Quickstart¶
Requirements¶
- Python 3.6+
Note on Requirements¶
I do not currently plan to support older versions of python. Python 2 support is very unlikely to arrive as the original author is a die-hard believer in python 3. As for older versions of python 3, my test harnesses depend on some features only available in python 3.4 and up.
Installation¶
Note
flask-praetorian does not support distutils or setuptools because the author has very strong feelings about python packaging and the role pip plays in taking us into a bright new future of standardized and usable python packaging
Install from pypi¶
This will install the latest release of flask-praetorian from pypi via pip:
$ pip install flask-praetorian
Install latest version from github¶
If you would like a version other than the latest published on pypi, you may do so by cloning the git repository:
$ git clone https://github.com/dusktreader/flask-praetorian.git
Next, checkout the branch or tag that you wish to use:
$ cd flask-praetorian
$ git checkout integration
Finally, use poetry to install from the local directory:
$ poetry install
Example¶
A minimal example of how to use the flask-praetorian decorators is included:
import flask
import tempfile
import flask_sqlalchemy
import flask_praetorian
import flask_cors
db = flask_sqlalchemy.SQLAlchemy()
guard = flask_praetorian.Praetorian()
cors = flask_cors.CORS()
# A generic user model that might be used by an app powered by flask-praetorian
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Text, unique=True)
hashed_password = db.Column(db.Text)
roles = db.Column(db.Text)
is_active = db.Column(db.Boolean, default=True, server_default="true")
@property
def identity(self):
"""
*Required Attribute or Property*
flask-praetorian requires that the user class has an ``identity`` instance
attribute or property that provides the unique id of the user instance
"""
return self.id
@property
def rolenames(self):
"""
*Required Attribute or Property*
flask-praetorian requires that the user class has a ``rolenames`` instance
attribute or property that provides a list of strings that describe the roles
attached to the user instance
"""
try:
return self.roles.split(",")
except Exception:
return []
@property
def password(self):
"""
*Required Attribute or Property*
flask-praetorian requires that the user class has a ``password`` instance
attribute or property that provides the hashed password assigned to the user
instance
"""
return self.hashed_password
@classmethod
def lookup(cls, username):
"""
*Required Method*
flask-praetorian requires that the user class implements a ``lookup()``
class method that takes a single ``username`` argument and returns a user
instance if there is one that matches or ``None`` if there is not.
"""
return cls.query.filter_by(username=username).one_or_none()
@classmethod
def identify(cls, id):
"""
*Required Method*
flask-praetorian requires that the user class implements an ``identify()``
class method that takes a single ``id`` argument and returns user instance if
there is one that matches or ``None`` if there is not.
"""
return cls.query.get(id)
def is_valid(self):
return self.is_active
# Initialize flask app for the example
app = flask.Flask(__name__)
app.debug = True
app.config["SECRET_KEY"] = "top secret"
app.config["JWT_ACCESS_LIFESPAN"] = {"hours": 24}
app.config["JWT_REFRESH_LIFESPAN"] = {"days": 30}
# Initialize the flask-praetorian instance for the app
guard.init_app(app, User)
# Initialize a local database for the example
local_database = tempfile.NamedTemporaryFile(prefix="local", suffix=".db")
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///{}".format(local_database)
db.init_app(app)
# Initializes CORS so that the api_tool can talk to the example app
cors.init_app(app)
# Add users for the example
with app.app_context():
db.create_all()
db.session.add(
User(
username="TheDude",
hashed_password=guard.hash_password("abides"),
)
)
db.session.add(
User(
username="Walter",
hashed_password=guard.hash_password("calmerthanyouare"),
roles="admin",
)
)
db.session.add(
User(
username="Donnie",
hashed_password=guard.hash_password("iamthewalrus"),
roles="operator",
)
)
db.session.add(
User(
username="Maude",
hashed_password=guard.hash_password("andthorough"),
roles="operator,admin",
)
)
db.session.commit()
# Set up some routes for the example
@app.route("/login", methods=["POST"])
def login():
"""
Logs a user in by parsing a POST request containing user credentials and
issuing a JWT token.
.. example::
$ curl http://localhost:5000/login -X POST \
-d '{"username":"Walter","password":"calmerthanyouare"}'
"""
req = flask.request.get_json(force=True)
username = req.get("username", None)
password = req.get("password", None)
user = guard.authenticate(username, password)
ret = {"access_token": guard.encode_jwt_token(user)}
return (flask.jsonify(ret), 200)
@app.route("/protected")
@flask_praetorian.auth_required
def protected():
"""
A protected endpoint. The auth_required decorator will require a header
containing a valid JWT
.. example::
$ curl http://localhost:5000/protected -X GET \
-H "Authorization: Bearer <your_token>"
"""
return flask.jsonify(
message="protected endpoint (allowed user {})".format(
flask_praetorian.current_user().username,
)
)
@app.route("/protected_admin_required")
@flask_praetorian.roles_required("admin")
def protected_admin_required():
"""
A protected endpoint that requires a role. The roles_required decorator
will require that the supplied JWT includes the required roles
.. example::
$ curl http://localhost:5000/protected_admin_required -X GET \
-H "Authorization: Bearer <your_token>"
"""
return flask.jsonify(
message="protected_admin_required endpoint (allowed user {})".format(
flask_praetorian.current_user().username,
)
)
@app.route("/protected_operator_accepted")
@flask_praetorian.roles_accepted("operator", "admin")
def protected_operator_accepted():
"""
A protected endpoint that accepts any of the listed roles. The
roles_accepted decorator will require that the supplied JWT includes at
least one of the accepted roles
.. example::
$ curl http://localhost/protected_operator_accepted -X GET \
-H "Authorization: Bearer <your_token>"
"""
return flask.jsonify(
message="protected_operator_accepted endpoint (allowed usr {})".format(
flask_praetorian.current_user().username,
)
)
# Run the example
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
The above code can be found in example/basic.py.